├── .editorconfig ├── .github └── workflows │ └── swift.yml ├── .gitignore ├── .travis.d ├── before-install.sh └── install.sh ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── Package.swift ├── README.md ├── Sources ├── Macro │ ├── Macro.swift │ └── README.md ├── MacroCore │ ├── Buffer │ │ ├── Buffer.swift │ │ ├── BufferData.swift │ │ ├── BufferDeprecations.swift │ │ ├── BufferHexEncoding.swift │ │ ├── BufferStrings.swift │ │ └── CollectionUtils.swift │ ├── Console.swift │ ├── Dirname.swift │ ├── EnvironmentValues.swift │ ├── Events │ │ ├── ErrorEmitter.swift │ │ ├── EventListenerSet.swift │ │ └── ListenerType.swift │ ├── JSError.swift │ ├── JSON.swift │ ├── JSStubs │ │ ├── CollectionStubs.swift │ │ ├── Math.swift │ │ ├── Object.swift │ │ ├── StringStubs.swift │ │ └── ToString.swift │ ├── LeftPad.swift │ ├── MacroCore.swift │ ├── MacroError.swift │ ├── NextTick.swift │ ├── Process │ │ ├── CommandLine.swift │ │ ├── DetectXcode.swift │ │ ├── Environment.swift │ │ ├── Process.swift │ │ ├── README.md │ │ └── Warnings.swift │ ├── Regex.swift │ ├── Streams │ │ ├── Concat.swift │ │ ├── DuplexStreamType.swift │ │ ├── Pipe.swift │ │ ├── README.md │ │ ├── ReadableByteStream.swift │ │ ├── ReadableByteStreamType.swift │ │ ├── ReadableStreamBase.swift │ │ ├── ReadableStreamType.swift │ │ ├── WritableByteStream.swift │ │ ├── WritableByteStreamType.swift │ │ ├── WritableStreamBase.swift │ │ └── WritableStreamType.swift │ └── StringEncoding.swift ├── MacroTestUtilities │ └── TestServerResponse.swift ├── fs │ ├── Directory.swift │ ├── FSWatcher.swift │ ├── File.swift │ ├── JSONFile.swift │ ├── Path.swift │ ├── PosixWrappers.swift │ ├── Promise.swift │ ├── README.md │ ├── Streams │ │ ├── FileReadStream.swift │ │ ├── FileStream.swift │ │ └── FileWriteStream.swift │ ├── Utils │ │ ├── AsyncWrapper.swift │ │ ├── ResultExtensions.swift │ │ └── StatStruct.swift │ └── fs.swift ├── http │ ├── Agent │ │ ├── Agent.swift │ │ ├── ClientRequest.swift │ │ └── Foundation │ │ │ ├── URLRequestInit.swift │ │ │ ├── URLSessionAgent.swift │ │ │ └── URLSessionClientRequest.swift │ ├── BasicAuth.swift │ ├── Globals.swift │ ├── IncomingMessage.swift │ ├── QueryString.swift │ ├── README.md │ ├── Server │ │ ├── Server.swift │ │ └── ServerResponse.swift │ ├── Support │ │ ├── HTTPHeadersHolder.swift │ │ └── OutgoingMessage.swift │ └── http.swift └── xsys │ ├── Module.swift │ ├── POSIXError.swift │ ├── README.md │ ├── SocketAddress.swift │ ├── UUID.swift │ ├── dylib.swift │ ├── fd.swift │ ├── ioctl.swift │ ├── misc.swift │ ├── ntohs.swift │ ├── sockaddr_any.swift │ ├── socket.swift │ ├── time.swift │ ├── timespec.swift │ └── timeval_any.swift └── Tests ├── LinuxMain.swift └── MacroTests ├── AgentTests.swift ├── BufferTests.swift ├── ByteBufferTests.swift ├── CollectionTests.swift ├── MacroTests.swift └── XCTestManifests.swift /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = space 3 | indent_size = 2 4 | tab_width = 8 5 | max_line_length = 80 6 | trim_trailing_whitespace = false 7 | end_of_line = lf 8 | insert_final_newline = true 9 | -------------------------------------------------------------------------------- /.github/workflows/swift.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: "0 9 * * 1" 8 | 9 | jobs: 10 | linux: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | image: 16 | - swift:5.9-jammy 17 | - swift:6.1-noble 18 | container: ${{ matrix.image }} 19 | steps: 20 | - name: Checkout Repository 21 | uses: actions/checkout@v4 22 | - name: Build Swift Debug Package 23 | run: swift build -c debug 24 | - name: Build Swift Release Package 25 | run: swift build -c release 26 | - name: Run Tests 27 | run: swift test --enable-test-discovery 28 | nextstep: 29 | runs-on: macos-latest 30 | steps: 31 | - name: Select latest available Xcode 32 | uses: maxim-lobanov/setup-xcode@v1.5.1 33 | with: 34 | xcode-version: latest 35 | - name: Checkout Repository 36 | uses: actions/checkout@v4 37 | - name: Build Swift Debug Package 38 | run: swift build -c debug 39 | - name: Build Swift Release Package 40 | run: swift build -c release 41 | - name: Run Tests 42 | run: swift test 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | 92 | # hh 93 | Package.resolved 94 | xcuserdata 95 | .docker.build 96 | .swiftpm 97 | .vscode 98 | .DS_Store 99 | *.swp 100 | 101 | -------------------------------------------------------------------------------- /.travis.d/before-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ "$TRAVIS_OS_NAME" == "Linux" ]]; then 4 | sudo apt-get install -y wget \ 5 | clang-3.6 libc6-dev make git libicu52 libicu-dev \ 6 | git autoconf libtool pkg-config \ 7 | libblocksruntime-dev \ 8 | libkqueue-dev \ 9 | libpthread-workqueue-dev \ 10 | systemtap-sdt-dev \ 11 | libbsd-dev libbsd0 libbsd0-dbg \ 12 | curl libcurl4-openssl-dev \ 13 | libedit-dev \ 14 | python2.7 python2.7-dev \ 15 | libxml2 16 | 17 | sudo update-alternatives --quiet --install /usr/bin/clang clang /usr/bin/clang-3.6 100 18 | sudo update-alternatives --quiet --install /usr/bin/clang++ clang++ /usr/bin/clang++-3.6 100 19 | fi 20 | -------------------------------------------------------------------------------- /.travis.d/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # our path is: 4 | # /home/travis/build/NozeIO/Noze.io/ 5 | 6 | if ! test -z "$SWIFT_SNAPSHOT_NAME"; then 7 | # Install Swift 8 | wget "${SWIFT_SNAPSHOT_NAME}" 9 | 10 | TARBALL="`ls swift-*.tar.gz`" 11 | echo "Tarball: $TARBALL" 12 | 13 | TARPATH="$PWD/$TARBALL" 14 | 15 | cd $HOME # expand Swift tarball in $HOME 16 | tar zx --strip 1 --file=$TARPATH 17 | pwd 18 | 19 | export PATH="$PWD/usr/bin:$PATH" 20 | which swift 21 | 22 | if [ `which swift` ]; then 23 | echo "Installed Swift: `which swift`" 24 | else 25 | echo "Failed to install Swift?" 26 | exit 42 27 | fi 28 | fi 29 | 30 | swift --version 31 | 32 | 33 | # Environment 34 | 35 | TT_SWIFT_BINARY=`which swift` 36 | 37 | echo "${TT_SWIFT_BINARY}" 38 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: generic 2 | 3 | notifications: 4 | slack: nozeio:LIFY1Jtkx0FRcLq3u1WliHRZ 5 | 6 | matrix: 7 | include: 8 | - os: Linux 9 | dist: trusty 10 | env: SWIFT_SNAPSHOT_NAME="https://swift.org/builds/swift-5.0.2-release/ubuntu1404/swift-5.0.2-RELEASE/swift-5.0.2-RELEASE-ubuntu14.04.tar.gz" 11 | sudo: required 12 | - os: Linux 13 | dist: trusty 14 | env: SWIFT_SNAPSHOT_NAME="https://swift.org/builds/swift-5.1.3-release/ubuntu1404/swift-5.1.3-RELEASE/swift-5.1.3-RELEASE-ubuntu14.04.tar.gz" 15 | sudo: required 16 | - os: Linux 17 | dist: xenial 18 | env: SWIFT_SNAPSHOT_NAME="https://swift.org/builds/swift-5.2-release/ubuntu1604/swift-5.2-RELEASE/swift-5.2-RELEASE-ubuntu16.04.tar.gz" 19 | sudo: required 20 | - os: Linux 21 | dist: xenial 22 | env: SWIFT_SNAPSHOT_NAME="https://swift.org/builds/swift-5.3.1-release/ubuntu1604/swift-5.3.1-RELEASE/swift-5.3.1-RELEASE-ubuntu16.04.tar.gz" 23 | sudo: required 24 | - os: osx 25 | osx_image: xcode12 26 | - os: osx 27 | osx_image: xcode11.4 28 | 29 | before_install: 30 | - ./.travis.d/before-install.sh 31 | 32 | install: 33 | - ./.travis.d/install.sh 34 | 35 | script: 36 | - export PATH="$HOME/usr/bin:$PATH" 37 | - swift build -c release 38 | - swift build -c debug 39 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Legal 2 | 3 | By submitting a pull request, you represent that you have the right to license 4 | your contribution to ZeeZide and the community, and agree by submitting the patch 5 | that your contributions are licensed under the Apache 2.0 license. 6 | 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile 2 | 3 | # local config 4 | SWIFT_BUILD=swift build 5 | SWIFT_CLEAN=swift package clean 6 | SWIFT_BUILD_DIR=.build 7 | SWIFT_TEST=swift test 8 | CONFIGURATION=release 9 | 10 | # docker config 11 | DOCKER_BUILD_DIR=".docker.build" 12 | #SWIFT_BUILD_IMAGE="swift:5.5.3" 13 | SWIFT_BUILD_IMAGE="helje5/arm64v8-swift-dev:5.5.3" 14 | SWIFT_DOCKER_BUILD_DIR="$(DOCKER_BUILD_DIR)/aarch64-unknown-linux/$(CONFIGURATION)" 15 | #SWIFT_DOCKER_BUILD_DIR="$(DOCKER_BUILD_DIR)/x86_64-unknown-linux/$(CONFIGURATION)" 16 | DOCKER_BUILD_PRODUCT="$(DOCKER_BUILD_DIR)/$(TOOL_NAME)" 17 | 18 | 19 | SWIFT_SOURCES=\ 20 | Sources/*/*/*.swift \ 21 | Sources/*/*/*/*.swift 22 | 23 | all: 24 | $(SWIFT_BUILD) -c $(CONFIGURATION) 25 | 26 | # Cannot test in `release` configuration?! 27 | test: 28 | $(SWIFT_TEST) 29 | 30 | clean : 31 | $(SWIFT_CLEAN) 32 | # We have a different definition of "clean", might be just German 33 | # pickyness. 34 | rm -rf $(SWIFT_BUILD_DIR) 35 | 36 | $(DOCKER_BUILD_PRODUCT): $(SWIFT_SOURCES) 37 | docker run --rm \ 38 | -v "$(PWD):/src" \ 39 | -v "$(PWD)/$(DOCKER_BUILD_DIR):/src/.build" \ 40 | "$(SWIFT_BUILD_IMAGE)" \ 41 | bash -c 'cd /src && swift build -c $(CONFIGURATION)' 42 | 43 | docker-all: $(DOCKER_BUILD_PRODUCT) 44 | 45 | docker-tests: #docker-all # doesn't help, gets rebuilt anyways 46 | docker run --rm \ 47 | -v "$(PWD):/src" \ 48 | -v "$(PWD)/$(DOCKER_BUILD_DIR):/src/.build" \ 49 | "$(SWIFT_BUILD_IMAGE)" \ 50 | bash -c 'cd /src && swift test --enable-test-discovery -c $(CONFIGURATION)' 51 | 52 | docker-clean: 53 | rm $(DOCKER_BUILD_PRODUCT) 54 | 55 | docker-distclean: 56 | rm -rf $(DOCKER_BUILD_DIR) 57 | 58 | distclean: clean docker-distclean 59 | 60 | docker-emacs: 61 | docker run --rm -it \ 62 | -v "$(PWD):/src" \ 63 | -v "$(PWD)/$(DOCKER_BUILD_DIR):/src/.build" \ 64 | "$(SWIFT_BUILD_IMAGE)" \ 65 | emacs /src 66 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.5 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | 7 | name: "Macro", 8 | 9 | products: [ 10 | .library(name: "Macro", targets: [ "Macro" ]), 11 | .library(name: "MacroCore", targets: [ "MacroCore" ]), 12 | .library(name: "xsys", targets: [ "xsys" ]), 13 | .library(name: "http", targets: [ "http" ]), 14 | .library(name: "fs", targets: [ "fs" ]), 15 | .library(name: "MacroTestUtilities", targets: [ "MacroTestUtilities" ]) 16 | ], 17 | 18 | dependencies: [ 19 | .package(url: "https://github.com/apple/swift-atomics.git", 20 | from: "1.0.3"), 21 | .package(url: "https://github.com/apple/swift-nio.git", 22 | from: "2.80.0"), 23 | .package(url: "https://github.com/apple/swift-log.git", 24 | from: "1.4.4") 25 | ], 26 | 27 | targets: [ 28 | .target(name: "MacroCore", 29 | dependencies: [ 30 | .product(name: "Atomics", package: "swift-atomics"), 31 | .product(name: "NIO", package: "swift-nio"), 32 | .product(name: "NIOConcurrencyHelpers", package: "swift-nio"), 33 | .product(name: "NIOFoundationCompat", package: "swift-nio"), 34 | .product(name: "Logging", package: "swift-log"), 35 | "xsys" 36 | ], exclude: [ "Process/README.md", "Streams/README.md" ]), 37 | .target(name: "xsys", exclude: [ "README.md" ]), 38 | .target(name: "http", 39 | dependencies: [ 40 | .product(name: "NIO", package: "swift-nio"), 41 | .product(name: "NIOConcurrencyHelpers", package: "swift-nio"), 42 | .product(name: "NIOHTTP1", package: "swift-nio"), 43 | "MacroCore" 44 | ], 45 | exclude: [ "README.md" ]), 46 | .target(name: "fs", 47 | dependencies: [ 48 | .product(name: "NIO", package: "swift-nio"), 49 | "MacroCore", "xsys" 50 | ], 51 | exclude: [ "README.md" ]), 52 | 53 | // This is the Umbrella Target 54 | .target(name: "Macro", dependencies: [ "MacroCore", "xsys", "http", "fs" ], 55 | exclude: [ "README.md" ]), 56 | 57 | 58 | // MARK: - Tests 59 | 60 | .target(name: "MacroTestUtilities", dependencies: [ "Macro" ]), 61 | 62 | .testTarget(name: "MacroTests", dependencies: [ "MacroTestUtilities" ]) 63 | ] 64 | ) 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Macro 2 | 4 |

5 | 6 | A small, unopinionated "don't get into my way" / "I don't wanna `wait`" 7 | asynchronous web framework for Swift. 8 | With a strong focus on replicating the Node APIs in Swift. 9 | But in a typesafe, and fast way. 10 | 11 | Macro is a more capable variant of 12 | [µExpress](https://github.com/NozeIO/MicroExpress). 13 | The goal is still to keep a small core, but add some 14 | [Noze.io](http://noze.io) 15 | modules and concepts. 16 | 17 | Eventually it might evolve into Noze.io v2 (once backpressure enabled streams 18 | are fully working). 19 | 20 | The companion [MacroExpress](https://github.com/Macro-swift/MacroExpress) 21 | package adds Express.js-like middleware processing and functions, as well 22 | as templates. 23 | [MacroLambda](https://github.com/Macro-swift/MacroLambda) has the bits to 24 | directly deploy Macro applications on AWS Lambda. 25 | 26 | ## Streams 27 | 28 | Checkout [Noze.io for people who don't know Node](http://noze.io/noze4nonnode/), 29 | most things apply to Macro as well. 30 | 31 | ## What does it look like? 32 | 33 | The Macro [Examples](https://github.com/Macro-swift/Examples) package 34 | contains a few examples which all can run straight from the source as 35 | swift-sh scripts. 36 | 37 | The most basic HTTP server: 38 | ```swift 39 | #!/usr/bin/swift sh 40 | import Macro // @Macro-swift ~> 0.8.0 41 | 42 | http 43 | .createServer { req, res in 44 | res.writeHead(200, [ "Content-Type": "text/html" ]) 45 | res.write("

Hello Client: \(req.url)

") 46 | res.end() 47 | } 48 | .listen(1337) 49 | ``` 50 | 51 | Macro also provides additional Node-like modules, such as: 52 | - `fs` 53 | - `path` 54 | - `jsonfile` 55 | - `JSON` 56 | - `basicAuth` 57 | - `querystring` 58 | 59 | 60 | ## Environment Variables 61 | 62 | - `macro.core.numthreads` 63 | - `macro.core.iothreads` 64 | - `macro.core.retain.debug` 65 | - `macro.concat.maxsize` 66 | - `macro.streams.debug.rc` 67 | 68 | ## Async/Await 69 | 70 | This intentionally doesn't support `async`/`await` yet as proper streaming 71 | would require custom executors. Which are not yet available. 72 | 73 | ### Links 74 | 75 | - [µExpress](http://www.alwaysrightinstitute.com/microexpress-nio2/) 76 | - [Noze.io](http://noze.io) 77 | - [SwiftNIO](https://github.com/apple/swift-nio) 78 | - JavaScript Originals 79 | - [Connect](https://github.com/senchalabs/connect) 80 | - [Express.js](http://expressjs.com/en/starter/hello-world.html) 81 | - Swift Apache 82 | - [mod_swift](http://mod-swift.org) 83 | - [ApacheExpress](http://apacheexpress.io) 84 | 85 | ### Who 86 | 87 | **Macro** is brought to you by 88 | [Helge Heß](https://github.com/helje5/) / [ZeeZide](https://zeezide.de). 89 | We like feedback, GitHub stars, cool contract work, 90 | presumably any form of praise you can think of. 91 | 92 | There is a `#microexpress` channel on the 93 | [Noze.io Slack](http://slack.noze.io/). Feel free to join! 94 | -------------------------------------------------------------------------------- /Sources/Macro/Macro.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Macro.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2020-2021 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | @_exported import func MacroCore.nextTick 10 | @_exported import func MacroCore.setTimeout 11 | @_exported import let MacroCore.console 12 | @_exported import func MacroCore.parseInt 13 | @_exported import enum MacroCore.process 14 | @_exported import func MacroCore.concat 15 | @_exported import enum MacroCore.JSONModule 16 | @_exported import enum MacroCore.ReadableError 17 | @_exported import enum MacroCore.WritableError 18 | @_exported import struct MacroCore.Buffer 19 | @_exported import func MacroCore.leftpad 20 | @_exported import enum MacroCore.Math 21 | @_exported import enum MacroCore.Object 22 | @_exported import protocol NIO.EventLoop 23 | @_exported import protocol NIO.EventLoopGroup 24 | @_exported import struct Logging.Logger 25 | @_exported import func MacroCore.__dirname 26 | 27 | // To support the pipe (`|`) operators. Swift can't re-export operators? 28 | @_exported import MacroCore 29 | 30 | // MARK: - Submodules in `fs` Target 31 | 32 | import enum fs.FileSystemModule 33 | import enum fs.PathModule 34 | import enum fs.JSONFileModule 35 | public typealias fs = FileSystemModule 36 | public typealias path = PathModule 37 | public typealias jsonfile = JSONFileModule 38 | 39 | // MARK: - Submodules in `http` Target 40 | 41 | @_exported import class http.IncomingMessage 42 | @_exported import class http.ServerResponse 43 | import enum http.HTTPModule 44 | import enum http.BasicAuthModule 45 | import enum http.QueryStringModule 46 | public typealias http = HTTPModule 47 | public typealias basicAuth = BasicAuthModule 48 | public typealias querystring = QueryStringModule 49 | 50 | // MARK: - Process stuff 51 | 52 | public var argv : [ String ] { return process.argv } 53 | public var env : [ String : String ] { return process.env } 54 | 55 | // MARK: - Foundation 56 | 57 | #if canImport(Foundation) 58 | import struct Foundation.Data 59 | import struct Foundation.Date 60 | 61 | public typealias Data = Foundation.Data 62 | public typealias Date = Foundation.Date 63 | #endif 64 | -------------------------------------------------------------------------------- /Sources/Macro/README.md: -------------------------------------------------------------------------------- 1 | # Macro 2 | 3 | Macro is the package you may usually want to import. 4 | It imports ALL Macro submodules and re-exports their functions. 5 | 6 | Note that doing a `import Macro` still results in namespaced 7 | functions, e.g. the `http` prefix is necessary: 8 | 9 | ```swift 10 | import Macro 11 | 12 | http.createServer { req, res in 13 | ... 14 | } 15 | ``` 16 | 17 | You can still import the specific module and get a top-level import: 18 | ``` 19 | import http` 20 | 21 | createServer { req, res in } 22 | ... 23 | } 24 | ``` 25 | 26 | If you don't want to import all Macro modules, you can also just import 27 | individual modules like `fs` (e.g. w/o HTTP/NIOHTTP1). 28 | -------------------------------------------------------------------------------- /Sources/MacroCore/Buffer/BufferData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BufferData.swift 3 | // Macro 4 | // 5 | // Created by Helge Heß. 6 | // Copyright © 2020-2024 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | #if canImport(Foundation) 10 | 11 | import struct Foundation.Data 12 | import protocol Foundation.ContiguousBytes 13 | import NIOCore 14 | import NIOFoundationCompat 15 | 16 | public extension Buffer { 17 | 18 | /** 19 | * Initialize the Buffer with the contents of the given `Data`. Copies the 20 | * bytes. 21 | * 22 | * - Parameters: 23 | * - data: The bytes to copy into the buffer. 24 | */ 25 | @inlinable 26 | init(_ data: Data) { 27 | self.init(capacity: data.count) 28 | byteBuffer.writeBytes(data) 29 | } 30 | 31 | @inlinable 32 | var data : Data { 33 | return byteBuffer.getData(at : byteBuffer.readerIndex, 34 | length : byteBuffer.readableBytes) ?? Data() 35 | } 36 | } 37 | 38 | extension Buffer: ContiguousBytes { 39 | 40 | @inlinable 41 | public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) 42 | rethrows -> R 43 | { 44 | return try byteBuffer.readableBytesView.withUnsafeBytes(body) 45 | } 46 | 47 | } 48 | 49 | #endif // canImport(Foundation) 50 | -------------------------------------------------------------------------------- /Sources/MacroCore/Buffer/BufferDeprecations.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BufferDeprecations.swift 3 | // Macro 4 | // 5 | // Created by Helge Heß. 6 | // Copyright © 2020-2023 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | #if canImport(Foundation) 10 | import Foundation 11 | 12 | public extension Buffer { 13 | 14 | @available(*, deprecated, message: "Do not use `encoding` label.") 15 | @inlinable 16 | static func from(_ string: String, encoding: String.Encoding) 17 | throws -> Buffer 18 | { 19 | return try from(string, encoding) 20 | } 21 | 22 | @available(*, deprecated, message: "Do not use `encoding` label.") 23 | @inlinable 24 | static func from(_ string: S, 25 | encoding: String.Encoding) 26 | throws -> Buffer 27 | { 28 | return try from(string, encoding) 29 | } 30 | 31 | @available(*, deprecated, message: "Do not use `encoding` label.") 32 | @inlinable 33 | static func from(_ string: S, encoding: String) throws 34 | -> Buffer 35 | { 36 | return try from(string, encoding) 37 | } 38 | } 39 | 40 | #endif // canImport(Foundation) 41 | -------------------------------------------------------------------------------- /Sources/MacroCore/Buffer/BufferHexEncoding.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BufferHexEncoding.swift 3 | // Macro 4 | // 5 | // Created by Helge Heß. 6 | // Copyright © 2020-2024 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | import NIOCore 10 | 11 | @usableFromInline 12 | internal let hexAlphabet = "0123456789abcdef".unicodeScalars.map { $0 } 13 | @usableFromInline 14 | internal let upperHexAlphabet = "0123456789ABCDEF".unicodeScalars.map { $0 } 15 | 16 | public extension Buffer { 17 | 18 | /** 19 | * Returns the data in the buffer as a hex encoded string. 20 | * 21 | * Example: 22 | * ```swift 23 | * let buffer = Buffer("Hello".utf8) 24 | * let string = buffer.hexEncodedString() 25 | * // "48656c6c6f" 26 | * ``` 27 | * 28 | * Each byte is represented by two hex digits, e.g. `6c` in the example. 29 | * 30 | * `hex` is also recognized as a string encoding, this works as well: 31 | * ```swift 32 | * let buffer = Buffer("Hello".utf8) 33 | * let string = try buffer.toString("hex") 34 | * // "48656c6c6f" 35 | * ``` 36 | * 37 | * - Parameters: 38 | * - uppercase: If true, the a-f hexdigits are generated in 39 | * uppercase (ABCDEF). Defaults to false. 40 | * - separator: A string to insert between the individual bytes, e.g. " " 41 | * or ":" 42 | * - Returns: The Buffer encoded as a hex string. 43 | */ 44 | @inlinable 45 | func hexEncodedString(uppercase: Bool = false, separator: String? = nil) 46 | -> String 47 | { 48 | // https://stackoverflow.com/questions/39075043/how-to-convert-data-to-hex 49 | return String(byteBuffer.readableBytesView.reduce(into: "".unicodeScalars, { 50 | ( result, value ) in 51 | if uppercase { 52 | if let separator = separator, !result.isEmpty { 53 | result.append(contentsOf: separator.unicodeScalars) 54 | } 55 | result.append(upperHexAlphabet[Int(value / 16)]) 56 | result.append(upperHexAlphabet[Int(value % 16)]) 57 | } 58 | else { 59 | if let separator = separator, !result.isEmpty { 60 | result.append(contentsOf: separator.unicodeScalars) 61 | } 62 | result.append(hexAlphabet[Int(value / 16)]) 63 | result.append(hexAlphabet[Int(value % 16)]) 64 | } 65 | })) 66 | } 67 | 68 | /** 69 | * Appends the bytes represented by a hex encoded string to the Buffer. 70 | * 71 | * Example: 72 | * ```swift 73 | * let buffer = Buffer() 74 | * buffer.writeHexString("48656c6c6f") 75 | * let string = try buffer.toString() 76 | * // "Hello" 77 | * ``` 78 | * `hex` is also recognized as a string encoding, this works as well: 79 | * ```swift 80 | * let buffer = try Buffer.from("48656c6c6f", "hex") 81 | * let string = try buffer.toString() 82 | * // "Hello" 83 | * ``` 84 | * - Parameters: 85 | * - hexString: A hex encoded string, no spaces etc allowed between the 86 | * bytes. 87 | * - Returns: true if successful, false if the input is invalid 88 | */ 89 | @inlinable 90 | @discardableResult 91 | mutating func writeHexString(_ hexString: S) -> Bool { 92 | guard !hexString.isEmpty else { return true } 93 | 94 | // https://stackoverflow.com/questions/41485494/convert-hex-encoded-string 95 | func decodeNibble(u: UInt16) -> UInt8? { 96 | switch(u) { 97 | case 0x30 ... 0x39: return UInt8(u - 0x30) 98 | case 0x41 ... 0x46: return UInt8(u - 0x41 + 10) 99 | case 0x61 ... 0x66: return UInt8(u - 0x61 + 10) 100 | default: return nil 101 | } 102 | } 103 | 104 | var even = true 105 | var byte : UInt8 = 0 106 | for c in hexString.utf16 { 107 | guard let val = decodeNibble(u: c) else { return false } 108 | if even { 109 | byte = val << 4 110 | } 111 | else { 112 | byte += val 113 | byteBuffer.writeInteger(byte) 114 | } 115 | even = !even 116 | } 117 | return even 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /Sources/MacroCore/Console.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Console.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | import struct Logging.Logger 10 | 11 | public let console = Logger(label: "μ.console") 12 | 13 | /** 14 | * Just a small JavaScript like `console` shim around the Swift Logging API. 15 | */ 16 | public extension Logger { 17 | 18 | @available(*, deprecated, message: "please use `console` directly") 19 | var logger : Logger { return self } 20 | 21 | @usableFromInline 22 | internal func string(for msg: String, _ values: [ Any? ]) -> Logger.Message { 23 | var message = msg 24 | for value in values { 25 | if let value = value { 26 | message.append(" ") 27 | if let s = value as? String { 28 | message.append(s) 29 | } 30 | else if let s = value as? CustomStringConvertible { 31 | message.append(s.description) 32 | } 33 | else { 34 | message.append("\(value)") 35 | } 36 | } 37 | else { 38 | message.append(" ") 39 | } 40 | } 41 | return Logger.Message(stringLiteral: message) 42 | } 43 | 44 | @inlinable func error(_ msg: @autoclosure () -> String, _ values : Any?...) { 45 | error(string(for: msg(), values)) 46 | } 47 | @inlinable func warn (_ msg: @autoclosure () -> String, _ values : Any?...) { 48 | warning(string(for: msg(), values)) 49 | } 50 | @inlinable func log (_ msg: @autoclosure () -> String, _ values : Any?...) { 51 | notice(string(for: msg(), values)) 52 | } 53 | @inlinable func info (_ msg: @autoclosure () -> String, _ values : Any?...) { 54 | info(string(for: msg(), values)) 55 | } 56 | @inlinable func trace(_ msg: @autoclosure () -> String, _ values : Any?...) { 57 | trace(string(for: msg(), values)) 58 | } 59 | 60 | func dir(_ obj: Any?) { 61 | guard let obj = obj else { 62 | return notice("") 63 | } 64 | 65 | struct StringOutputStream: TextOutputStream { 66 | var value = "" 67 | mutating func write(_ string: String) { value += string } 68 | } 69 | var out = StringOutputStream() 70 | 71 | dump(obj, to: &out) 72 | return notice(Logger.Message(stringLiteral: out.value)) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Sources/MacroCore/Dirname.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dirname.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | import struct Foundation.URL 10 | import class Foundation.FileManager 11 | 12 | /** 13 | * An attempt to emulate the `__dirname` variable in Node modules, 14 | * requires a function in Swift. 15 | * `__dirname` gives the directory location of the current Swift file, commonly 16 | * used to lookup resources that live alongside the Swift source file. 17 | * 18 | * Note: Do not confuse w/ `process.cwd()`, which returns the current directory 19 | * of the process. 20 | * 21 | * Note: Can do synchronous I/O, be careful when to call this! 22 | * 23 | * ### Implementation 24 | * 25 | * The complicated thing is that SPM does not have proper resource locations. 26 | * A workaround is to use the `#file` compiler directive, which contains the 27 | * location of the Swift sourcefile _calling_ `__dirname()`. 28 | * 29 | * Now the difficult part is, that the environment may not have access to the 30 | * source file anymore (because just the library is being deployed). 31 | * In this case, we return `process.cwd`. 32 | * 33 | * ### `swift sh` 34 | * 35 | * There are extra issues w/ [swift-sh](https://github.com/mxcl/swift-sh): 36 | * 37 | * https://github.com/mxcl/swift-sh/issues/101 38 | * 39 | * So we catch this and (try to) use the CWD in that situation. 40 | * Note: This does not yet work properly for nested modules! 41 | */ 42 | public func ___dirname(caller: String) -> String { 43 | // The check for `swift sh` 44 | let fm = FileManager.default 45 | 46 | if caller.contains("swift-sh.cache"), let toolname = process.env["_"] { 47 | let dirURL = URL(fileURLWithPath: process.cwd(), isDirectory: true) 48 | let toolURL : URL = { 49 | if #available(macOS 10.11, iOS 11, *) { 50 | return URL(fileURLWithPath: toolname, relativeTo: dirURL) 51 | } 52 | else { 53 | return dirURL.appendingPathComponent(toolname) // TBD 54 | } 55 | }() 56 | 57 | if fm.fileExists(atPath: toolURL.path) { 58 | return toolURL.deletingLastPathComponent().path 59 | } 60 | } 61 | 62 | if fm.fileExists(atPath: caller) { 63 | return URL(fileURLWithPath: caller).deletingLastPathComponent().path 64 | } 65 | 66 | return process.cwd() 67 | } 68 | 69 | #if swift(>=5.3) 70 | @inlinable 71 | public func __dirname(caller: String = #filePath) -> String { 72 | return ___dirname(caller: caller) 73 | } 74 | #else 75 | @inlinable 76 | public func __dirname(caller: String = #file) -> String { 77 | return ___dirname(caller: caller) 78 | } 79 | #endif 80 | -------------------------------------------------------------------------------- /Sources/MacroCore/EnvironmentValues.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EnvironmentValues.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | /** 10 | * A protocol describing a value which can be put into a `Macro` environment 11 | * (`EnvironmentValue`'s). 12 | * The sole purpose is to avoid stringly typed environment keys (like 13 | * "") 14 | * 15 | * Example: 16 | * 17 | * enum LoginUserEnvironmentKey: EnvironmentKey { 18 | * static let defaultValue = "" 19 | * } 20 | * 21 | * In addition to the key definition, one usually declares an accessor to the 22 | * respective environment holder, for example the `IncomingMessage`: 23 | * 24 | * extension IncomingMessage { 25 | * 26 | * var loginUser : String { 27 | * set { self[LoginUserEnvironmentKey.self] = newValue } 28 | * get { self[LoginUserEnvironmentKey.self] } 29 | * } 30 | * } 31 | * 32 | * It can then be used like: 33 | * 34 | * app.use { req, res, next in 35 | * console.log("active user:", req.loginUser) 36 | * next() 37 | * } 38 | * 39 | * If the value really is optional, it can be declared an optional: 40 | * 41 | * enum DatabaseConnectionEnvironmentKey: EnvironmentKey { 42 | * static let defaultValue : DatabaseConnection? 43 | * } 44 | * 45 | * To add a shorter name for environment dumps, implement the `loggingKey` 46 | * property: 47 | * 48 | * enum DatabaseConnectionEnvironmentKey: EnvironmentKey { 49 | * static let defaultValue : DatabaseConnection? = nil 50 | * static let loggingKey = "db" 51 | * } 52 | * 53 | */ 54 | public protocol EnvironmentKey { 55 | 56 | associatedtype Value 57 | 58 | /** 59 | * If a value isn't set in the environment, the `defaultValue` will be 60 | * returned. 61 | */ 62 | static var defaultValue: Self.Value { get } 63 | 64 | /** 65 | * The logging key is used when the environment is logged into a string. 66 | * (It defaults to the Swift runtime name of the implementing type). 67 | */ 68 | static var loggingKey : String { get } 69 | } 70 | 71 | public extension EnvironmentKey { 72 | 73 | @inlinable 74 | static var loggingKey : String { 75 | return String(describing: self) 76 | } 77 | } 78 | 79 | #if swift(>=5.1) 80 | /** 81 | * A dictionary which can hold values assigned to `EnvironmentKey`s. 82 | * 83 | * This is a way to avoid stringly typed `extra` APIs by making use of type 84 | * identity in Swift. 85 | * In Node/JS you would usually just attach properties to the `IncomingMessage` 86 | * object. 87 | * 88 | * To drive `EnvironmentValues`, `EnvironmentKey`s need to be defined. Since 89 | * the type of an `EnviromentKey` is globally unique within Swift, it can be 90 | * used to key into the structure to the store the associated value. 91 | * 92 | * Note that in APIs like SwiftUI or SwiftBlocksUI, EnvironmentValues are 93 | * usually "stacked" in the hierarchy of Views or Blocks. 94 | * That's not necessarily the case in Macro, though Macro application can also 95 | * use it like that. 96 | */ 97 | @frozen 98 | public struct EnvironmentValues { 99 | 100 | @usableFromInline 101 | var values = [ ObjectIdentifier : ( loggingKey: String, value: Any ) ]() 102 | } 103 | #else 104 | public struct EnvironmentValues { // 5.0 compat, no @frozen 105 | @usableFromInline 106 | var values = [ ObjectIdentifier : ( loggingKey: String, value: Any ) ]() 107 | } 108 | #endif 109 | 110 | public extension EnvironmentValues { 111 | 112 | static let empty = EnvironmentValues() 113 | 114 | @inlinable 115 | var isEmpty : Bool { return values.isEmpty } 116 | @inlinable 117 | var count : Int { return values.count } 118 | 119 | @inlinable 120 | subscript(key: K.Type) -> K.Value { 121 | set { 122 | values[ObjectIdentifier(key)] = ( K.loggingKey, newValue ) 123 | } 124 | get { 125 | guard let value = values[ObjectIdentifier(key)]?.value else { 126 | return K.defaultValue 127 | } 128 | guard let typedValue = value as? K.Value else { 129 | assertionFailure("unexpected typed value: \(value)") 130 | return K.defaultValue 131 | } 132 | return typedValue 133 | } 134 | } 135 | 136 | @inlinable 137 | var loggingDictionary : [ String : Any ] { 138 | var dict = [ String : Any ]() 139 | dict.reserveCapacity(values.count) 140 | for ( key, value ) in values.values { 141 | dict[key] = value 142 | } 143 | return dict 144 | } 145 | } 146 | 147 | public protocol EnvironmentValuesHolder { 148 | 149 | var environment : EnvironmentValues { set get } 150 | 151 | subscript(key: K.Type) -> K.Value { set get } 152 | } 153 | 154 | public extension EnvironmentValuesHolder { 155 | 156 | @inlinable 157 | subscript(key: K.Type) -> K.Value { 158 | set { environment[key] = newValue } 159 | get { return environment[key] } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /Sources/MacroCore/Events/ErrorEmitter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ErrorEmitter.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2020-2023 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | public protocol ErrorEmitterType { 10 | 11 | typealias ErrorCB = ( Error ) -> Void 12 | 13 | @discardableResult func onError (execute: @escaping ErrorCB) -> Self 14 | @discardableResult func onceError(execute: @escaping ErrorCB) -> Self 15 | } 16 | 17 | public protocol ErrorEmitterTarget { 18 | 19 | func emit(error: Error) 20 | } 21 | 22 | 23 | import struct Logging.Logger 24 | 25 | /** 26 | * A reusable base class for objects which can emit errors. 27 | */ 28 | open class ErrorEmitter : ErrorEmitterType, ErrorEmitterTarget { 29 | 30 | @inlinable 31 | public var core : MacroCore { return MacroCore.shared } 32 | 33 | @inlinable 34 | open var errorLog : Logger { return console } 35 | 36 | public init() {} 37 | 38 | // MARK: - ErrorEmitter 39 | 40 | public final var errorListeners = EventListenerSet() 41 | 42 | @inlinable 43 | open func emit(error: Error) { 44 | if errorListeners.isEmpty { 45 | let id = String(Int(bitPattern: ObjectIdentifier(self)), radix: 16) 46 | let objectInfo = "\(type(of: self)):0x\(id)" 47 | errorLog.error("[\(objectInfo)] Error not handled: \(error)") 48 | } 49 | else { 50 | errorListeners.emit(error) 51 | } 52 | } 53 | 54 | @inlinable 55 | @discardableResult 56 | public func onError(execute: @escaping ErrorCB) -> Self { 57 | errorListeners += execute 58 | return self 59 | } 60 | 61 | @inlinable 62 | @discardableResult 63 | public func onceError(execute: @escaping ErrorCB) -> Self { 64 | errorListeners.once(execute) 65 | return self 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Sources/MacroCore/Events/ListenerType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListenerType.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | public protocol ListenerType: AnyObject {} 10 | -------------------------------------------------------------------------------- /Sources/MacroCore/JSError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSError.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2021 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | /** 10 | * A simple Error similar to the JavaScript error. 11 | */ 12 | public struct JSError: Swift.Error { 13 | 14 | public var name : String 15 | public var message : String 16 | public var fileName : StaticString 17 | public var lineNumber : Int 18 | 19 | public init(_ name: String, _ message: String = "", 20 | fileName: StaticString = #file, lineNumber: Int = #line) 21 | { 22 | self.name = name 23 | self.message = message 24 | self.fileName = fileName 25 | self.lineNumber = lineNumber 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/MacroCore/JSStubs/CollectionStubs.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionStubs.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2021 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | public extension Collection { 10 | 11 | @inlinable 12 | var length: Int { return count } 13 | } 14 | 15 | public extension Collection where Index == Int, Element: Equatable { 16 | 17 | @inlinable 18 | func indexOf(_ element: Element) -> Int { 19 | return firstIndex(of: element) ?? -1 20 | } 21 | } 22 | 23 | public extension RandomAccessCollection where Index == Int { 24 | 25 | @inlinable 26 | func slice(_ start: Int = 0, _ end: Int? = nil) -> [ Element ] { 27 | guard isEmpty else { return [] } 28 | var start = start >= 0 ? start : count + start 29 | var end = end.flatMap { end in end >= 0 ? end : count + end } ?? count 30 | if start < 0 { start = 0 } 31 | else if start >= count { start = count - 1 } 32 | if end < 0 { end = 0 } 33 | else if end >= count { end = count - 1 } 34 | if start == end { return [] } 35 | if end < start { swap(&start, &end) } 36 | return Array(self[start.. Element? { 45 | guard !isEmpty else { return nil } 46 | return removeFirst() 47 | } 48 | 49 | @inlinable 50 | @discardableResult 51 | mutating func unshift(_ element: Element) -> Int { 52 | insert(element, at: startIndex) 53 | return count 54 | } 55 | } 56 | 57 | public extension RangeReplaceableCollection { 58 | 59 | @inlinable 60 | func concat() -> Self { return self } 61 | 62 | @inlinable 63 | func concat(_ element1: Element, elements: Element...) -> Self { 64 | var copy = self 65 | copy.append(element1) 66 | copy += elements 67 | return copy 68 | } 69 | 70 | @inlinable 71 | func concat(_ sequence1: S, sequences: S...) -> Self 72 | where S: Sequence, S.Element == Element 73 | { 74 | var copy = self 75 | copy += sequence1 76 | sequences.forEach { copy += $0 } 77 | return copy 78 | } 79 | 80 | @inlinable 81 | func concat(_ collection1: C, collections: C...) -> Self 82 | where C: Collection, C.Element == Element 83 | { 84 | let totalCount = self.count + collection1.count 85 | + collections.reduce(0, { $0 + $1.count }) 86 | var copy = self 87 | copy.reserveCapacity(totalCount) 88 | copy += collection1 89 | collections.forEach { copy += $0 } 90 | return copy 91 | } 92 | } 93 | 94 | public extension RangeReplaceableCollection 95 | where Self: BidirectionalCollection 96 | { 97 | 98 | @inlinable 99 | mutating func push(_ element: Element) { append(element) } 100 | 101 | @inlinable 102 | @discardableResult 103 | mutating func pop() -> Element? { 104 | guard !isEmpty else { return nil } 105 | return removeLast() 106 | } 107 | } 108 | 109 | public extension Sequence { 110 | 111 | /** 112 | * Treat optionals as booleans when filtering. 113 | * 114 | * Example: 115 | * 116 | * return Object.keys(index.toc()) 117 | * .filter { $0.match("^" + searchPath.replace("\\" /*/g?!*/, "\\\\")) } 118 | */ 119 | @inlinable 120 | func filter(_ isIncluded: ( Element ) throws -> Value?) 121 | rethrows -> [ Element ] 122 | { 123 | return try filter { 124 | switch try isIncluded($0) { 125 | case .some: return true 126 | case .none: return false 127 | } 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /Sources/MacroCore/JSStubs/Math.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Math.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2021 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | #if canImport(Foundation) // Just a hack-around to import the system mods 10 | import Foundation 11 | #endif 12 | 13 | public enum Math {} 14 | 15 | // Incomplete, a few things are missing. 16 | 17 | public extension Math { 18 | static let PI = Double.pi 19 | } 20 | 21 | public extension Math { 22 | 23 | @inlinable 24 | static func abs(_ x: T) -> T where T: Comparable & SignedNumeric { 25 | return Swift.abs(x) 26 | } 27 | 28 | @inlinable 29 | static func max(_ x: T, _ y: T) -> T where T: Comparable { 30 | return Swift.max(x, y) 31 | } 32 | @inlinable 33 | static func min(_ x: T, _ y: T) -> T where T: Comparable { 34 | return Swift.min(x, y) 35 | } 36 | 37 | @inlinable 38 | static func random() -> Double { return Double.random(in: 0...1.0) } 39 | 40 | @inlinable 41 | static func round(_ v: Double) -> Double { return v.rounded() } 42 | 43 | @inlinable 44 | static func sign(_ value: T) -> T { 45 | return value.signum() 46 | } 47 | @inlinable 48 | static func sign(_ value: Double) -> FloatingPointSign { 49 | return value.sign 50 | } 51 | } 52 | 53 | public extension Math { 54 | 55 | @inlinable 56 | static func max(_ x: T, _ y: T, _ more: T...) -> T where T: Comparable { 57 | return more.reduce(Math.max(x, y), Math.max) 58 | } 59 | @inlinable 60 | static func min(_ x: T, _ y: T, _ more: T...) -> T where T: Comparable { 61 | return more.reduce(Math.min(x, y), Math.min) 62 | } 63 | } 64 | 65 | #if canImport(Foundation) 66 | 67 | public extension Math { 68 | 69 | @inlinable static func acos(_ value: Double) -> Double { 70 | return Foundation.acos(value) 71 | } 72 | @inlinable static func acosh(_ value: Double) -> Double { 73 | return Foundation.acosh(value) 74 | } 75 | @inlinable static func asin(_ value: Double) -> Double { 76 | return Foundation.asin(value) 77 | } 78 | @inlinable static func asinh(_ value: Double) -> Double { 79 | return Foundation.asinh(value) 80 | } 81 | @inlinable static func atan(_ value: Double) -> Double { 82 | return Foundation.atan(value) 83 | } 84 | @inlinable static func atanh(_ value: Double) -> Double { 85 | return Foundation.atanh(value) 86 | } 87 | @inlinable static func atan2(_ x: Double, _ y: Double) -> Double { 88 | return Foundation.atan2(x, y) 89 | } 90 | @inlinable static func ceil(_ value: Double) -> Double { 91 | return Foundation.ceil(value) 92 | } 93 | @inlinable static func cos(_ value: Double) -> Double { 94 | return Foundation.cos(value) 95 | } 96 | @inlinable static func cosh(_ value: Double) -> Double { 97 | return Foundation.cosh(value) 98 | } 99 | @inlinable static func exp(_ value: Double) -> Double { 100 | return Foundation.exp(value) 101 | } 102 | @inlinable static func expm1(_ value: Double) -> Double { 103 | return Foundation.expm1(value) 104 | } 105 | @inlinable static func floor(_ value: Double) -> Double { 106 | return Foundation.floor(value) 107 | } 108 | @inlinable static func hypot(_ x: Double, _ y: Double) -> Double { 109 | return Foundation.hypot(x, y) 110 | } 111 | @inlinable static func log(_ value: Double) -> Double { 112 | return Foundation.log(value) 113 | } 114 | @inlinable static func log1p(_ value: Double) -> Double { 115 | return Foundation.log1p(value) 116 | } 117 | @inlinable static func log10(_ value: Double) -> Double { 118 | return Foundation.log10(value) 119 | } 120 | @inlinable static func log2(_ value: Double) -> Double { 121 | return Foundation.log2(value) 122 | } 123 | @inlinable static func pow(_ x: Double, _ y: Double) -> Double { 124 | return Foundation.pow(x, y) 125 | } 126 | @inlinable static func sin(_ value: Double) -> Double { 127 | return Foundation.sin(value) 128 | } 129 | @inlinable static func sinh(_ value: Double) -> Double { 130 | return Foundation.sinh(value) 131 | } 132 | @inlinable static func sqrt(_ value: Double) -> Double { 133 | return Foundation.sqrt(value) 134 | } 135 | @inlinable static func tan(_ value: Double) -> Double { 136 | return Foundation.tan(value) 137 | } 138 | @inlinable static func tanh(_ value: Double) -> Double { 139 | return Foundation.tanh(value) 140 | } 141 | } 142 | 143 | public extension Math { 144 | 145 | @inlinable 146 | static func hypot(_ x: Double, _ y: Double, _ more: Double...) -> Double { 147 | return more.reduce(Math.hypot(x, y), Math.hypot) 148 | } 149 | } 150 | #endif 151 | -------------------------------------------------------------------------------- /Sources/MacroCore/JSStubs/Object.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Object.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2021 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | public enum Object {} 10 | 11 | public extension Object { 12 | 13 | @inlinable 14 | static func keys(_ dictionary: [ K : V ]) -> [ K ] { 15 | return Array(dictionary.keys) 16 | } 17 | 18 | @inlinable 19 | static func keys(_ array: [ V ]) -> [ Int ] { 20 | return Array(array.indices) 21 | } 22 | 23 | @inlinable 24 | static func keys(_ object: Any) -> [ String ] { 25 | return Mirror(reflecting: object).children.compactMap { label, _ in 26 | return label 27 | } 28 | } 29 | 30 | @inlinable 31 | static func entries(_ dictionary: [ K : V ]) -> [ ( K, V ) ] { 32 | return Array(dictionary) 33 | } 34 | 35 | @inlinable 36 | static func entries(_ array: [ V ]) -> [ ( Int, V ) ] { 37 | return Array(array.enumerated()) 38 | } 39 | 40 | @inlinable 41 | static func entries(_ object: Any) -> [ ( String, Any ) ] { 42 | return Mirror(reflecting: object).children.compactMap { label, value in 43 | guard let label = label else { return nil } 44 | return ( label, value ) 45 | } 46 | } 47 | 48 | @inlinable 49 | static func values(_ dictionary: [ K : V ]) -> [ V ] { 50 | return Array(dictionary.values) 51 | } 52 | 53 | @inlinable 54 | static func values(_ array: [ V ]) -> [ V ] { 55 | return array 56 | } 57 | 58 | @inlinable 59 | static func values(_ object: Any) -> [ Any ] { 60 | return Mirror(reflecting: object).children 61 | .filter { $0.label != nil } 62 | .map { $0.value } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Sources/MacroCore/JSStubs/StringStubs.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StringStubs.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2021-2023 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | #if canImport(Foundation) 10 | import Foundation 11 | 12 | @inlinable 13 | public func parseInt(_ string: String, _ radix: Int = 10) -> Int? { 14 | guard radix >= 2 && radix <= 36 else { return nil } 15 | let trimmed = string.trimmingCharacters(in: .whitespacesAndNewlines) 16 | return Int(trimmed, radix: radix) 17 | } 18 | 19 | @inlinable 20 | public func parseInt(_ string: T, _ radix: Int = 10) -> Int? { 21 | return parseInt(String(describing: string), radix) 22 | } 23 | 24 | public extension String { 25 | 26 | /// Same like `String.split(separator:)`, but returns a `[ String ]` array 27 | @inlinable 28 | func split(_ separator: Character) -> [ String ] { 29 | return split(separator: separator).map { String($0) } 30 | } 31 | 32 | @inlinable 33 | func trim() -> String { 34 | return trimmingCharacters(in: .whitespacesAndNewlines) 35 | } 36 | } 37 | #endif // canImport(Foundation) 38 | 39 | public extension Sequence where Element: StringProtocol { 40 | 41 | @inlinable 42 | func join(_ separator: String = ",") -> String { 43 | return joined(separator: separator) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/MacroCore/JSStubs/ToString.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ToString.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2021 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | public extension BinaryInteger { 10 | 11 | @inlinable 12 | func toString(_ radix: Int) -> String { 13 | return String(self, radix: radix) 14 | } 15 | @inlinable 16 | func toString() -> String { return toString(10) } 17 | } 18 | 19 | public extension Swift.Error { 20 | 21 | @inlinable 22 | func toString() -> String { return String(describing: self) } 23 | } 24 | 25 | #if canImport(Foundation) 26 | import struct Foundation.Date 27 | import class Foundation.DateFormatter 28 | 29 | public extension Date { 30 | 31 | /* JS/Node Does: 32 | * new Date().toString() 33 | * 'Tue Apr 13 2021 16:24:10 GMT+0200 (Central European Summer Time)' 34 | */ 35 | @usableFromInline 36 | internal static let jsDateFmt : DateFormatter = { 37 | let df = DateFormatter() 38 | // Tue 13 Apr 2021 14:26:59 GMT+0000 (Coordinated Universal Time) 39 | df.dateFormat = "E d MMM yyyy HH:mm:ss 'GMT'Z '('zzzz')'" 40 | return df 41 | }() 42 | 43 | @inlinable 44 | func toString() -> String { 45 | return Date.jsDateFmt.string(from: self) 46 | } 47 | } 48 | #endif // canImport(Foundation) 49 | -------------------------------------------------------------------------------- /Sources/MacroCore/LeftPad.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LeftPad.swift 3 | // MacroCore 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | /** 10 | * An awesome module following the leads of [left-pad](http://left-pad.io). 11 | */ 12 | public enum LeftPadModule {} 13 | 14 | public extension LeftPadModule { 15 | 16 | @inlinable 17 | static func leftpad(_ string: String, _ length: Int, 18 | _ paddingCharacter: Character = " ") -> String 19 | { 20 | let count = string.count 21 | guard count < length else { return string } 22 | let padCount = length - count 23 | return String(repeating: paddingCharacter, count: padCount) + string 24 | } 25 | } 26 | 27 | @inlinable 28 | public func leftpad(_ string: String, _ length: Int, 29 | _ paddingCharacter: Character = " ") -> String 30 | { 31 | return LeftPadModule.leftpad(string, length, paddingCharacter) 32 | } 33 | 34 | public extension String { 35 | 36 | @inlinable 37 | func padStart(_ targetLength: Int, _ padString: String = " ") -> String { 38 | return _pad(targetLength: targetLength, padString: padString) { $0 + self } 39 | } 40 | 41 | @inlinable 42 | func padEnd(_ targetLength: Int, _ padString: String = " ") -> String { 43 | return _pad(targetLength: targetLength, padString: padString) { self + $0 } 44 | } 45 | 46 | @usableFromInline 47 | internal func _pad(targetLength: Int, padString: String = " ", 48 | combine: ( Substring ) -> String) -> String { 49 | let count = self.count 50 | guard count < targetLength else { return self } 51 | 52 | // That is so complex it might actually deserve a test :-> 53 | let missingChars = targetLength - count 54 | let padWidth = padString.count 55 | let padCount = (missingChars % padWidth == 0) 56 | ? (missingChars / padWidth) 57 | : (missingChars / padWidth) + 1 58 | let dropCount = padCount * padWidth - missingChars 59 | let padder = String(repeating: padString, count: padCount) 60 | .dropLast(dropCount) 61 | return combine(padder) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Sources/MacroCore/MacroError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MacroError.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | public enum MacroError: Swift.Error { 10 | 11 | case failedToConvertByteBufferToData 12 | 13 | } 14 | -------------------------------------------------------------------------------- /Sources/MacroCore/NextTick.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NextTick.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | import NIO 10 | 11 | public extension MacroCore { 12 | 13 | /// Enqueue the given closure for later dispatch in the Q. 14 | @inlinable 15 | func nextTick(on eventLoop : EventLoop? = nil, 16 | _ execute : @escaping () -> Void) 17 | { 18 | // Node says that tick() is special in that it runs before IO events. Is the 19 | // same true for NIO? 20 | 21 | let module = MacroCore.shared 22 | module.retain() // TBD: expensive? Do in here? 23 | 24 | let loop = eventLoop 25 | ?? MultiThreadedEventLoopGroup.currentEventLoop 26 | ?? module.eventLoopGroup.next() 27 | loop.execute { 28 | execute() 29 | module.release() 30 | } 31 | } 32 | 33 | /// Execute the given closure after the amount of milliseconds given. 34 | @inlinable 35 | func setTimeout(on eventLoop : EventLoop? = nil, 36 | _ milliseconds : Int, 37 | _ execute : @escaping () -> Void) 38 | { 39 | // TBD: what is the proper place for this? 40 | // TODO: in JS this also allows for a set of arguments to be passed to the 41 | // callback (but who uses this facility?) 42 | 43 | let module = MacroCore.shared 44 | module.retain() // TBD: expensive? Do in here? 45 | 46 | let loop = eventLoop 47 | ?? MultiThreadedEventLoopGroup.currentEventLoop 48 | ?? module.eventLoopGroup.next() 49 | 50 | loop.scheduleTask(in: .milliseconds(Int64(milliseconds))) { 51 | execute() 52 | module.release() 53 | } 54 | } 55 | 56 | } 57 | 58 | /// Enqueue the given closure for later dispatch in the Q. 59 | @inlinable 60 | public func nextTick(on eventLoop : EventLoop? = nil, 61 | _ execute : @escaping () -> Void) 62 | { 63 | MacroCore.shared.nextTick(on: eventLoop, execute) 64 | } 65 | 66 | /// Execute the given closure after the amount of milliseconds given. 67 | @inlinable 68 | public func setTimeout(on eventLoop : EventLoop? = nil, 69 | _ milliseconds : Int, 70 | _ execute : @escaping () -> Void) 71 | { 72 | MacroCore.shared.setTimeout(on: eventLoop, milliseconds, execute) 73 | } 74 | -------------------------------------------------------------------------------- /Sources/MacroCore/Process/CommandLine.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CommandLine.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | public extension process { // CommandLine 10 | 11 | @inlinable 12 | static var argv : [ String ] { return CommandLine.arguments } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/MacroCore/Process/DetectXcode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetectXcode.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | #if os(Windows) 10 | import func WinSDK.strstr 11 | #elseif os(Linux) 12 | import func Glibc.strstr 13 | #else 14 | import func Darwin.strstr 15 | #endif 16 | import let xsys.getenv 17 | 18 | public extension process { 19 | 20 | #if os(Linux) || os(Windows) 21 | static let isRunningInXCode = false 22 | #else 23 | static let isRunningInXCode : Bool = { 24 | // TBD: is there a better way? 25 | if let s = xsys.getenv("XPC_SERVICE_NAME") { // not in Xcode 16 anymore 26 | if strstr(s, "Xcode") != nil { return true } 27 | } 28 | if xsys.getenv("__XCODE_BUILT_PRODUCTS_DIR_PATHS") != nil { // Xcode 16 29 | return true 30 | } 31 | return false 32 | }() 33 | #endif 34 | } 35 | -------------------------------------------------------------------------------- /Sources/MacroCore/Process/Environment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Environment.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | import class Foundation.ProcessInfo 10 | 11 | public extension process { // Environment 12 | 13 | /** 14 | * Returns the environment variables of the process. 15 | * 16 | * This hooks into `Foundation.processInfo`, it doesn't maintain an own 17 | * cache. 18 | */ 19 | @inlinable 20 | static var env : [ String : String ] { 21 | return ProcessInfo.processInfo.environment 22 | } 23 | } 24 | 25 | // MARK: - Helpers 26 | 27 | public extension process { 28 | 29 | /** 30 | * Checks for an integer environment variable. 31 | * 32 | * - Parameter environmentVariableName: The name of the environment variable. 33 | * - Parameter defaultValue: Returned if the variable isn't set or can't be 34 | * parsed. 35 | * - Parameter lowerWarningBound: If the value set is below this optional 36 | * value, a warning will be logged to the 37 | * console. 38 | * - Parameter upperWarningBound: If the value set is above this optional 39 | * value, a warning will be logged to the 40 | * console. 41 | * - Returns: the integer value of the environment variable, or the 42 | * `defaultValue` if the environment variable wasn't set. 43 | */ 44 | @inlinable 45 | static func getenv(_ environmentVariableName : String, 46 | defaultValue : Int, 47 | lowerWarningBound : Int? = nil, 48 | upperWarningBound : Int? = nil) -> Int 49 | { 50 | if let s = process.env[environmentVariableName], !s.isEmpty { 51 | guard let value = Int(s), value > 0 else { 52 | console.error("invalid int value in env \(environmentVariableName):", s) 53 | return defaultValue 54 | } 55 | if let wv = lowerWarningBound, value < wv { 56 | console.warn("pretty small \(environmentVariableName) value:", value) 57 | } 58 | if let wv = upperWarningBound, value > wv { 59 | console.warn("pretty large \(environmentVariableName) value:", value) 60 | } 61 | return value 62 | } 63 | else { 64 | return defaultValue 65 | } 66 | } 67 | 68 | /** 69 | * Checks for an boolean environment variable. 70 | * 71 | * - Parameter environmentVariableName: The name of the environment variable. 72 | * - Returns: False if the variable is not set, or none of the predefined 73 | * strings: `1`, `true`, `YES` or `enabled`. 74 | */ 75 | @inlinable 76 | static func getenvflag(_ environmentVariableName: String) -> Bool { 77 | guard let s = process.env[environmentVariableName], !s.isEmpty else { 78 | return false 79 | } 80 | switch s.lowercased() { 81 | case "1", "true", "yes", "enabled" : return true 82 | case "0", "false", "no", "disabled" : return false 83 | default: 84 | console.warn("unexpected value for bool environment variable:", 85 | environmentVariableName) 86 | return false 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Sources/MacroCore/Process/Process.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Process.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2020-2023 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | import xsys 10 | import NIO 11 | 12 | #if os(Windows) 13 | import WinSDK 14 | #elseif os(Linux) 15 | import Glibc 16 | #else 17 | import Darwin 18 | // importing this from xsys doesn't seem to work 19 | import Foundation // this is for POSIXError : Error 20 | #endif 21 | 22 | public enum process {} 23 | 24 | public extension process { 25 | static let nextTick = MacroCore.nextTick 26 | } 27 | 28 | public extension process { // File System 29 | 30 | @inlinable 31 | static func chdir(path: String) throws { 32 | let rc = xsys.chdir(path) 33 | guard rc == 0 else { throw POSIXErrorCode(rawValue: xsys.errno)! } 34 | } 35 | 36 | @inlinable 37 | static func cwd() -> String { 38 | let pathMaybe = xsys.getcwd(nil /* malloc */, 0) 39 | assert(pathMaybe != nil, "process has no cwd??") 40 | guard let path = pathMaybe else { return "" } 41 | defer { free(path) } 42 | 43 | let s = String(validatingUTF8: path) 44 | assert(s != nil, "could not convert cwd to String?!") 45 | return s ?? "/tmp" 46 | } 47 | } 48 | 49 | public extension process { // Process Info 50 | 51 | #if os(Windows) 52 | static let platform = "win32" 53 | #elseif os(Linux) 54 | static let platform = "linux" 55 | #else 56 | static let platform = "darwin" 57 | #endif 58 | } 59 | 60 | #if !os(Windows) 61 | public extension process { // Process Info 62 | 63 | @inlinable 64 | static var pid : Int { return Int(getpid()) } 65 | 66 | static let getegid = xsys.getegid 67 | static let geteuid = xsys.geteuid 68 | static let getgid = xsys.getgid 69 | static let getuid = xsys.getuid 70 | // TODO: getgroups, initgroups, setegid, seteuid, setgid, setgroups, setuid 71 | 72 | // TODO: hrtime() 73 | // TODO: memoryUsage() 74 | // TODO: title { set get } 75 | // TODO: uptime 76 | 77 | // TODO: arch 78 | // TODO: release 79 | } 80 | #endif // !os(Windows) 81 | 82 | public extension process { // Run Control 83 | 84 | /** 85 | * The exit code to use if `exit` is called without an explicit code, 86 | * defaults to `0` (aka no error). 87 | * 88 | * This can be used to change the default to some error code, so all exits 89 | * will error out, unless a success code is used. For example: 90 | * 91 | * process.exitCode = 1 92 | * 93 | * guard process.argv.count > 1 else { process.exit() } // will fail 94 | * if answer == 42 { process.exit() } // will fail 95 | * 96 | * print("OK, all good.") 97 | * process.exit(0) // explict successful exit 98 | * 99 | */ 100 | static var exitCode : Int { 101 | set { MacroCore.shared.exitCode = newValue } 102 | get { return MacroCore.shared.exitCode } 103 | } 104 | 105 | /** 106 | * Terminate the process with the given process exit code. 107 | * 108 | * It no code is passed in, the current value of the `process.exitCode` 109 | * property is used (which itself defaults to 0). 110 | * 111 | * - Parameters: 112 | * - code: The optional exit code, defaults to `process.exitCode`. 113 | */ 114 | @inlinable 115 | static func exit(_ code: Int? = nil) -> Never { MacroCore.shared.exit(code) } 116 | 117 | /** 118 | * Terminate the process with the given exit code associated with the 119 | * given value. 120 | * 121 | * This can be used with enums like so: 122 | * 123 | * enum ExitCodes: Int { 124 | * case directoryMissing = 1 125 | * case outOfMemory = 2 126 | * } 127 | * 128 | * - Parameters: 129 | * - code: The optional exit code, defaults to `process.exitCode`. 130 | */ 131 | @inlinable 132 | static func exit(_ code: C) -> Never 133 | where C: RawRepresentable, C.RawValue == Int 134 | { 135 | exit(code.rawValue) 136 | } 137 | 138 | @inlinable 139 | @available(*, deprecated, message: "Avoid argument label, just `exit(10)`.") 140 | static func exit(code: Int?) { exit(code) } 141 | } 142 | 143 | #if !os(Windows) 144 | public extension process { // Run Control 145 | 146 | static let abort = xsys.abort 147 | 148 | @inlinable 149 | static func kill(_ pid: Int, _ signal: Int32 = xsys.SIGTERM) throws { 150 | let rc = xsys.kill(pid_t(pid), signal) 151 | guard rc == 0 else { throw POSIXErrorCode(rawValue: xsys.errno)! } 152 | } 153 | @inlinable 154 | static func kill(_ pid: Int, _ signal: String) throws { 155 | var sc : Int32 = xsys.SIGTERM 156 | switch signal.uppercased() { 157 | case "SIGTERM": sc = xsys.SIGTERM 158 | case "SIGHUP": sc = xsys.SIGHUP 159 | case "SIGINT": sc = xsys.SIGINT 160 | case "SIGQUIT": sc = xsys.SIGQUIT 161 | case "SIGKILL": sc = xsys.SIGKILL 162 | case "SIGSTOP": sc = xsys.SIGSTOP 163 | default: emitWarning("unsupported signal: \(signal)") 164 | } 165 | try kill(pid, sc) 166 | } 167 | } 168 | #endif // !os(Windows) 169 | -------------------------------------------------------------------------------- /Sources/MacroCore/Process/README.md: -------------------------------------------------------------------------------- 1 | # Macro `process` module 2 | 3 | A module modelled after the builtin Node 4 | [process module](https://nodejs.org/dist/latest-v7.x/docs/api/process.html). 5 | 6 | This is a not a standalone Swift module, but part of the Swift `MacroCore` module. 7 | 8 | The module provides access to: 9 | - cmdline arguments 10 | - environment 11 | - the "warning" facility 12 | - current directory 13 | - user / process IDs 14 | - platform ID 15 | - sending signals using `kill()` 16 | - exiting the process using `exit()` 17 | - `nextTick` 18 | -------------------------------------------------------------------------------- /Sources/MacroCore/Process/Warnings.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Warnings.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2020-2022 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | import struct NIOConcurrencyHelpers.NIOLock 10 | 11 | public extension process { // Warnings 12 | 13 | private static var _warningListeners = EventListenerSet() 14 | private static let _warningListenersLock = NIOConcurrencyHelpers.NIOLock() 15 | 16 | static func onWarning(execute: @escaping ( Warning ) -> Void) { 17 | _warningListenersLock.lock() 18 | _warningListeners.add(execute) 19 | _warningListenersLock.unlock() 20 | } 21 | 22 | struct Warning { 23 | public let name : String 24 | public let message : String 25 | public let error : Swift.Error? 26 | // us have nope stack: TODO: there was something by IBM 27 | 28 | @usableFromInline 29 | init(name: String, message: String? = nil, error: Error? = nil) { 30 | self.name = name 31 | self.error = error 32 | 33 | if let s = message { self.message = s } 34 | else if let e = error { self.message = "\(e)" } 35 | else { self.message = "Unknown Error" } 36 | } 37 | } 38 | 39 | static func emit(warning w: Warning) { 40 | console.log("(Macro: \(pid)): \(w.name): \(w.message)") 41 | _warningListenersLock.lock() 42 | var warningListeners = _warningListeners 43 | _warningListenersLock.unlock() 44 | warningListeners.emit(w) 45 | } 46 | 47 | @inlinable 48 | static func emitWarning(_ warning: String, name: String = "Warning") { 49 | emit(warning: Warning(name: name, message: warning)) 50 | } 51 | @inlinable 52 | static func emitWarning(_ warning: Error, name: String = "Warning") { 53 | emit(warning: Warning(name: name, error: warning)) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sources/MacroCore/Regex.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Regex.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2021 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | #if canImport(Foundation) 10 | import class Foundation.NSRegularExpression 11 | import class Foundation.NSString 12 | import struct Foundation.NSRange 13 | 14 | public extension String { 15 | 16 | /** 17 | * No Regex syntax in Swift. This is used to replicate those: 18 | * 19 | * arg.match(/^-/) 20 | * 21 | * TODO: `g` and `i` suffixes, don't know about `g`, but `i` is an option: 22 | * 23 | * arg.match("ain", .caseInsensitive) 24 | * 25 | * Note: Like JS this returns `nil` if no matches are found. 26 | */ 27 | @inlinable 28 | func match(_ pattern: String, 29 | options: NSRegularExpression.Options = []) 30 | -> [ String ]? 31 | { 32 | guard let regex = 33 | try? NSRegularExpression(pattern: pattern, options: options) 34 | else { 35 | assertionFailure("Could not parse regex: \(pattern)") 36 | return nil 37 | } 38 | let range = NSRange(self.startIndex.. String 64 | { 65 | guard let regex = 66 | try? NSRegularExpression(pattern: pattern, options: options) 67 | else { 68 | assertionFailure("Could not parse regex: \(pattern)") 69 | return self 70 | } 71 | let range = NSRange(self.startIndex.. [ String ] { 79 | guard !self.isEmpty else { return [ "" ] } // That's what Node does 80 | 81 | guard let regex = try? NSRegularExpression(pattern: pattern, options: []) 82 | else { 83 | assertionFailure("Could not parse regex: \(pattern)") 84 | return [ self ] 85 | } 86 | 87 | let range = NSRange(self.startIndex..= 0 && idx < nsSelf.length) 102 | assert(componentRange.length >= 0) 103 | assert(componentRange.location > 0) 104 | let component = nsSelf.substring(with: componentRange) 105 | if !omitEmpty || !component.isEmpty { components.append(component) } 106 | 107 | idx = splitterRange.upperBound 108 | } 109 | 110 | if idx + 1 < nsSelf.length { 111 | let component = nsSelf.substring(from: idx) 112 | if !omitEmpty || !component.isEmpty { components.append(component) } 113 | } 114 | 115 | return components 116 | } 117 | } 118 | #endif // canImport(Foundation) 119 | -------------------------------------------------------------------------------- /Sources/MacroCore/Streams/Concat.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Concat.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2020-2023 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | /** 10 | * Returns a stream in the spirit of the Node `concat-stream` module: 11 | * 12 | * https://github.com/maxogden/concat-stream 13 | * 14 | * Be careful w/ using that. You don't want to baloon payloads in memory! 15 | * 16 | * Usage: 17 | * ``` 18 | * request | concat { buffer in 19 | * console.log("The size of the request body is:", buffer.count) 20 | * } 21 | * ``` 22 | * 23 | * Pushes a ``ConcatError`` if the maximum size was exceeded. 24 | * 25 | * - Parameters: 26 | * - maximumSize: The maximum size of the request body. Can be configured 27 | * using the `macro.concat.maxsize` environment variable. 28 | * - yield: A closure called with the loaded data (a ``Buffer``). 29 | * - Returns: The ``ConcatByteStream``. 30 | */ 31 | @inlinable 32 | public func concat(maximumSize: Int = _defaultConcatMaximumSize, 33 | yield: @escaping ( Buffer ) -> Void) -> ConcatByteStream 34 | { 35 | let stream = ConcatByteStream(maximumSize: maximumSize) 36 | return stream.onceFinish { 37 | let result = stream.writableBuffer 38 | stream.writableBuffer = MacroCore.shared.emptyBuffer 39 | yield(result) 40 | } 41 | } 42 | 43 | /** 44 | * Returns a stream in the spirit of the Node `concat-stream` module: 45 | * 46 | * https://github.com/maxogden/concat-stream 47 | * 48 | * Be careful w/ using that. You don't want to baloon payloads in memory! 49 | * 50 | * Usage: 51 | * ``` 52 | * request | concat { buffer in 53 | * console.log("The size of the request body is:", buffer.count) 54 | * } 55 | * ``` 56 | * 57 | * Pushes a ``ConcatError`` if the maximum size was exceeded. 58 | * 59 | * - Parameters: 60 | * - maximumSize: The maximum size of the request body. Can be configured 61 | * using the `macro.concat.maxsize` environment variable. 62 | * - yield: A closure called with the loaded data (a ``Buffer``). 63 | * - Returns: The ``ConcatByteStream``. 64 | */ 65 | @inlinable 66 | public func concat(maximumSize: Int = _defaultConcatMaximumSize, 67 | yield: @escaping ( Buffer ) throws -> Void) 68 | -> ConcatByteStream 69 | { 70 | let stream = ConcatByteStream(maximumSize: maximumSize) 71 | return stream.onceFinish { 72 | defer { stream.writableBuffer = MacroCore.shared.emptyBuffer } 73 | do { 74 | try yield(stream.writableBuffer) 75 | } 76 | catch { 77 | stream.emit(error: error) 78 | } 79 | } 80 | } 81 | 82 | /** 83 | * A stream in the spirit of the Node `concat-stream` module: 84 | * 85 | * https://github.com/maxogden/concat-stream 86 | * 87 | * Be careful w/ using that. You don't want to baloon payloads in memory! 88 | * 89 | * Create those objects using the `concat` function. 90 | */ 91 | public class ConcatByteStream: WritableByteStream, 92 | WritableByteStreamType, WritableStreamType, 93 | CustomStringConvertible 94 | { 95 | // TBD: This should be duplex? Or is the duplex a "compacting stream"? 96 | 97 | enum StreamState: Equatable { 98 | case ready 99 | case finished 100 | } 101 | 102 | public var writableBuffer = MacroCore.shared.emptyBuffer 103 | public let maximumSize : Int 104 | private var state = StreamState.ready 105 | 106 | override public var writableFinished : Bool { return state == .finished } 107 | @inlinable 108 | override public var writableEnded : Bool { return writableFinished } 109 | @inlinable 110 | override public var writable : Bool { return !writableFinished } 111 | 112 | @inlinable 113 | open var writableLength : Int { return writableBuffer.count } 114 | 115 | @usableFromInline init(maximumSize: Int) { 116 | self.maximumSize = maximumSize 117 | } 118 | 119 | @discardableResult 120 | @inlinable 121 | public func write(_ bytes: Buffer, whenDone: @escaping () -> Void = {}) 122 | -> Bool 123 | { 124 | guard !writableEnded else { 125 | emit(error: WritableError.writableEnded) 126 | whenDone() 127 | return true 128 | } 129 | 130 | let newSize = writableBuffer.count + bytes.count 131 | guard newSize <= maximumSize else { 132 | emit(error: ConcatError 133 | .maximumSizeExceeded(maximumSize: maximumSize, 134 | availableSize: newSize)) 135 | whenDone() 136 | return true 137 | } 138 | 139 | writableBuffer.append(bytes) 140 | whenDone() 141 | return true 142 | } 143 | 144 | public func end() { 145 | state = .finished 146 | finishListeners.emit() 147 | finishListeners.removeAll() 148 | errorListeners .removeAll() 149 | } 150 | 151 | 152 | // MARK: - CustomStringConvertible 153 | 154 | open var description: String { 155 | let id = String(Int(bitPattern: ObjectIdentifier(self)), radix: 16) 156 | var ms = " 0 { 162 | ms += " corked=#\(count)" 163 | } 164 | else { 165 | ms += " corked(empty)" 166 | } 167 | } 168 | else { 169 | ms += " buffered=#\(count)" 170 | } 171 | 172 | if writableEnded { ms += " ended" } 173 | 174 | return ms 175 | } 176 | } 177 | 178 | /** 179 | * The ``concat`` middleware failed. 180 | */ 181 | public enum ConcatError: Swift.Error { 182 | /// The maximum allowed size was exceeded. 183 | case maximumSizeExceeded(maximumSize: Int, availableSize: Int) 184 | } 185 | 186 | /** 187 | * The default maximum request size for the ``concat`` middleware. 188 | * 189 | * Can be set using the `macro.concat.maxsize` environment variable (in bytes), 190 | * and defaults to 1MB. 191 | */ 192 | public let _defaultConcatMaximumSize = 193 | process.getenv("macro.concat.maxsize", 194 | defaultValue : 1024 * 1024, // 1MB 195 | lowerWarningBound : 128) 196 | -------------------------------------------------------------------------------- /Sources/MacroCore/Streams/DuplexStreamType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DuplexStreamType.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | public protocol DuplexStreamType: ReadableStreamType, 10 | WritableStreamType 11 | { 12 | } 13 | 14 | public protocol DuplexByteStreamType: ReadableByteStreamType, 15 | WritableByteStreamType 16 | { 17 | } 18 | -------------------------------------------------------------------------------- /Sources/MacroCore/Streams/README.md: -------------------------------------------------------------------------------- 1 | # Macro Streams 2 | 3 | Preparations for [Noze.io](http://noze.io) like type-safe, asynchronous streams. 4 | Which in turn are modelled after Node v3 streams, but in type-safe. 5 | Node streams are either "object streams" or "byte streams". In Swift we can do both at 6 | the same type (w/ byte streams being `Buffer` object streams). 7 | 8 | *Work in Progress*: Do not consider this part API stable yet. 9 | 10 | Streams are readable, or writable, or both. 11 | 12 | The core stream protocols: `ReadableStreamType` and `WritableStreamType` are 13 | generic over the items they yield or can write. Because the protocols are generic, 14 | they can't be used as types (i.e. Swift doesn't support `ReadableStreamType` 15 | yet). 16 | 17 | For the common byte based streams, there are concrete `Buffer` based protocols: 18 | `ReadableByteStreamType` and `WritableByteStreamType`. 19 | 20 | Finally, there are base classes which provide partial implementations for both: 21 | `ReadableStreamBase`, `ReadableByteStream` and the `Buffer` counter part. 22 | -------------------------------------------------------------------------------- /Sources/MacroCore/Streams/ReadableByteStream.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ReadableByteStream.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | /** 10 | * A `Buffer` based stream. This buffers the data until it is read. 11 | * 12 | * Hierarchy: 13 | * 14 | * - ErrorEmitter 15 | * - ReadableStreamBase 16 | * * ReadableByteStream 17 | * - IncomingMessage 18 | */ 19 | open class ReadableByteStream: ReadableStreamBase, 20 | ReadableStreamType, 21 | ReadableByteStreamType 22 | { 23 | 24 | public var readableBuffer : Buffer? 25 | 26 | @inlinable 27 | override open var readableLength : Int { return readableBuffer?.count ?? 0 } 28 | 29 | /// This is used to coalesce multiple reads. Do not trigger readable, if we 30 | /// sent readable already, but the client didn't call `read()` yet. 31 | @usableFromInline internal var readPending = false 32 | 33 | open func push(_ bytes: Buffer?) { 34 | guard !readableEnded else { 35 | assert(!readableEnded, "trying to push to a readable which ended!") 36 | emit(error: ReadableError.readableEnded) 37 | return 38 | } 39 | 40 | guard let bytes = bytes else { // nil push means EOF (aka finish) 41 | // TBD: This logic needs to be brought in line w/ Noze.io. That 42 | // we pushed EOF, doesn't mean there is nothing left to read. 43 | readableEnded = true // TBD 44 | if readableBuffer?.isEmpty ?? true { 45 | endListeners.emit() 46 | endListeners.removeAll() 47 | } 48 | return 49 | } 50 | 51 | guard bytes.count > 0 else { return } 52 | 53 | // when in reading mode, `data` is only emitted when `read` is called 54 | if readableListeners.isEmpty && !dataListeners.isEmpty { 55 | dataListeners.emit(bytes) 56 | return // pure 'data' mode, do not buffer 57 | } 58 | 59 | if readableBuffer == nil { readableBuffer = bytes } 60 | else { readableBuffer?.append(bytes) } 61 | 62 | if !readPending && !readableListeners.isEmpty { 63 | readPending = true 64 | readableListeners.emit() 65 | } 66 | } 67 | 68 | public func read(_ count: Int? = nil) -> Buffer { 69 | readPending = false 70 | guard let buffer = readableBuffer else { return core.emptyBuffer } 71 | 72 | let readBuffer : Buffer 73 | if let count = count, count < buffer.count { 74 | guard count > 0 else { return core.emptyBuffer } 75 | readBuffer = self.readableBuffer!.consumeFirst(count) 76 | } 77 | else { 78 | readBuffer = buffer 79 | self.readableBuffer = nil 80 | } 81 | dataListeners.emit(readBuffer) 82 | return readBuffer 83 | } 84 | 85 | 86 | @usableFromInline 87 | internal func _emitDataIfAppropriate(execute: ( Buffer ) -> Void) -> Bool 88 | { 89 | guard let buffer = self.readableBuffer, !buffer.isEmpty else { 90 | return false 91 | } 92 | if readableListeners.isEmpty { 93 | self.readableBuffer = nil 94 | execute(buffer) 95 | return true 96 | } 97 | else if !readPending { 98 | execute(buffer) 99 | return true 100 | } 101 | // else: will be triggered on next read 102 | return false 103 | } 104 | 105 | @discardableResult 106 | @inlinable 107 | open func onceData(execute: @escaping ( Buffer ) -> Void) -> Self { 108 | if !_emitDataIfAppropriate(execute: execute) { 109 | dataListeners.once(execute) 110 | readableFlowing = true 111 | } 112 | return self 113 | } 114 | 115 | @discardableResult 116 | @inlinable 117 | open func onData(execute: @escaping ( Buffer ) -> Void) -> Self { 118 | dataListeners.add(execute) 119 | readableFlowing = true 120 | _ = _emitDataIfAppropriate(execute: execute) 121 | return self 122 | } 123 | 124 | open func _clearListeners() { 125 | dataListeners .removeAll() 126 | readableListeners.removeAll() 127 | errorListeners .removeAll() 128 | endListeners .removeAll() 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /Sources/MacroCore/Streams/ReadableByteStreamType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ReadableByteStreamType.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | /** 10 | * A non-generic materialized variant of `ReadableStreamType` 11 | */ 12 | public protocol ReadableByteStreamType: ErrorEmitterType { 13 | 14 | var readableHighWaterMark : Int { get } 15 | var readableLength : Int { get } 16 | var readableEnded : Bool { get } 17 | var readableFlowing : Bool { get } 18 | 19 | func push(_ bytes: Buffer?) 20 | 21 | func read(_ count: Int?) -> Buffer 22 | func read() -> Buffer 23 | 24 | // MARK: - Events 25 | 26 | @discardableResult 27 | func onceEnd(execute: @escaping () -> Void) -> Self 28 | 29 | @discardableResult 30 | func onEnd(execute: @escaping () -> Void) -> Self 31 | 32 | @discardableResult 33 | func onceReadable(execute: @escaping () -> Void) -> Self 34 | 35 | @discardableResult 36 | func onReadable(execute: @escaping () -> Void) -> Self 37 | 38 | @discardableResult 39 | func onceData(execute: @escaping ( Buffer ) -> Void) -> Self 40 | 41 | @discardableResult 42 | func onData(execute: @escaping ( Buffer ) -> Void) -> Self 43 | } 44 | -------------------------------------------------------------------------------- /Sources/MacroCore/Streams/ReadableStreamBase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ReadableStreamBase.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2020-2023 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | /** 10 | * Superclass for readable streams. 11 | * 12 | * Note: This does not conform to `ReadableStreamType` yet, because this base 13 | * class does not implement the buffer in a generic way. 14 | * 15 | * If a byte stream is being implemented, consider using the 16 | * `ReadableByteStream` base class. 17 | * 18 | * ## Subclassing 19 | * 20 | * - `readableLength` MUST be overridden 21 | * - the subclass should to conform to `ReadableStreamType` 22 | * - the `onData` setup functions need to be implemented 23 | * 24 | * Hierarchy: 25 | * 26 | * - ErrorEmitter 27 | * * ReadableStreamBase 28 | * - ReadableByteStream 29 | * - IncomingMessage 30 | */ 31 | open class ReadableStreamBase: ErrorEmitter { 32 | 33 | open var readableHighWaterMark = 16 * 1024 34 | public var readableEnded = false 35 | open var readableFlowing = false 36 | 37 | #if DEBUG && false // cycle debugging 38 | public override init() { 39 | super.init() 40 | let id = String(Int(bitPattern: ObjectIdentifier(self)), radix: 16) 41 | print("INIT:0x\(id) \(type(of:self))") 42 | } 43 | deinit { 44 | let id = String(Int(bitPattern: ObjectIdentifier(self)), radix: 16) 45 | print("DEINIT:0x\(id) \(type(of:self))") 46 | } 47 | #else 48 | public override init() { 49 | super.init() 50 | } 51 | #endif 52 | 53 | @inlinable 54 | open var readableLength : Int { 55 | assertionFailure("subclass responsibility: \(#function)") 56 | return 0 57 | } 58 | 59 | public var dataListeners = EventListenerSet() 60 | public var endListeners = EventListenerSet() 61 | public var readableListeners = EventListenerSet() 62 | 63 | @discardableResult @inlinable 64 | open func onceEnd(execute: @escaping () -> Void) -> Self { 65 | if readableEnded { 66 | nextTick(execute) 67 | return self 68 | } 69 | else { 70 | endListeners.once(execute); return self 71 | } 72 | } 73 | 74 | /// `onEnd` is the same like `onceEnd` 75 | @discardableResult @inlinable 76 | open func onEnd(execute: @escaping () -> Void) -> Self { 77 | return onceEnd(execute: execute) 78 | } 79 | 80 | @discardableResult @inlinable 81 | open func onceReadable(execute: @escaping () -> Void) -> Self { 82 | if readableLength > 0 { execute() } 83 | else { 84 | readableListeners.once(execute) 85 | readableFlowing = true 86 | } 87 | return self 88 | } 89 | 90 | @discardableResult 91 | @inlinable 92 | open func onReadable(execute: @escaping () -> Void) -> Self { 93 | readableListeners.add(execute) 94 | if readableLength > 0 { execute() } 95 | readableFlowing = true 96 | return self 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Sources/MacroCore/Streams/ReadableStreamType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ReadableStreamType.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | public protocol ReadableStreamType: ErrorEmitterType { 10 | 11 | associatedtype ReadablePayload 12 | 13 | var readableHighWaterMark : Int { get } 14 | var readableLength : Int { get } 15 | var readableEnded : Bool { get } 16 | var readableFlowing : Bool { get } 17 | 18 | func push(_ bytes: ReadablePayload?) 19 | 20 | func read(_ count: Int?) -> ReadablePayload 21 | func read() -> ReadablePayload 22 | 23 | // MARK: - Events 24 | 25 | @discardableResult 26 | func onceEnd(execute: @escaping () -> Void) -> Self 27 | 28 | @discardableResult 29 | func onEnd(execute: @escaping () -> Void) -> Self 30 | 31 | @discardableResult 32 | func onceReadable(execute: @escaping () -> Void) -> Self 33 | 34 | @discardableResult 35 | func onReadable(execute: @escaping () -> Void) -> Self 36 | 37 | @discardableResult 38 | func onceData(execute: @escaping ( ReadablePayload ) -> Void) -> Self 39 | 40 | @discardableResult 41 | func onData(execute: @escaping ( ReadablePayload ) -> Void) -> Self 42 | } 43 | 44 | public enum ReadableError: Swift.Error { 45 | case readableEnded 46 | } 47 | 48 | // MARK: - Default Implementations 49 | 50 | public extension ReadableStreamType { 51 | func read() -> ReadablePayload { return read(nil) } 52 | } 53 | -------------------------------------------------------------------------------- /Sources/MacroCore/Streams/WritableByteStream.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WritableByteStream.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | /** 10 | * A `Buffer` based stream. 11 | * 12 | * Note: `WritableStreamType` and `WritableByteStreamType` are not implemented 13 | * here. It is just a base class. 14 | * 15 | * Hierarchy: 16 | * 17 | * WritableStreamBase 18 | * WritableByteStreamBase 19 | */ 20 | open class WritableByteStream: WritableStreamBase { 21 | 22 | public typealias WritablePayload = Buffer 23 | 24 | open func _clearListeners() { 25 | finishListeners.removeAll() 26 | drainListeners .removeAll() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/MacroCore/Streams/WritableByteStreamType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WritableByteStreamType.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | /** 10 | * A non-generic materialized variant of `WritableStreamType` 11 | */ 12 | public protocol WritableByteStreamType: ErrorEmitterType { 13 | 14 | var writableEnded : Bool { get } 15 | 16 | // TBD: Pass `Result` to done callback? 17 | // Yes, and provide those w/o args as wrappers. 18 | 19 | @discardableResult 20 | func write(_ bytes : Buffer, whenDone : @escaping () -> Void) -> Bool 21 | @discardableResult 22 | func write(_ string : String, whenDone : @escaping () -> Void) -> Bool 23 | 24 | // MARK: - Events 25 | // TODO: drain/close 26 | 27 | @discardableResult 28 | func onceFinish(execute: @escaping () -> Void) -> Self 29 | 30 | /// `onFinish` is the same like `onceFinish` (only ever finishes once) 31 | @discardableResult 32 | func onFinish(execute: @escaping () -> Void) -> Self 33 | } 34 | 35 | 36 | public extension WritableStreamType where WritablePayload == Buffer { 37 | 38 | @inlinable 39 | @discardableResult 40 | func write(_ string: String, whenDone: @escaping () -> Void = {}) -> Bool { 41 | return write(Buffer(string), whenDone: whenDone) 42 | } 43 | 44 | @inlinable 45 | func end(_ string: String, _ encoding: String.Encoding = .utf8) { 46 | do { 47 | write(try Buffer.from(string, encoding)) { self.end() } 48 | } 49 | catch { 50 | emit(error: error) 51 | } 52 | } 53 | @inlinable 54 | func end(_ string: String, _ encoding: String) { 55 | do { 56 | write(try Buffer.from(string, encoding)) { self.end() } 57 | } 58 | catch { 59 | emit(error: error) 60 | } 61 | } 62 | } 63 | 64 | import struct Foundation.Data 65 | 66 | public extension WritableStreamType where WritablePayload == Buffer { 67 | 68 | @inlinable 69 | @discardableResult 70 | func write(_ data: Data, whenDone: @escaping () -> Void = {}) -> Bool { 71 | return write(Buffer(data), whenDone: whenDone) 72 | } 73 | 74 | @inlinable 75 | func end(_ data: Data) { 76 | write(Buffer(data)) { self.end() } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Sources/MacroCore/Streams/WritableStreamBase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WritableStreamBase.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2020-2023 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | /** 10 | * Superclass for writable streams. 11 | * 12 | * Note: This does not conform to `WritableStreamType` yet, because this base 13 | * class does not implement the buffer in a generic way. 14 | * 15 | * If a byte stream is being implemented, consider using the 16 | * `WritableByteStream` base class. 17 | * 18 | * ## Subclassing 19 | * 20 | * - the subclass should to conform to `WritableStreamType` 21 | */ 22 | open class WritableStreamBase: ErrorEmitter { 23 | 24 | open var writableHighWaterMark = 4096 25 | public var finishListeners = EventListenerSet() 26 | public var drainListeners = EventListenerSet() 27 | 28 | open var writableEnded : Bool { 29 | get { 30 | fatalError("subclass responsibility \(#function)") 31 | } 32 | } 33 | open var writableFinished : Bool { 34 | get { 35 | fatalError("subclass responsibility \(#function)") 36 | } 37 | } 38 | open var writableCorked : Bool { return false } 39 | open var writable : Bool { return !writableFinished } 40 | 41 | // MARK: - Init 42 | 43 | #if DEBUG && false // cycle debugging 44 | public override init() { 45 | super.init() 46 | let id = String(Int(bitPattern: ObjectIdentifier(self)), radix: 16) 47 | print("INIT:0x\(id) \(type(of:self))") 48 | } 49 | deinit { 50 | let id = String(Int(bitPattern: ObjectIdentifier(self)), radix: 16) 51 | print("DEINIT:0x\(id) \(type(of:self))") 52 | } 53 | #endif 54 | 55 | 56 | // MARK: - Listeners 57 | 58 | @discardableResult 59 | open func onceFinish(execute: @escaping () -> Void) -> Self { 60 | finishListeners.once(immediate: writableEnded, execute) 61 | return self 62 | } 63 | @discardableResult 64 | open func onFinish(execute: @escaping () -> Void) -> Self { 65 | onceFinish(execute: execute) 66 | return self 67 | } 68 | 69 | @discardableResult 70 | open func onceDrain(execute: @escaping () -> Void) -> Self { 71 | // TBD: does drain need an immediate mode if the buffer is empty? 72 | drainListeners.once(execute) 73 | return self 74 | } 75 | @discardableResult 76 | open func onDrain(execute: @escaping () -> Void) -> Self { 77 | drainListeners.add(execute) 78 | return self 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Sources/MacroCore/Streams/WritableStreamType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WritableStreamType.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | public protocol WritableStreamType: ErrorEmitterType, ErrorEmitterTarget { 10 | 11 | associatedtype WritablePayload 12 | 13 | var writableHighWaterMark : Int { get } 14 | var writableEnded : Bool { get } 15 | 16 | @discardableResult 17 | func write(_ payload: WritablePayload, whenDone : @escaping () -> Void) 18 | -> Bool 19 | 20 | func end() 21 | 22 | // MARK: - Events 23 | // TODO: drain/close 24 | 25 | @discardableResult 26 | func onceFinish(execute: @escaping () -> Void) -> Self 27 | 28 | /// `onFinish` is the same like `onceFinish` (only ever finishes once) 29 | @discardableResult 30 | func onFinish(execute: @escaping () -> Void) -> Self 31 | } 32 | 33 | public enum WritableError: Swift.Error { 34 | case writableEnded 35 | } 36 | 37 | // MARK: - Default Implementations 38 | 39 | public extension WritableStreamType { 40 | 41 | /// `onFinish` is the same like `onceFinish` (only ever finishes once) 42 | @discardableResult 43 | @inlinable 44 | func onFinish(execute: @escaping () -> Void) -> Self { 45 | return onceFinish(execute: execute) 46 | } 47 | } 48 | 49 | public extension WritableStreamType { 50 | 51 | @inlinable 52 | func end(_ payload: WritablePayload) { 53 | write(payload) { self.end() } 54 | } 55 | } 56 | 57 | // MARK: - Deprecated Properties 58 | 59 | public extension WritableStreamType { 60 | 61 | @available(*, deprecated, message: "please use `writableEnded`") 62 | var finished : Bool { return writableEnded } 63 | } 64 | -------------------------------------------------------------------------------- /Sources/MacroCore/StringEncoding.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StringEncoding.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2020-2023 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | #if canImport(Foundation) 10 | import Foundation 11 | 12 | @usableFromInline 13 | internal let stringEncodingNames : Set = [ 14 | "utf8", "utf-8", 15 | "ascii", 16 | "iso2022jp", 17 | "isolatin1", "latin1", "iso-8859-1", 18 | "isolatin2", "latin2", "iso-8859-2", 19 | "japaneseeuc", 20 | "macosroman", "macos", 21 | "nextstep", 22 | "nonlossyascii", 23 | "shiftjis", 24 | "symbol", 25 | "unicode", 26 | "utf-16", "utf16", 27 | "utf-32", "utf32" 28 | // TODO: add the rest 29 | ] 30 | 31 | public extension String.Encoding { 32 | 33 | @inlinable 34 | static func isEncoding(_ name: String) -> Bool { 35 | return stringEncodingNames.contains(name) 36 | } 37 | 38 | @inlinable 39 | static func encodingWithName(_ name : String, 40 | fallbackEncoding : String.Encoding = .utf8) 41 | -> String.Encoding 42 | { 43 | let lc = name.lowercased() 44 | switch lc { 45 | case "utf8", "utf-8" : return .utf8 46 | case "ascii" : return .ascii 47 | case "iso2022jp" : return .iso2022JP 48 | case "isolatin1", "latin1", "iso-8859-1" : return .isoLatin1 49 | case "isolatin2", "latin2", "iso-8859-2" : return .isoLatin2 50 | case "japaneseeuc" : return .japaneseEUC 51 | case "macosroman", "macos" : return .macOSRoman 52 | case "nextstep" : return .nextstep 53 | case "nonlossyascii" : return .nonLossyASCII 54 | case "shiftjis" : return .shiftJIS 55 | case "symbol" : return .symbol 56 | case "unicode" : return .unicode 57 | case "utf-16", "utf16" : return .utf16 58 | case "utf-32", "utf32" : return .utf32 59 | // TODO: add the rest 60 | default: 61 | process.emitWarning("Unexpected String encoding: '\(name)'", 62 | name: #function) 63 | return fallbackEncoding 64 | } 65 | } 66 | } 67 | 68 | public enum CharsetConversionError: Swift.Error { 69 | case failedToConverData (encoding: String.Encoding) 70 | case failedToConvertString(encoding: String.Encoding) 71 | } 72 | #endif // canImport(Foundation) 73 | -------------------------------------------------------------------------------- /Sources/MacroTestUtilities/TestServerResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestServerResponse.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2020-2023 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | import struct Logging.Logger 10 | import struct NIOHTTP1.HTTPHeaders 11 | import enum NIOHTTP1.HTTPResponseStatus 12 | import struct NIOHTTP1.HTTPResponseHead 13 | import enum MacroCore.WritableError 14 | import struct MacroCore.Buffer 15 | import class http.ServerResponse 16 | 17 | public let MacroTestLogger = Logger(label: "μ.tests") 18 | 19 | /** 20 | * A ServerResponse for testing purposes. Doesn't write to an actual Channel. 21 | */ 22 | open class TestServerResponse: ServerResponse { 23 | 24 | public var continueCount = 0 25 | public var writtenContent = Buffer() 26 | public var errorToTrigger : Swift.Error? 27 | 28 | public init() { 29 | super.init(unsafeChannel: nil, log: MacroTestLogger) 30 | } 31 | 32 | 33 | // MARK: - Emit Header 34 | 35 | @usableFromInline 36 | internal func primaryWriteHead(_ part: HTTPResponseHead) { 37 | assert(!headersSent) 38 | guard !headersSent else { return } 39 | headersSent = true 40 | } 41 | @usableFromInline 42 | internal func primaryWriteHead() { 43 | let head = HTTPResponseHead(version: version, 44 | status: status, headers: headers) 45 | primaryWriteHead(head) 46 | } 47 | 48 | @inlinable 49 | override open func writeHead(_ status : HTTPResponseStatus = .ok, 50 | headers : HTTPHeaders = [:]) 51 | { 52 | if !headers.isEmpty { 53 | for ( name, value ) in headers { 54 | setHeader(name, value) 55 | } 56 | } 57 | let head = HTTPResponseHead(version: version, 58 | status: status, headers: headers) 59 | primaryWriteHead(head) 60 | } 61 | 62 | 63 | // MARK: - End 64 | 65 | override open func end() { 66 | guard !writableEnded else { return } 67 | if !headersSent { primaryWriteHead() } 68 | 69 | state = .finished 70 | finishListeners.emit() 71 | _clearListenersOnFinish() 72 | } 73 | private func _clearListenersOnFinish() { 74 | finishListeners.removeAll() 75 | errorListeners .removeAll() 76 | } 77 | 78 | 79 | @inlinable 80 | override open func writeHead(_ statusCode : Int, 81 | _ statusMessage : String?, 82 | _ headers : [ String : Any ] = [ : ]) 83 | { 84 | assert(!headersSent) 85 | guard !headersSent else { return } 86 | 87 | self.statusCode = statusCode 88 | if let s = statusMessage { self.statusMessage = s } 89 | 90 | // merge in headers 91 | for ( key, value ) in headers { 92 | setHeader(key, value) 93 | } 94 | 95 | primaryWriteHead() 96 | } 97 | 98 | 99 | // MARK: - 100-continue 100 | 101 | override open func writeContinue() { 102 | guard !writableEnded else { 103 | handleError(WritableError.writableEnded) 104 | return 105 | } 106 | 107 | continueCount += 1 108 | } 109 | 110 | 111 | // MARK: - WritableByteStream 112 | 113 | private func consumeErrorToTrigger() -> Swift.Error? { 114 | guard let error = errorToTrigger else { return nil } 115 | errorToTrigger = nil 116 | return error 117 | } 118 | 119 | @discardableResult 120 | override 121 | open func write(_ bytes: Buffer, whenDone: @escaping ( Error? ) -> Void) 122 | -> Bool 123 | { 124 | guard !writableEnded else { 125 | handleError(WritableError.writableEnded) 126 | return true 127 | } 128 | 129 | if !headersSent { primaryWriteHead() } 130 | 131 | if let error = consumeErrorToTrigger() { 132 | handleError(error) 133 | whenDone(error) 134 | } 135 | else { 136 | writtenContent.append(bytes) 137 | whenDone(nil) 138 | } 139 | return true 140 | } 141 | 142 | @discardableResult 143 | override open func write(_ bytes: Buffer, 144 | whenDone: @escaping () -> Void = {}) -> Bool 145 | { 146 | return write(bytes) { _ in whenDone() } 147 | } 148 | @discardableResult @inlinable override 149 | open func write(_ string: String, whenDone: @escaping () -> Void = {}) -> Bool 150 | { 151 | return write(Buffer(string), whenDone: whenDone) 152 | } 153 | 154 | 155 | // MARK: - CustomStringConvertible 156 | 157 | override open var description: String { 158 | let id = String(Int(bitPattern: ObjectIdentifier(self)), radix: 16) 159 | var ms = " Void) 18 | { 19 | FileSystemModule._evalAsync(readdirSync, path, yield) 20 | } 21 | 22 | // TBD: should that be a stream? Maybe, but it may not be worth it 23 | public func readdirSync(_ path: String) throws -> [ String ] { 24 | guard let dir = xsys.opendir(path) else { 25 | throw POSIXErrorCode(rawValue: xsys.errno)! 26 | } 27 | defer { _ = xsys.closedir(dir) } 28 | 29 | var entries = [ String ]() 30 | repeat { 31 | xsys.errno = 0 32 | guard let entry = xsys.readdir(dir) else { 33 | if xsys.errno == 0 { 34 | break 35 | } 36 | // On Linux, only EBADF is documented. macOS lists EFAULT, which 37 | // is equally implausible. But it also mentions EIO, which might 38 | // just be possible enough to consider it. 39 | throw POSIXErrorCode(rawValue: xsys.errno)! 40 | } 41 | 42 | var s : String? = nil 43 | if entry.pointee.d_name.0 == 46 /* . */ { 44 | guard entry.pointee.d_name.1 != 0 else { continue } 45 | if entry.pointee.d_name.1 == 46 /* .. */ { 46 | guard entry.pointee.d_name.2 != 0 else { continue } 47 | } 48 | } 49 | 50 | 51 | //&entry.pointee.d_name 52 | 53 | withUnsafePointer(to: &entry.pointee.d_name) { p in 54 | // TBD: Cast ptr to (CChar,CChar) tuple to an UnsafePointer. 55 | // Is this the right way to do it? No idea. 56 | // Rather do withMemoryRebound? But what about the capacity? 57 | let rp = UnsafeRawPointer(p) 58 | let crp = rp.assumingMemoryBound(to: CChar.self) 59 | s = String(cString: crp) // TBD: rather validatingUTF8? 60 | } 61 | 62 | if let s = s { 63 | entries.append(s) 64 | } 65 | else { 66 | assert(false, "could not decode directory name") 67 | } 68 | } while true 69 | 70 | return entries 71 | } 72 | 73 | -------------------------------------------------------------------------------- /Sources/fs/Path.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Path.swift 3 | // Noze.io / Macro 4 | // 5 | // Created by Helge Heß on 6/8/16. 6 | // Copyright © 2016-2021 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | #if os(Windows) 10 | import WinSDK 11 | #elseif os(Linux) 12 | import Glibc 13 | #else 14 | import Darwin 15 | #endif 16 | 17 | #if canImport(Foundation) 18 | import struct Foundation.URL 19 | import class Foundation.FileManager 20 | #endif 21 | 22 | public enum PathModule {} 23 | public typealias path = PathModule 24 | 25 | public extension PathModule { 26 | 27 | @inlinable 28 | static func basename(_ path: String) -> String { 29 | // TODO: this doesn't deal proper with trailing slashes 30 | return path.withCString { cs in 31 | let sp = rindex(cs, 47 /* / */) 32 | guard sp != nil else { return path } 33 | let bn = sp! + 1 34 | return String(cString: bn) 35 | } 36 | } 37 | 38 | @inlinable 39 | static func dirname(_ path: String) -> String { 40 | // TODO: this doesn't deal proper with trailing slashes 41 | return path.withCString { cs in 42 | let sp = UnsafePointer(rindex(cs, 47 /* / */)) 43 | guard sp != nil else { return path } 44 | let len = sp! - cs 45 | return String.fromCString(cs, length: len)! 46 | } 47 | } 48 | 49 | @inlinable 50 | static func join(_ components: String...) -> String { 51 | guard !components.isEmpty else { return "" } 52 | if components.count == 1 { return components[0] } 53 | 54 | #if canImport(Foundation) 55 | var base = URL(fileURLWithPath: components[0]) 56 | for component in components.dropFirst() { 57 | guard !component.isEmpty else { continue } 58 | base.appendPathComponent(component) 59 | } 60 | return base.path 61 | #else 62 | #if os(Windows) 63 | return components.joined(separator: "\\") 64 | #else 65 | return components.joined(separator: "/") 66 | #endif 67 | #endif 68 | } 69 | 70 | #if canImport(Foundation) 71 | // https://nodejs.org/api/path.html#path_path_resolve_paths 72 | @inlinable 73 | static func resolve(_ paths: String...) -> String { 74 | guard !paths.isEmpty else { return "" } 75 | 76 | func buildPath(_ pathURL: URL, with components: [ String ]) -> String { 77 | var pathURL = pathURL 78 | components.forEach { pathURL.appendPathComponent($0) } 79 | var path = pathURL.path 80 | while path.hasSuffix("/") { path.removeLast() } 81 | return path 82 | } 83 | 84 | var components = [ String ]() 85 | components.reserveCapacity(paths.count) 86 | for path in paths.reversed() { 87 | guard !path.isEmpty else { continue } 88 | let pathURL = URL(fileURLWithPath: path).standardizedFileURL 89 | if pathURL.path.hasPrefix("/") { // found absolute URL 90 | return buildPath(pathURL, with: components) 91 | } 92 | else { 93 | components.append(path) 94 | } 95 | } 96 | let cwd = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) 97 | return buildPath(cwd, with: components) 98 | } 99 | #endif 100 | } 101 | 102 | 103 | // MARK: - CString 104 | 105 | extension String { 106 | 107 | // FIXME: This is probably not necessary anymore? 108 | @usableFromInline 109 | static func fromCString(_ cs: UnsafePointer, length olength: Int?) 110 | -> String? 111 | { 112 | guard let length = olength else { // no length given, use \0 std imp 113 | return String(validatingUTF8: cs) 114 | } 115 | 116 | let buflen = length + 1 117 | let buf = UnsafeMutablePointer.allocate(capacity: buflen) 118 | memcpy(buf, cs, length) 119 | buf[length] = 0 // zero terminate 120 | 121 | let s = String(validatingUTF8: buf) 122 | buf.deallocate() 123 | return s 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /Sources/fs/PosixWrappers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PosixWrappers.swift 3 | // Noze.io / Macro 4 | // 5 | // Created by Helge Heß on 5/8/16. 6 | // Copyright © 2016-2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | import xsys 10 | #if os(Linux) || os(Windows) 11 | #else 12 | import Foundation // for POSIXError 13 | #endif 14 | 15 | public let F_OK = Int(xsys.F_OK) 16 | public let R_OK = Int(xsys.R_OK) 17 | public let W_OK = Int(xsys.W_OK) 18 | public let X_OK = Int(xsys.X_OK) 19 | 20 | 21 | // MARK: - Async functions, Unix functions are dispatched to a different Q 22 | 23 | /// Check whether we have access to the given path in the given mode. 24 | @inlinable 25 | public func access(_ path: String, _ mode: Int = F_OK, 26 | yield: @escaping ( Error? ) -> Void) { 27 | FileSystemModule._evalAsync(accessSync, (path, mode), yield) 28 | } 29 | 30 | @inlinable 31 | public func stat(_ path: String, 32 | yield: @escaping ( Error?, xsys.stat_struct? ) -> Void) 33 | { 34 | FileSystemModule._evalAsync(statSync, path, yield) 35 | } 36 | @inlinable 37 | public func lstat(_ path: String, 38 | yield: @escaping ( Error?, xsys.stat_struct? ) -> Void) 39 | { 40 | FileSystemModule._evalAsync(lstatSync, path, yield) 41 | } 42 | 43 | 44 | // MARK: - Synchronous wrappers 45 | 46 | // If you do a lot of FS operations in sequence, you might want to use a single 47 | // (async) GCD call, instead of using the convenience async functions. 48 | // 49 | // Example: 50 | // FileSystemModule.workerQueue.async { 51 | // statSync(...) 52 | // accessSync(...) 53 | // readdirSync(..) 54 | // dispatch(MacroCore.module.Q) { cb() } // or EventLoop! 55 | // } 56 | 57 | @inlinable 58 | public func accessSync(_ path: String, mode: Int = F_OK) throws { 59 | let rc = xsys.access(path, Int32(mode)) 60 | if rc != 0 { throw POSIXErrorCode(rawValue: xsys.errno)! } 61 | } 62 | 63 | @inlinable 64 | public func statSync(_ path: String) throws -> xsys.stat_struct { 65 | var info = xsys.stat_struct() 66 | let rc = xsys.stat(path, &info) 67 | if rc != 0 { throw POSIXErrorCode(rawValue: xsys.errno)! } 68 | return info 69 | } 70 | @inlinable 71 | public func lstatSync(_ path: String) throws -> xsys.stat_struct { 72 | var info = xsys.stat_struct() 73 | let rc = xsys.lstat(path, &info) 74 | if rc != 0 { throw POSIXErrorCode(rawValue: xsys.errno)! } 75 | return info 76 | } 77 | -------------------------------------------------------------------------------- /Sources/fs/README.md: -------------------------------------------------------------------------------- 1 |

Macro `fs` (FileSystem), `jsonfile`, `path` Modules 2 | 4 |

5 | 6 | A module modelled after: 7 | - the builtin Node [fs module](https://nodejs.org/dist/latest-v7.x/docs/api/fs.html) 8 | - the builtin Node [path module](https://nodejs.org/dist/latest-v7.x/docs/api/path.html) 9 | - the [jsonfile](https://www.npmjs.com/package/jsonfile) NPM module 10 | 11 | It often provides methods in asynchronous (e.g. `fs.readdir`) and synchronous 12 | (e.g. `fs.readdirSync`) versions. 13 | For asynchronous invocations all Macro functions use a shared thread pool 14 | (`fs.threadPool`). The number of threads allocated can be set using the 15 | `macro.core.iothreads` environment variables (defaults to half the number of CPU 16 | cores the machine has). 17 | 18 | It further includes an implementation of the 19 | [`jsonfile`](https://www.npmjs.com/package/jsonfile) 20 | module. 21 | 22 | 23 | ### `fs` Examples 24 | 25 | #### Synchronous filesystem access 26 | 27 | ```swift 28 | #!/usr/bin/swift sh 29 | import fs // Macro-swift/Macro 30 | 31 | if fs.existsSync("/etc/passwd") { 32 | if let passwd = fs.readFileSync("/etc/passwd", .utf8) { 33 | print("Passwd:") 34 | print(passwd) 35 | } 36 | } 37 | else { 38 | print("Contents of /etc:", try fs.readdirSync("/etc")) 39 | } 40 | ``` 41 | 42 | 43 | ### `jsonfile` Example 44 | 45 | ```swift 46 | #!/usr/bin/swift sh 47 | import fs // Macro-swift/Macro 48 | 49 | jsonfile.readFile("/tmp/myfile.json) { error, value in 50 | if let error = error { 51 | console.error("loading failed:", error) 52 | } 53 | else { 54 | print("Did load JSON:", value) 55 | } 56 | } 57 | ``` 58 | 59 | 60 | ### `path` Example 61 | 62 | ```swift 63 | #!/usr/bin/swift sh 64 | import Macro // @Macro-swift 65 | 66 | print("/usr/bin basename:", path.basename("/usr/bin")) // => '/bin' 67 | print("/usr/bin dirname: ", path.dirname ("/usr/bin")) // => '/usr' 68 | ``` 69 | -------------------------------------------------------------------------------- /Sources/fs/Streams/FileReadStream.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileReadStream.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | import class MacroCore.MacroCore 10 | import class MacroCore.ReadableByteStream 11 | import enum MacroCore.EventListenerSet 12 | import struct MacroCore.Buffer 13 | import class NIO.NIOFileHandle 14 | import protocol NIO.EventLoop 15 | import struct NIO.NonBlockingFileIO 16 | 17 | @inlinable 18 | public func createReadStream(on eventLoop: EventLoop? = nil, 19 | _ path: String) -> FileReadStream 20 | { 21 | let core = MacroCore.shared 22 | return FileReadStream(eventLoop: core.fallbackEventLoop(eventLoop), 23 | path: path) 24 | } 25 | 26 | open class FileReadStream: ReadableByteStream, FileStream { 27 | 28 | public static var defaultReadableHighWaterMark = 64 * 1024 29 | 30 | public let eventLoop : EventLoop 31 | public let path : String 32 | public var fileHandle : NIOFileHandle? 33 | public var pending = true 34 | 35 | override open var readableFlowing : Bool { 36 | didSet { 37 | // TBD: I think `flowing` refers to `data` vs `readable` events, not 38 | // whether the connection is paused. Read up on that :-) 39 | guard oldValue != readableFlowing else { return } 40 | if readableFlowing { 41 | _resume() 42 | } 43 | } 44 | } 45 | 46 | @inlinable 47 | init(eventLoop: EventLoop, path: String) { 48 | self.eventLoop = eventLoop 49 | self.path = path 50 | super.init() 51 | readableHighWaterMark = FileReadStream.defaultReadableHighWaterMark 52 | } 53 | 54 | public func _resume() { 55 | assert(readableFlowing) 56 | 57 | if pending { 58 | open() 59 | } 60 | else { 61 | if !readableFlowing { readableFlowing = true } 62 | startReading() 63 | } 64 | } 65 | 66 | private func close() { 67 | readableFlowing = false 68 | guard let fileHandle = fileHandle else { return } 69 | do { 70 | try fileHandle.close() 71 | self.fileHandle = nil 72 | closeListeners.emit() 73 | closeListeners.removeAll() 74 | _clearListeners() 75 | } 76 | catch { 77 | emit(error: error) 78 | } 79 | } 80 | 81 | private func handleEOF() { 82 | endListeners.emit() 83 | endListeners.removeAll() 84 | 85 | readableFlowing = false 86 | close() 87 | } 88 | 89 | private func startReading() { 90 | assert(!pending) 91 | guard let fileHandle = fileHandle else { 92 | assert(self.fileHandle != nil) 93 | return 94 | } 95 | 96 | // Note: Right now we just read proactively regardless of the HWM. 97 | #if false // FIXME: once we can properly do this 98 | let bytesToRead = readableHighWaterMark - readableLength 99 | #else 100 | let space = readableHighWaterMark - readableLength 101 | let bytesToRead = space > 0 ? space : readableHighWaterMark 102 | #endif 103 | 104 | fileIO.read(fileHandle: fileHandle, byteCount: bytesToRead, 105 | allocator: allocator, eventLoop: eventLoop) 106 | .whenComplete { result in 107 | switch result { 108 | case .success(let buffer): 109 | if buffer.readableBytes == 0 { 110 | self.handleEOF() 111 | } 112 | else { 113 | self.push(Buffer(buffer)) 114 | self.startReading() // not actually recursive ... 115 | } 116 | case .failure(let error): 117 | self.emit(error: error) 118 | } 119 | } 120 | } 121 | 122 | private func open() { 123 | assert(fileHandle == nil) 124 | assert(pending) 125 | fileIO.openFile(path: path, mode: .read, eventLoop: eventLoop) 126 | .whenComplete(_handleOpenResult) 127 | } 128 | func _handleOpenResult(_ result: Result) { 129 | switch result { 130 | case .success(let handle): 131 | assert(self.fileHandle == nil, "file handle already assigned!") 132 | try? self.fileHandle?.close() 133 | 134 | fileHandle = handle 135 | pending = false 136 | openListeners .emit(handle) 137 | readyListeners.emit() 138 | openListeners .removeAll() 139 | readyListeners.removeAll() 140 | _resume() 141 | 142 | case .failure(let error): 143 | pending = false 144 | emit(error: error) 145 | endListeners.emit() 146 | _clearListeners() 147 | } 148 | } 149 | 150 | 151 | override open func _clearListeners() { 152 | _clearFileListeners() 153 | super._clearListeners() 154 | } 155 | 156 | public var readyListeners = EventListenerSet() 157 | public var closeListeners = EventListenerSet() 158 | public var openListeners = EventListenerSet() 159 | } 160 | -------------------------------------------------------------------------------- /Sources/fs/Streams/FileStream.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileStream.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | import class MacroCore.MacroCore 10 | import enum MacroCore.EventListenerSet 11 | import protocol MacroCore.ErrorEmitterType 12 | import protocol MacroCore.ErrorEmitterTarget 13 | import struct NIO.ByteBufferAllocator 14 | import class NIO.NIOFileHandle 15 | import protocol NIO.EventLoop 16 | import struct NIO.NonBlockingFileIO 17 | 18 | // TODO: a pipe/| implementation which does a `sendfile`? 19 | 20 | public protocol FileStream: AnyObject, ErrorEmitterType, ErrorEmitterTarget { 21 | 22 | var eventLoop : EventLoop { get } 23 | var path : String { get } 24 | var fileHandle : NIOFileHandle? { get set } 25 | var pending : Bool { get } 26 | 27 | var allocator : ByteBufferAllocator { get } 28 | 29 | var readyListeners : EventListenerSet { get set } 30 | var closeListeners : EventListenerSet { get set } 31 | var openListeners : EventListenerSet { get set } 32 | } 33 | 34 | internal extension FileStream { 35 | 36 | var fileIO : NonBlockingFileIO { return FileSystemModule.fileIO } 37 | 38 | func _clearFileListeners() { 39 | openListeners .removeAll() 40 | closeListeners.removeAll() 41 | readyListeners.removeAll() 42 | } 43 | 44 | } 45 | 46 | public extension FileStream { 47 | 48 | @inlinable var allocator : ByteBufferAllocator { 49 | return MacroCore.shared.allocator 50 | } 51 | 52 | @discardableResult 53 | @inlinable 54 | func onceOpen(execute: @escaping ( NIOFileHandle ) -> Void) -> Self { 55 | if let fileHandle = fileHandle { execute(fileHandle) } 56 | else { openListeners.once(execute) } 57 | return self 58 | } 59 | 60 | /// Note: onOpen is the same as onceOpen 61 | @discardableResult @inlinable 62 | func onOpen(execute: @escaping ( NIOFileHandle ) -> Void) -> Self { 63 | return onceOpen(execute: execute) 64 | } 65 | 66 | @discardableResult @inlinable 67 | func onceReady(execute: @escaping () -> Void) -> Self { 68 | if !pending && fileHandle != nil { execute() } 69 | else { readyListeners.once(execute) } 70 | return self 71 | } 72 | 73 | /// Note: onReady is the same as onceReady 74 | @discardableResult @inlinable 75 | func onReady(execute: @escaping () -> Void) -> Self { 76 | return onceReady(execute: execute) 77 | } 78 | 79 | @discardableResult @inlinable 80 | func onceClose(execute: @escaping () -> Void) -> Self { 81 | closeListeners.once(execute) 82 | return self 83 | } 84 | 85 | /// Note: onClose is the same as onceClose 86 | @discardableResult @inlinable 87 | func onClose(execute: @escaping () -> Void) -> Self { 88 | return onceClose(execute: execute) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Sources/fs/Utils/AsyncWrapper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncWrapper.swift 3 | // Noze.io / Macro 4 | // 5 | // Created by Helge Heß on 5/8/16. 6 | // Copyright © 2016-2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | import class Dispatch.DispatchQueue 10 | import protocol NIO.EventLoop 11 | import class NIO.MultiThreadedEventLoopGroup 12 | import class NIO.NIOThreadPool 13 | import enum NIO.ChannelError 14 | import class MacroCore.MacroCore 15 | 16 | extension FileSystemModule { 17 | 18 | /// 19 | /// This is useful for wrapping synchronous file APIs. Example: 20 | /// 21 | /// public func readdir(path: String, cb: ( [ String ]? ) -> Void) { 22 | /// module.Q._evalAsync(readdirSync, path, cb) 23 | /// } 24 | /// 25 | 26 | @inlinable static 27 | func _evalAsync(on eventLoop: EventLoop? = nil, 28 | _ f : @escaping ( ArgT ) throws -> Void, 29 | _ arg : ArgT, 30 | _ yield : @escaping ( Error? ) -> Void) 31 | { 32 | let module = MacroCore.shared.retain() 33 | let loop = module.fallbackEventLoop(eventLoop) 34 | FileSystemModule.threadPool.submit { shouldRun in 35 | let returnError : Error? 36 | 37 | if case shouldRun = NIOThreadPool.WorkItemState.active { 38 | do { 39 | try f(arg) 40 | returnError = nil 41 | } 42 | catch let error { 43 | returnError = error 44 | } 45 | } 46 | else { 47 | returnError = ChannelError.ioOnClosedChannel 48 | } 49 | 50 | loop.execute { 51 | yield(returnError) 52 | module.release() 53 | } 54 | } 55 | } 56 | 57 | @inlinable static 58 | func _evalAsync(on eventLoop: EventLoop? = nil, 59 | _ f : @escaping ( ArgT ) throws -> RT, 60 | _ arg : ArgT, 61 | _ yield : @escaping ( Error?, RT? ) -> Void) 62 | { 63 | let module = MacroCore.shared.retain() 64 | let loop = module.fallbackEventLoop(eventLoop) 65 | FileSystemModule.threadPool.submit { shouldRun in 66 | let returnError : Error? 67 | let result : RT? 68 | 69 | if case shouldRun = NIOThreadPool.WorkItemState.active { 70 | do { 71 | result = try f(arg) 72 | returnError = nil 73 | } 74 | catch let error { 75 | returnError = error 76 | result = nil 77 | } 78 | } 79 | else { 80 | returnError = ChannelError.ioOnClosedChannel 81 | result = nil 82 | } 83 | 84 | loop.execute { 85 | yield(returnError, result) 86 | module.release() 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Sources/fs/Utils/ResultExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ResultExtensions.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | internal extension Result where Failure : Swift.Error { 10 | 11 | @inlinable 12 | var jsValue : Success? { 13 | switch self { 14 | case .success(let success) : return success 15 | case .failure : return nil 16 | } 17 | } 18 | @inlinable 19 | var jsError : Swift.Error? { 20 | switch self { 21 | case .success : return nil 22 | case .failure(let error) : return error 23 | } 24 | } 25 | 26 | @inlinable 27 | var jsTuple : ( Swift.Error?, Success? ) { 28 | switch self { 29 | case .success(let success) : return ( nil, success ) 30 | case .failure(let error) : return ( error, nil ) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/fs/Utils/StatStruct.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StatStruct.swift 3 | // Noze.io / Macro 4 | // 5 | // Created by Helge Heß on 5/8/16. 6 | // Copyright © 2016-2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | import xsys 10 | 11 | #if os(Windows) 12 | // TODO: port me 13 | #elseif os(Linux) 14 | import let Glibc.S_IFMT 15 | import let Glibc.S_IFREG 16 | import let Glibc.S_IFDIR 17 | import let Glibc.S_IFBLK 18 | import let Glibc.S_IFLNK 19 | import let Glibc.S_IFIFO 20 | import let Glibc.S_IFSOCK 21 | import let Glibc.S_IFCHR 22 | #else 23 | import let Darwin.S_IFMT 24 | import let Darwin.S_IFREG 25 | import let Darwin.S_IFDIR 26 | import let Darwin.S_IFBLK 27 | import let Darwin.S_IFLNK 28 | import let Darwin.S_IFIFO 29 | import let Darwin.S_IFSOCK 30 | import let Darwin.S_IFCHR 31 | #endif 32 | import struct Foundation.Date 33 | import struct Foundation.TimeInterval 34 | 35 | #if !os(Windows) 36 | /** 37 | * Node like accessors to the Unix `stat` structure. 38 | */ 39 | public extension xsys.stat_struct { 40 | 41 | // could be properties, but for consistency with Node ... 42 | @inlinable 43 | func isFile() -> Bool { return (st_mode & S_IFMT) == S_IFREG } 44 | @inlinable 45 | func isDirectory() -> Bool { return (st_mode & S_IFMT) == S_IFDIR } 46 | @inlinable 47 | func isBlockDevice() -> Bool { return (st_mode & S_IFMT) == S_IFBLK } 48 | @inlinable 49 | func isSymbolicLink() -> Bool { return (st_mode & S_IFMT) == S_IFLNK } 50 | @inlinable 51 | func isFIFO() -> Bool { return (st_mode & S_IFMT) == S_IFIFO } 52 | @inlinable 53 | func isSocket() -> Bool { return (st_mode & S_IFMT) == S_IFSOCK } 54 | 55 | @inlinable 56 | func isCharacterDevice() -> Bool { 57 | return (st_mode & S_IFMT) == S_IFCHR 58 | } 59 | 60 | 61 | @inlinable 62 | var size : Int { return Int(st_size) } 63 | 64 | 65 | // MARK: - Dates 66 | 67 | #if os(Linux) 68 | @inlinable var atime : Date { 69 | return Date(timeIntervalSince1970: st_atim.timeInterval) 70 | } 71 | /// The timestamp of the last modification to the file. 72 | @inlinable var mtime : Date { 73 | return Date(timeIntervalSince1970: st_mtim.timeInterval) 74 | } 75 | /// The timestamp of the last file status change. 76 | @inlinable var ctime : Date { 77 | return Date(timeIntervalSince1970: st_ctim.timeInterval) 78 | } 79 | @available(*, unavailable) 80 | @inlinable var birthtime : Date { 81 | fatalError("\(#function) not available on Linux") 82 | } 83 | #else // Darwin 84 | /// The timestamp of the last access (read or write?) to the file. 85 | @inlinable var atime : Date { 86 | return Date(timeIntervalSince1970: st_atimespec.timeInterval) 87 | } 88 | /// The timestamp of the last modification to the file. 89 | @inlinable var mtime : Date { 90 | return Date(timeIntervalSince1970: st_mtimespec.timeInterval) 91 | } 92 | /// The timestamp of the last file status change. 93 | @inlinable var ctime : Date { 94 | return Date(timeIntervalSince1970: st_ctimespec.timeInterval) 95 | } 96 | /// The timestamp when the file was created. 97 | @inlinable var birthtime : Date { 98 | return Date(timeIntervalSince1970: st_birthtimespec.timeInterval) 99 | } 100 | #endif // Darwin 101 | } 102 | 103 | extension timespec { 104 | @usableFromInline 105 | var timeInterval : TimeInterval { 106 | let nanoSecondsToSeconds = 1.0E-9 107 | return TimeInterval(tv_sec) 108 | + (TimeInterval(tv_nsec) * nanoSecondsToSeconds) 109 | } 110 | } 111 | extension time_t { 112 | @usableFromInline 113 | var timeInterval : TimeInterval { return TimeInterval(self) } 114 | } 115 | #endif // !os(Windows) 116 | -------------------------------------------------------------------------------- /Sources/http/Agent/Agent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Agent.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | /** 10 | * TODO: 11 | * This eventually should also support Async HTTP Client and allow the framework 12 | * consumer to select the Agent backend. 13 | * The switching could be done by either making `Agent` a function, or by 14 | * creating a `Agent` class with distinct backends. 15 | * 16 | * For AHC we might want to tie `globalAgent` to an eventloop (i.e. thread). 17 | */ 18 | 19 | public typealias Agent = URLSessionAgent 20 | 21 | public extension HTTPModule { 22 | 23 | static var globalAgent = Agent(options: .init()) 24 | 25 | } 26 | -------------------------------------------------------------------------------- /Sources/http/Agent/Foundation/URLRequestInit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Helge Heß on 11.12.20. 6 | // 7 | 8 | import Foundation 9 | 10 | import struct Foundation.URL 11 | 12 | #if canImport(FoundationNetworking) 13 | import struct FoundationNetworking.URLRequest 14 | #else 15 | import struct Foundation.URLRequest 16 | #endif 17 | 18 | public extension URLRequest { 19 | 20 | init(options: HTTPModule.ClientRequestOptions) { 21 | if let timeInterval = options.timeoutInterval { 22 | self.init(url: options.url, cachePolicy: .useProtocolCachePolicy, 23 | timeoutInterval: timeInterval) 24 | } 25 | else { 26 | self.init(url: options.url) 27 | } 28 | 29 | httpMethod = options.method 30 | 31 | for ( name, value ) in options.headers { 32 | addValue(value, forHTTPHeaderField: name) 33 | } 34 | 35 | if options.setHost && value(forHTTPHeaderField: "Host") == nil { 36 | addValue(options.host, forHTTPHeaderField: "Host") 37 | } 38 | 39 | if let auth = options.auth { 40 | let b64 = Data(auth.utf8).base64EncodedString() 41 | setValue("Basic " + b64, forHTTPHeaderField: "Authorization") 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/http/Agent/Foundation/URLSessionAgent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLSessionAgent.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | import MacroCore 10 | import struct Logging.Logger 11 | import struct Foundation.URL 12 | import enum NIOHTTP1.HTTPMethod 13 | 14 | #if canImport(FoundationNetworking) 15 | import class FoundationNetworking.URLSession 16 | import struct FoundationNetworking.URLRequest 17 | #else 18 | import class Foundation.URLSession 19 | import struct Foundation.URLRequest 20 | #endif 21 | 22 | /** 23 | * Some Node HTTP Agent API built on top of URLSession. 24 | */ 25 | public final class URLSessionAgent { 26 | 27 | public struct Options { 28 | // TODO: expose more URLSession options 29 | 30 | public var session : URLSession 31 | public var logger : Logger 32 | 33 | public init(session: URLSession = .shared, 34 | logger: Logger = .init(label: "μ.http")) 35 | { 36 | self.session = session 37 | self.logger = logger 38 | } 39 | } 40 | 41 | public var options : Options 42 | 43 | public init(options: Options = .init()) { 44 | self.options = options 45 | } 46 | 47 | 48 | // MARK: - Initiate Request 49 | 50 | @usableFromInline 51 | func request(_ options: HTTPModule.ClientRequestOptions, 52 | onResponse execute: (( IncomingMessage ) -> Void)? = nil) 53 | -> ClientRequest 54 | { 55 | // This one is a little weird, because URLSession seems to lack a streaming 56 | // HTTP body. 57 | // Or, well, it does have URLStreamTask. TODO :-) (rather do AHC) 58 | 59 | let loop = MacroCore.shared.fallbackEventLoop() 60 | let urlRequest = URLRequest(options: options) 61 | let clientRequest = 62 | URLSessionClientRequest(agent: self, request: urlRequest, eventLoop: loop) 63 | if let execute = execute { 64 | clientRequest.onceResponse(execute: execute) 65 | } 66 | 67 | clientRequest.isWaitingForEnd = true 68 | if !clientRequest.isWaitingForEnd { 69 | clientRequest.startRequest() 70 | } 71 | 72 | return clientRequest 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Sources/http/Agent/Foundation/URLSessionClientRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLSessionClientRequest.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | import struct MacroCore.Buffer 10 | import enum MacroCore.WritableError 11 | import struct Foundation.URL 12 | import class Foundation.NSObject 13 | import struct Logging.Logger 14 | import protocol NIO.EventLoop 15 | import struct NIOHTTP1.HTTPResponseHead 16 | import enum NIOHTTP1.HTTPResponseStatus 17 | import struct NIOHTTP1.HTTPHeaders 18 | 19 | #if canImport(FoundationNetworking) 20 | import class FoundationNetworking.URLSession 21 | import struct FoundationNetworking.URLRequest 22 | import class FoundationNetworking.URLSessionTask 23 | import class FoundationNetworking.URLSessionDataTask 24 | import class FoundationNetworking.HTTPURLResponse 25 | #else 26 | import class Foundation.URLSession 27 | import struct Foundation.URLRequest 28 | import class Foundation.URLSessionTask 29 | import class Foundation.URLSessionDataTask 30 | import class Foundation.HTTPURLResponse 31 | #endif 32 | 33 | public final class URLSessionClientRequest: ClientRequest { 34 | 35 | let agent : URLSessionAgent 36 | let request : URLRequest 37 | let eventLoop : EventLoop 38 | var task : URLSessionDataTask? 39 | var isWaitingForEnd = false 40 | var writtenContent = Buffer() 41 | 42 | var response : IncomingMessage? 43 | 44 | private var didRetain = false 45 | 46 | init(agent: URLSessionAgent, request: URLRequest, eventLoop: EventLoop) { 47 | self.agent = agent 48 | self.request = request 49 | self.eventLoop = eventLoop 50 | 51 | super.init(unsafeChannel: nil, log: agent.options.logger) 52 | } 53 | deinit { 54 | if didRetain { core.release(); didRetain = false } 55 | } 56 | 57 | private var selfRef : AnyObject? 58 | 59 | private func setupResponse(with httpResponse: HTTPURLResponse?) 60 | -> IncomingMessage 61 | { 62 | assert(response == nil) 63 | 64 | var headers = HTTPHeaders() 65 | for ( name, value ) in httpResponse?.allHeaderFields ?? [:] { 66 | if let name = name as? String, let value = value as? String { 67 | headers.add(name: name, value: value) 68 | } 69 | else { 70 | headers.add(name: String(describing: name), 71 | value: String(describing: value)) // TBD 72 | } 73 | } 74 | let status = HTTPResponseStatus(statusCode: httpResponse?.statusCode ?? 200) 75 | 76 | let response = IncomingMessage(status: status, headers: headers) 77 | return response 78 | } 79 | 80 | func startRequest() { 81 | assert(task == nil) 82 | assert(response == nil) 83 | 84 | let eventLoop = self.eventLoop 85 | 86 | var request = self.request 87 | if !writtenContent.isEmpty { 88 | request.httpBody = writtenContent.data 89 | } 90 | 91 | // FIXME: the agent should be the session delegate and do the receiving 92 | task = agent.options.session.dataTask(with: request) { 93 | data, urlResponse, error in 94 | defer { self.task = nil } 95 | 96 | let response = self.setupResponse(with: urlResponse as? HTTPURLResponse) 97 | self.response = response 98 | 99 | eventLoop.execute { 100 | self.responseListeners.emit(response) 101 | } 102 | 103 | // give the client another chance to register in a delayed way! 104 | eventLoop.execute { 105 | if let data = data, !data.isEmpty { 106 | response.push(Buffer(data)) 107 | } 108 | response.push(nil) // EOF 109 | 110 | self.selfRef = nil 111 | if self.didRetain { self.didRetain = false; self.core.release() } 112 | } 113 | } 114 | 115 | guard let task = task else { 116 | log.error("request has no associated data task!") 117 | assertionFailure("attempt to start request w/o data task!") 118 | return 119 | } 120 | 121 | if !didRetain { didRetain = true; core.retain() } 122 | selfRef = self 123 | task.resume() 124 | } 125 | 126 | 127 | // MARK: - Writable stream ... 128 | 129 | override public func end() { 130 | guard !writableEnded else { return } 131 | 132 | finishListeners.emit() 133 | _clearListenersOnFinish() 134 | 135 | if isWaitingForEnd { 136 | isWaitingForEnd = false 137 | startRequest() 138 | } 139 | } 140 | private func _clearListenersOnFinish() { 141 | finishListeners.removeAll() 142 | errorListeners .removeAll() 143 | } 144 | 145 | @discardableResult 146 | public override func write(_ bytes: Buffer, 147 | whenDone: @escaping () -> Void) -> Bool { 148 | writtenContent.append(bytes) 149 | whenDone() 150 | return true 151 | } 152 | @discardableResult @inlinable 153 | override public func write(_ string: String, 154 | whenDone: @escaping () -> Void = {}) -> Bool 155 | { 156 | return write(Buffer(string), whenDone: whenDone) 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /Sources/http/BasicAuth.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BasicAuth.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2020-2023 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | public enum BasicAuthModule {} 10 | public typealias basicAuth = BasicAuthModule 11 | 12 | #if canImport(Foundation) 13 | import Foundation 14 | #endif 15 | import enum MacroCore.CharsetConversionError 16 | 17 | public extension BasicAuthModule { 18 | 19 | /** 20 | * HTTP Basic Authentication credentials as extracted by the 21 | * ``auth(_:encoding:)`` function, i.e. the name/password associated 22 | * with an ``IncomingMessage``. 23 | */ 24 | struct Credentials { 25 | public let name : String 26 | public let pass : String 27 | } 28 | 29 | enum BasicAuthError : Swift.Error { 30 | case missingAuthorizationHeader 31 | case unexpectedAuthorizationHeaderType 32 | case invalidAuthorizationHeader 33 | case differentAuthorization 34 | case invalidBasicAuthorizationHeader 35 | case stringEncodingError 36 | } 37 | 38 | #if canImport(Foundation) // `String.Encoding.utf8`, provide alternative! 39 | /** 40 | * Extract HTTP Basic authentication credentials (name/pass) from the given 41 | * ```IncomingMessage```. 42 | * 43 | * - Parameters: 44 | * - request: The ``IncomingMessage`` containing the `Authorization` 45 | * header. 46 | * - encoding: The encoding the String is using. 47 | * - Throws: ``BasicAuthError/missingAuthorizationHeader`` if there is no 48 | * `Authorization` header, 49 | * ``BasicAuthError/unexpectedAuthorizationHeaderType`` if the header 50 | * existed, but wasn't a `String`, 51 | * ``BasicAuthError/invalidAuthorizationHeader`` if the header value 52 | * syntax could not be parsed, 53 | * ``BasicAuthError/differentAuthorization`` if the header was set, but 54 | * wasn't HTTP Basic authentication, 55 | * ``BasicAuthError/invalidBasicAuthorizationHeader`` if the header was 56 | * set, but didn't had the right Basic auth syntax, 57 | * ``BasicAuthError/invalidBasicAuthorizationHeader`` if the header could 58 | * be parsed, but the values could not be parsed using the given 59 | * `encoding` specified. 60 | * - Returns: The ``Credentials``, i.e. `name`/`pass`. 61 | */ 62 | static func auth(_ request: IncomingMessage, 63 | encoding: String.Encoding = .utf8) 64 | throws -> Credentials 65 | { 66 | guard let authorization = request.getHeader("Authorization") else { 67 | throw BasicAuthError.missingAuthorizationHeader 68 | } 69 | guard let authString = authorization as? String else { 70 | throw BasicAuthError.unexpectedAuthorizationHeaderType 71 | } 72 | 73 | guard let idx = authString 74 | .firstIndex(where: { $0 == "\t" || $0 == " "}) else 75 | { 76 | throw BasicAuthError.invalidAuthorizationHeader 77 | } 78 | 79 | let scheme = authString[authString.startIndex..Macro `http` Module 2 | 4 | 5 | 6 | An HTTP module modelled after the builtin Node 7 | [http module](https://nodejs.org/dist/latest-v7.x/docs/api/http.html). 8 | In applications you probably want to use the Connect or Express module instead. 9 | 10 | ### HTTP server 11 | 12 | Example: 13 | 14 | ```swift 15 | import http 16 | 17 | http.createServer { req, res in 18 | res.writeHead(200, [ "Content-Type": "text/html" ]) 19 | res.end("

Hello World

") 20 | } 21 | .listen(1337) 22 | ``` 23 | 24 | ### HTTP Client 25 | 26 | A simple GET request, collecting the full response in memory: 27 | 28 | ```swift 29 | #!/usr/bin/swift sh 30 | import http // Macro-swift/Macro 31 | 32 | http.get("https://zeezide.de") { res in 33 | console.log("got response:", res) 34 | 35 | res.onError { error in 36 | console.error("error:", error) 37 | } 38 | 39 | res | concat { buffer in 40 | let s = try? buffer.toString() 41 | console.log("Response:\n\(s ?? "-")") 42 | } 43 | } 44 | ``` 45 | 46 | A simple POST request: 47 | 48 | ```swift 49 | #!/usr/bin/swift sh 50 | import http // Macro-swift/Macro 51 | 52 | let options = http.ClientRequestOptions( 53 | protocol : "https:", 54 | host : "jsonplaceholder.typicode.com", 55 | method : "POST", 56 | path : "/posts" 57 | ) 58 | 59 | let req = http.request(options) { res in 60 | console.log("got response:", res) 61 | 62 | res.onError { error in 63 | console.error("error:", error) 64 | } 65 | 66 | res | concat { buffer in 67 | let s = try? buffer.toString() 68 | console.log("Response:\n\(s ?? "-")") 69 | } 70 | } 71 | 72 | req.write( 73 | """ 74 | { "userId": 1, 75 | "title": "Blubs", 76 | "body": "Rummss" } 77 | """ 78 | ) 79 | req.end() 80 | ``` 81 | -------------------------------------------------------------------------------- /Sources/http/Support/HTTPHeadersHolder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HTTPHeadersHolder.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | import struct NIOHTTP1.HTTPHeaders 10 | 11 | public protocol HTTPHeadersHolder: AnyObject { 12 | var headers : HTTPHeaders { get } 13 | } 14 | public protocol HTTPMutableHeadersHolder: HTTPHeadersHolder { 15 | var headers : HTTPHeaders { get set } 16 | } 17 | 18 | // MARK: - Node API 19 | 20 | public extension HTTPHeadersHolder { 21 | 22 | @inlinable 23 | func getHeader(_ name: String) -> Any? { 24 | let values = headers[name] 25 | guard !values.isEmpty else { return nil } 26 | if !shouldReturnArrayValueForHeader(name) && values.count == 1 { 27 | return values.first 28 | } 29 | return values 30 | } 31 | 32 | @usableFromInline 33 | internal func shouldReturnArrayValueForHeader(_ name: String) -> Bool { 34 | // TODO: which headers only make sense as arrays? Accept? 35 | return false 36 | } 37 | } 38 | 39 | public extension HTTPMutableHeadersHolder { 40 | 41 | @inlinable 42 | func setHeader(_ name: String, _ value: String) { 43 | headers.replaceOrAdd(name: name, value: value) 44 | } 45 | @inlinable 46 | func setHeader(_ name: String, _ value: S) { 47 | setHeader(name, String(value)) 48 | } 49 | @inlinable 50 | func setHeader(_ name: String, _ value: Int) { 51 | setHeader(name, String(value)) 52 | } 53 | // TODO: Bool version for Brief 54 | 55 | @inlinable 56 | func setHeader(_ name: String, _ value: S) 57 | where S.Element == String 58 | { 59 | headers.remove(name: name) 60 | value.forEach { headers.add(name: name, value: $0) } 61 | } 62 | @inlinable 63 | func setHeader(_ name: String, _ value: S) 64 | where S.Element: StringProtocol 65 | { 66 | setHeader(name, value.map { String($0) }) 67 | } 68 | 69 | @inlinable 70 | func setHeader(_ name: String, _ value: Any) { 71 | if let value = value as? String { 72 | setHeader(name, value) 73 | } 74 | else if let value = value as? [ String ] { 75 | setHeader(name, value) 76 | } 77 | else { 78 | setHeader(name, stringForValue(value, of: name)) 79 | } 80 | } 81 | 82 | @inlinable 83 | func removeHeader(_ name: String) { 84 | headers.remove(name: name) 85 | } 86 | 87 | // MARK: - Support 88 | 89 | @usableFromInline 90 | internal func stringForValue(_ value: Any, of header: String) -> String { 91 | // TODO: improve, support HTTP dates, Ints, etc 92 | return "\(value)" 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Sources/http/Support/OutgoingMessage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OutgoingMessage.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | import protocol NIO.Channel 10 | import struct NIOHTTP1.HTTPHeaders 11 | import struct Logging.Logger 12 | import class MacroCore.WritableByteStream 13 | import protocol MacroCore.WritableStreamType 14 | import protocol MacroCore.WritableByteStreamType 15 | import protocol MacroCore.ListenerType 16 | import class MacroCore.ErrorEmitter 17 | import enum MacroCore.EventListenerSet 18 | import func MacroCore.nextTick 19 | import struct MacroCore.Buffer 20 | import struct MacroCore.EnvironmentValues 21 | import protocol MacroCore.EnvironmentValuesHolder 22 | 23 | /** 24 | * Baseclass for `ServerResponse` and `ClientRequest`. 25 | * 26 | * Hierarchy: 27 | * 28 | * WritableStreamBase 29 | * WritableByteStreamBase 30 | * * OutgoingMessage 31 | * ServerResponse 32 | * ClientRequest 33 | */ 34 | open class OutgoingMessage: WritableByteStream, 35 | WritableStreamType, WritableByteStreamType 36 | { 37 | 38 | public enum StreamState: Equatable { 39 | case ready 40 | case isEnding 41 | case finished 42 | } 43 | 44 | public var log : Logger 45 | public var headers = HTTPHeaders() 46 | public var headersSent = false 47 | public var sendDate = true 48 | 49 | /** 50 | * Use `EnvironmentKey`s to store extra information alongside requests. 51 | * This is similar to using a Node/Express `locals` dictionary (or attaching 52 | * directly properties to a request), but typesafe. 53 | * 54 | * For example a database connection associated with the request, 55 | * or some extra data a custom bodyParser parsed. 56 | * 57 | * Example: 58 | * 59 | * enum LoginUserEnvironmentKey: EnvironmentKey { 60 | * static let defaultValue = "" 61 | * } 62 | * 63 | * In addition to the key definition, one usually declares an accessor to the 64 | * respective environment holder, for example the `IncomingMessage`: 65 | * 66 | * extension IncomingMessage { 67 | * 68 | * var loginUser : String { 69 | * set { self[LoginUserEnvironmentKey.self] = newValue } 70 | * get { self[LoginUserEnvironmentKey.self] } 71 | * } 72 | * } 73 | * 74 | */ 75 | public lazy var environment = MacroCore.EnvironmentValues.empty 76 | 77 | public internal(set) var socket : Channel? 78 | 79 | @available(*, deprecated, message: "Please use the regular `log` w/ `.error`") 80 | @inlinable 81 | override open var errorLog : Logger { return log } // this was a mistake 82 | 83 | public var state = StreamState.ready 84 | 85 | override open var writableFinished : Bool { return state == .finished } 86 | override open var writableEnded : Bool { 87 | return state == .isEnding || state == .finished 88 | } 89 | @inlinable 90 | override open var writable : Bool { return !writableEnded } 91 | 92 | public init(unsafeChannel channel: Channel?, log: Logger) { 93 | self.socket = channel 94 | self.log = log 95 | super.init() 96 | } 97 | 98 | // MARK: - End Stream 99 | 100 | open func end() { 101 | assertionFailure("subclass responsibility: \(#function)") 102 | } 103 | 104 | // MARK: - Error Handling 105 | 106 | open func handleError(_ error: Error) { 107 | log.error("\(error)") 108 | _ = socket?.close() // TBD 109 | socket = nil 110 | emit(error: error) 111 | finishListeners.emit() 112 | } 113 | 114 | // MARK: - WritableByteStream 115 | 116 | @discardableResult 117 | open func write(_ bytes: Buffer, whenDone: @escaping () -> Void) -> Bool { 118 | assertionFailure("subclass responsibility: \(#function)") 119 | whenDone() 120 | return false 121 | } 122 | @discardableResult @inlinable 123 | open func write(_ string: String, whenDone: @escaping () -> Void = {}) -> Bool 124 | { 125 | guard writableCorked || socket != nil else { whenDone(); return false } 126 | return write(Buffer(string), whenDone: whenDone) 127 | } 128 | 129 | } 130 | 131 | extension OutgoingMessage: EnvironmentValuesHolder {} 132 | extension OutgoingMessage: HTTPMutableHeadersHolder {} 133 | -------------------------------------------------------------------------------- /Sources/http/http.swift: -------------------------------------------------------------------------------- 1 | // 2 | // http.swift 3 | // Macro 4 | // 5 | // Created by Helge Hess. 6 | // Copyright © 2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | /** 10 | * The Macro HTTP module. 11 | */ 12 | public enum HTTPModule { 13 | } 14 | 15 | public extension HTTPModule { 16 | typealias IncomingMessage = http.IncomingMessage 17 | typealias ServerResponse = http.ServerResponse 18 | typealias Server = http.Server 19 | 20 | @inlinable 21 | @discardableResult 22 | static func createServer(handler: (( IncomingMessage, ServerResponse ) 23 | -> Void)? = nil) 24 | -> Server 25 | { 26 | return http.createServer(handler: handler) 27 | } 28 | } 29 | 30 | 31 | // MARK: - Server 32 | 33 | /// Creates an `http.Server` object and attaches a provided `onRequest` handler. 34 | /// 35 | /// To activate the server, the `listen` method needs to be called. 36 | /// 37 | /// Example: 38 | /// 39 | /// http.createServer { req, res in 40 | /// res.end("Hello World!") 41 | /// } 42 | /// .listen(1337) 43 | /// 44 | @inlinable 45 | @discardableResult 46 | public func createServer(handler: (( IncomingMessage, ServerResponse ) -> Void)? 47 | = nil) 48 | -> Server 49 | { 50 | let server = Server() 51 | if let handler = handler { _ = server.onRequest(execute: handler) } 52 | return server 53 | } 54 | -------------------------------------------------------------------------------- /Sources/xsys/Module.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Module.swift 3 | // Noze.io / Macro 4 | // 5 | // Created by Helge Hess on 11/04/16. 6 | // Copyright © 2016-2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | #if os(Windows) 10 | import WinSDK 11 | #elseif os(Linux) 12 | import Glibc 13 | #else 14 | import Darwin 15 | #endif 16 | 17 | public struct XSysModule { 18 | } 19 | public let module = XSysModule() 20 | -------------------------------------------------------------------------------- /Sources/xsys/POSIXError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // POSIXError.swift 3 | // Noze.io / Macro 4 | // 5 | // Created by Helge Hess on 11/04/16. 6 | // Copyright © 2016-2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | // TBD: This is a bit weird. Now even more due to POSIXErrorCode vs POSIXError. 10 | // But well :-) 11 | 12 | #if os(Windows) 13 | import WinSDK 14 | 15 | // TODO: port me 16 | #elseif os(Linux) 17 | import Glibc 18 | 19 | public let EWOULDBLOCK = Glibc.EWOULDBLOCK 20 | 21 | // This is lame, but how else? This does not work: 22 | // case EAGAIN = Glibc.EAGAIN 23 | // 24 | // code from, hopefully they are kinda stable :-): 25 | // /usr/include/asm-generic/errno-base.h 26 | 27 | public enum POSIXErrorCode : CInt { 28 | case EPERM = 1 29 | case ENOENT = 2 30 | case ESRCH = 3 31 | case EINTR = 4 32 | case EIO = 5 33 | case ENXIO = 6 34 | case E2BIG = 7 35 | case ENOEXEC = 8 36 | case EBADF = 9 37 | case ECHILD = 10 38 | case EAGAIN = 11 // == EWOULDBLOCK 39 | case ENOMEM = 12 40 | case EACCES = 13 41 | case EFAULT = 14 42 | case ENOTBLK = 15 43 | case EBUSY = 16 44 | case EEXIST = 17 45 | case EXDEV = 18 46 | case ENODEV = 19 47 | case ENOTDIR = 20 48 | case EISDIR = 21 49 | case EINVAL = 22 50 | case ENFILE = 23 51 | case EMFILE = 24 52 | case ENOTTY = 25 53 | case ETXTBSY = 26 54 | case EFBIG = 27 55 | case ENOSPC = 28 56 | case ESPIPE = 29 57 | case EROFS = 30 58 | case EMLINK = 31 59 | case EPIPE = 32 60 | case EDOM = 33 61 | case ERANGE = 34 62 | case EDEADLK = 35 // == EDEADLOCK 63 | case ENAMETOOLONG = 36 64 | case ENOLCK = 37 65 | case ENOSYS = 38 66 | case ENOTEMPTY = 39 67 | case ELOOP = 40 68 | case ENOMSG = 42 69 | case EIDRM = 43 70 | case ECHRNG = 44 71 | case EL2NSYNC = 45 72 | case EL3HLT = 46 73 | case EL3RST = 47 74 | case ELNRNG = 48 75 | case EUNATCH = 49 76 | case ENOCSI = 50 77 | case EL2HLT = 51 78 | case EBADE = 52 79 | case EBADR = 53 80 | case EXFULL = 54 81 | case ENOANO = 55 82 | case EBADRQC = 56 83 | case EBADSLT = 57 84 | case EBFONT = 59 85 | case ENOSTR = 60 86 | case ENODATA = 61 87 | case ETIME = 62 88 | case ENOSR = 63 89 | case ENONET = 64 90 | case ENOPKG = 65 91 | case EREMOTE = 66 92 | case ENOLINK = 67 93 | case EADV = 68 94 | case ESRMNT = 69 95 | case ECOMM = 70 96 | case EPROTO = 71 97 | case EMULTIHOP = 72 98 | case EDOTDOT = 73 99 | case EBADMSG = 74 100 | case EOVERFLOW = 75 101 | case ENOTUNIQ = 76 102 | case EBADFD = 77 103 | case EREMCHG = 78 104 | case ELIBACC = 79 105 | case ELIBBAD = 80 106 | case ELIBSCN = 81 107 | case ELIBMAX = 82 108 | case ELIBEXEC = 83 109 | case EILSEQ = 84 110 | case ERESTART = 85 111 | case ESTRPIPE = 86 112 | case EUSERS = 87 113 | case ENOTSOCK = 88 114 | case EDESTADDRREQ = 89 115 | case EMSGSIZE = 90 116 | case EPROTOTYPE = 91 117 | case ENOPROTOOPT = 92 118 | case EPROTONOSUPPORT = 93 119 | case ESOCKTNOSUPPORT = 94 120 | case ENOTSUP = 95 // == EOPNOTSUPP 121 | case EPFNOSUPPORT = 96 122 | case EAFNOSUPPORT = 97 123 | case EADDRINUSE = 98 124 | case EADDRNOTAVAIL = 99 125 | case ENETDOWN = 100 126 | case ENETUNREACH = 101 127 | case ENETRESET = 102 128 | case ECONNABORTED = 103 129 | case ECONNRESET = 104 130 | case ENOBUFS = 105 131 | case EISCONN = 106 132 | case ENOTCONN = 107 133 | case ESHUTDOWN = 108 134 | case ETOOMANYREFS = 109 135 | case ETIMEDOUT = 110 136 | case ECONNREFUSED = 111 137 | case EHOSTDOWN = 112 138 | case EHOSTUNREACH = 113 139 | case EALREADY = 114 140 | case EINPROGRESS = 115 141 | case ESTALE = 116 142 | case EUCLEAN = 117 143 | case ENOTNAM = 118 144 | case ENAVAIL = 119 145 | case EISNAM = 120 146 | case EREMOTEIO = 121 147 | case EDQUOT = 122 148 | case ENOMEDIUM = 123 149 | case EMEDIUMTYPE = 124 150 | case ECANCELED = 125 151 | case ENOKEY = 126 152 | case EKEYEXPIRED = 127 153 | case EKEYREVOKED = 128 154 | case EKEYREJECTED = 129 155 | case EOWNERDEAD = 130 156 | case ENOTRECOVERABLE = 131 157 | case ERFKILL = 132 158 | case EHWPOISON = 133 159 | } 160 | 161 | extension POSIXErrorCode : Error {} 162 | 163 | public var errno : Int32 { 164 | get { return Glibc.errno } 165 | set { Glibc.errno = newValue } 166 | } 167 | 168 | #else // MacOS 169 | import Darwin 170 | 171 | public let EWOULDBLOCK = Darwin.EWOULDBLOCK 172 | 173 | public var errno : Int32 { 174 | get { return Darwin.errno } 175 | set { Darwin.errno = newValue } 176 | } 177 | 178 | // this doesn't seem to work though 179 | import Foundation // this is for POSIXError : Error 180 | 181 | #if compiler(>=6) 182 | extension POSIXErrorCode : @retroactive Error {} 183 | #else 184 | extension POSIXErrorCode : Error {} 185 | #endif 186 | #endif // MacOS 187 | -------------------------------------------------------------------------------- /Sources/xsys/README.md: -------------------------------------------------------------------------------- 1 | # xsys 2 | 3 | Posix wrappers and naming shims. 4 | 5 | Instead of having to do this in all your code: 6 | 7 | ```swift 8 | #if os(Linux) 9 | import Glibc 10 | #else 11 | import Darwin 12 | #endif 13 | 14 | let h = dlopen("/blub") 15 | ``` 16 | 17 | You can do this: 18 | 19 | ```swift 20 | import xsys 21 | 22 | let h = dlopen("/blub") 23 | ``` 24 | 25 | ### `timeval_any` 26 | 27 | Abstracts three different Posix types into one common protocol, and provides common 28 | operations for all of them. 29 | 30 | - `timeval_t` 31 | - `timespec_t` 32 | - `time_t` 33 | -------------------------------------------------------------------------------- /Sources/xsys/SocketAddress.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketAddress.swift 3 | // Noze.io / Macro 4 | // 5 | // Created by Helge Hess on 12/04/16. 6 | // Copyright © 2016-2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | #if os(Windows) 10 | import WinSDK 11 | #elseif os(Linux) 12 | import Glibc 13 | #else 14 | import Darwin 15 | #endif 16 | 17 | public protocol SocketAddress { 18 | 19 | static var domain: Int32 { get } 20 | 21 | init() // create empty address, to be filled by eg getsockname() 22 | 23 | var len: __uint8_t { get } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/xsys/UUID.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UUID.swift 3 | // Noze.io 4 | // 5 | // Created by Helge Hess on 23/07/16. 6 | // Copyright © 2016 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | #if !os(Linux) 10 | import Darwin 11 | 12 | private func makeArray(count c: Int) -> [ T ] { 13 | return Array(repeating: 0, count: c) 14 | } 15 | 16 | /// Hm, `uuid_t` is a tuple and can't be extended. Also, you can't really work 17 | /// with that tuple in the C API. 18 | public struct xsys_uuid { 19 | 20 | public var arrayValue : [ UInt8 ] 21 | 22 | public var value : uuid_t { 23 | return uuid(fromArray: arrayValue) 24 | } 25 | 26 | 27 | // MARK: - UUID Generators 28 | 29 | public static func generate() -> xsys_uuid { 30 | var v : [UInt8] = makeArray(count: 16) 31 | uuid_generate(&v) 32 | return xsys_uuid(v) 33 | } 34 | 35 | public static func generateRandom() -> xsys_uuid { 36 | var v : [UInt8] = makeArray(count: 16) 37 | uuid_generate_random(&v) 38 | return xsys_uuid(v) 39 | } 40 | 41 | public static func generateTime() -> xsys_uuid { 42 | var v : [UInt8] = makeArray(count: 16) 43 | uuid_generate_time(&v) 44 | return xsys_uuid(v) 45 | } 46 | 47 | 48 | // MARK: - Init Structure 49 | 50 | public init!(_ uuid: [ UInt8 ]) { 51 | arrayValue = uuid 52 | } 53 | 54 | public init(_ uuid: uuid_t) { 55 | var v = Array() 56 | v.reserveCapacity(16) 57 | // This can be done with reflection, but presumable that is too expensive 58 | v.append(uuid.0); v.append(uuid.1); v.append(uuid.2); v.append(uuid.3) 59 | v.append(uuid.4); v.append(uuid.5); v.append(uuid.6); v.append(uuid.7) 60 | v.append(uuid.8); v.append(uuid.9); v.append(uuid.10); v.append(uuid.11) 61 | v.append(uuid.12); v.append(uuid.13); v.append(uuid.14); v.append(uuid.15) 62 | self.arrayValue = v 63 | } 64 | 65 | 66 | // MARK: - Parse UUID strings 67 | 68 | public init?(_ uuid: String) { 69 | var v : [UInt8] = makeArray(count: 16) 70 | let rc : Int32 = uuid.withCString { cs in return uuid_parse(cs, &v) } 71 | guard rc == 0 else { return nil } 72 | arrayValue = v 73 | } 74 | 75 | 76 | // MARK: - String Representation 77 | 78 | // TODO: Parse Strings 79 | 80 | // TODO: Use Pointer directly, avoid array 81 | 82 | public func lowercased() -> String { 83 | var cs : [Int8] = makeArray(count: 36 + 1) 84 | var uuid = arrayValue 85 | 86 | uuid_unparse_lower(&uuid, &cs) 87 | return String(cString: &cs) 88 | } 89 | 90 | public func uppercased() -> String { 91 | var cs : [Int8] = makeArray(count: 36 + 1) 92 | var uuid = arrayValue 93 | 94 | uuid_unparse_upper(&uuid, &cs) 95 | return String(cString: &cs) 96 | } 97 | 98 | public var stringValue : String { 99 | var cs : [Int8] = makeArray(count: 36 + 1) 100 | var uuid = arrayValue 101 | 102 | uuid_unparse(&uuid, &cs) 103 | return String(cString: &cs) 104 | } 105 | } 106 | 107 | extension xsys_uuid : CustomStringConvertible { 108 | 109 | public var description : String { 110 | return stringValue 111 | } 112 | 113 | } 114 | 115 | 116 | // MARK: - Equatable 117 | 118 | extension xsys_uuid : Equatable { 119 | } 120 | 121 | public func ==(lhs: xsys_uuid, rhs: xsys_uuid) -> Bool { 122 | return lhs.arrayValue == rhs.arrayValue 123 | } 124 | 125 | public func ==(lhs: uuid_t, rhs: uuid_t) -> Bool { 126 | // Weird that this isn't automatic. Any better way to do this? 127 | return lhs.0 == rhs.0 && lhs.1 == rhs.1 && lhs.2 == rhs.2 && lhs.3 == rhs.3 128 | && lhs.4 == rhs.4 && lhs.5 == rhs.5 && lhs.6 == rhs.6 && lhs.7 == rhs.7 129 | && lhs.8 == rhs.8 && lhs.9 == rhs.9 130 | && lhs.10 == rhs.10 && lhs.11 == rhs.11 131 | && lhs.12 == rhs.12 && lhs.13 == rhs.13 132 | && lhs.14 == rhs.14 && lhs.15 == rhs.15 133 | } 134 | 135 | 136 | // MARK: - Tuple Helper 137 | 138 | func uuid(fromArray v: [UInt8]) -> uuid_t { 139 | // This is a little stupid, but stick to the exposed Unix API 140 | return ( 141 | v[0], v[1], v[ 2], v[ 3], v[ 4], v[ 5], v[ 6], v[ 7], 142 | v[8], v[9], v[10], v[11], v[12], v[13], v[14], v[15] 143 | ) 144 | } 145 | 146 | #else 147 | import Glibc 148 | 149 | // TBD: uuid is not standard on Linux libc, one needs to link to (and install) 150 | // libuuid explicitly. 151 | #endif 152 | -------------------------------------------------------------------------------- /Sources/xsys/dylib.swift: -------------------------------------------------------------------------------- 1 | // 2 | // dylib.swift 3 | // Noze.io / Macro 4 | // 5 | // Created by Helge Hess on 11/04/16. 6 | // Copyright © 2016-2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | #if os(Windows) 10 | import WinSDK 11 | #elseif os(Linux) 12 | import Glibc 13 | 14 | public let dlsym = Glibc.dlsym 15 | public let dlopen = Glibc.dlopen 16 | 17 | #else 18 | import Darwin 19 | 20 | public let dlsym = Darwin.dlsym 21 | public let dlopen = Darwin.dlopen 22 | #endif 23 | -------------------------------------------------------------------------------- /Sources/xsys/fd.swift: -------------------------------------------------------------------------------- 1 | // 2 | // fd.swift 3 | // Noze.io / Macro 4 | // 5 | // Created by Helge Hess on 11/04/16. 6 | // Copyright © 2016-2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | public typealias xsysOpenType = (UnsafePointer, CInt) -> CInt 10 | 11 | #if os(Windows) 12 | import WinSDK 13 | #elseif os(Linux) 14 | import Glibc 15 | 16 | public let open : xsysOpenType = Glibc.open 17 | public let close = Glibc.close 18 | public let read = Glibc.read 19 | public let write = Glibc.write 20 | public let recvfrom = Glibc.recvfrom 21 | public let sendto = Glibc.sendto 22 | 23 | public let access = Glibc.access 24 | public let F_OK = Glibc.F_OK 25 | public let R_OK = Glibc.R_OK 26 | public let W_OK = Glibc.W_OK 27 | public let X_OK = Glibc.X_OK 28 | 29 | #if swift(>=3.2) // Swift 3.2/4 maps Glibc.stat to the struct 30 | public func stat(_ p: UnsafePointer!, 31 | _ r: UnsafeMutablePointer!) -> Int32 32 | { 33 | // FIXME: We cannot call `Darwin.stat` here since that resolves to the 34 | // `struct stat` in Swift 3.2, not the `stat` function. 35 | // A potential workaround is creating two separate files, one 36 | // doing: 37 | // import struct Darwin.stat 38 | // typealias xsys_struct_stat = Darwin.stat 39 | // and the other one doing 40 | // import func Darwin.stat 41 | // let xsys_func_stat = Darwin.stat 42 | // ... but well. 43 | return Glibc.lstat(p, r) 44 | } 45 | #else 46 | public let stat = Glibc.stat 47 | #endif 48 | public let lstat = Glibc.lstat 49 | 50 | public let opendir = Glibc.opendir 51 | public let closedir = Glibc.closedir 52 | public let readdir = Glibc.readdir 53 | 54 | public typealias dirent = Glibc.dirent 55 | public typealias stat_struct = Glibc.stat 56 | 57 | // TODO: no O_EVTONLY on Linux? 58 | public let O_EVTONLY = Glibc.O_RDONLY 59 | 60 | #else 61 | import Darwin 62 | 63 | public let open : xsysOpenType = Darwin.open 64 | public let close = Darwin.close 65 | public let read = Darwin.read 66 | public let write = Darwin.write 67 | public let recvfrom = Darwin.recvfrom 68 | public let sendto = Darwin.sendto 69 | 70 | public let access = Darwin.access 71 | public let F_OK = Darwin.F_OK 72 | public let R_OK = Darwin.R_OK 73 | public let W_OK = Darwin.W_OK 74 | public let X_OK = Darwin.X_OK 75 | 76 | #if swift(>=3.2) // Swift 3.2 maps Darwin.stat to the struct 77 | public func stat(_ p: UnsafePointer!, 78 | _ r: UnsafeMutablePointer!) -> Int32 79 | { 80 | // FIXME: We cannot call `Darwin.stat` here since that resolves to the 81 | // `struct stat` in Swift 3.2, not the `stat` function. 82 | // A potential workaround is creating two separate files, one 83 | // doing: 84 | // import struct Darwin.stat 85 | // typealias xsys_struct_stat = Darwin.stat 86 | // and the other one doing 87 | // import func Darwin.stat 88 | // let xsys_func_stat = Darwin.stat 89 | // ... but well. 90 | return Darwin.lstat(p, r) 91 | } 92 | #else 93 | public let stat = Darwin.stat 94 | #endif 95 | public let lstat = Darwin.lstat 96 | 97 | public let opendir = Darwin.opendir 98 | public let closedir = Darwin.closedir 99 | public let readdir = Darwin.readdir 100 | 101 | public typealias dirent = Darwin.dirent 102 | public typealias stat_struct = Darwin.stat 103 | 104 | public let O_EVTONLY = Darwin.O_EVTONLY 105 | 106 | #endif 107 | -------------------------------------------------------------------------------- /Sources/xsys/ioctl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ioctl.swift 3 | // Noze.io / Macro 4 | // 5 | // Created by Helge Hess on 11/04/16. 6 | // Copyright © 2016-2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | #if os(Windows) 10 | import WinSDK 11 | #elseif os(Linux) 12 | import Glibc 13 | #else 14 | import Darwin 15 | #endif 16 | // MARK: - ioctl / ioccom stuff 17 | 18 | #if os(Windows) 19 | // port me, WinSock2 20 | #elseif os(Linux) 21 | 22 | public let FIONREAD : CUnsignedLong = CUnsignedLong(Glibc.FIONREAD) 23 | 24 | public let F_SETFD = Glibc.F_SETFD 25 | public let FD_CLOEXEC = Glibc.FD_CLOEXEC 26 | 27 | #else /* os(Darwin) */ 28 | // TODO: still required? 29 | public let IOC_OUT : CUnsignedLong = 0x40000000 30 | 31 | // hh: not sure this is producing the right value 32 | public let FIONREAD : CUnsignedLong = 33 | ( IOC_OUT 34 | | ((CUnsignedLong(4 /* Int32 */) & CUnsignedLong(IOCPARM_MASK)) << 16) 35 | | (102 /* 'f' */ << 8) | 127) 36 | 37 | public let F_SETFD = Darwin.F_SETFD 38 | public let FD_CLOEXEC = Darwin.FD_CLOEXEC 39 | 40 | #endif /* os(Darwin) */ 41 | 42 | 43 | #if !os(Windows) 44 | 45 | // MARK: - Replicate C shims - BAD HACK 46 | // TODO: not required anymore? varargs work on Linux? 47 | // but not in Xcode yet? 48 | 49 | private let dlHandle = dlopen(nil, RTLD_NOW) 50 | private let fnFcntl = dlsym(dlHandle, "fcntl") 51 | private let fnIoctl = dlsym(dlHandle, "ioctl") 52 | 53 | typealias fcntlViType = 54 | @convention(c) (Int32, Int32, Int32) -> Int32 55 | typealias ioctlVipType = 56 | @convention(c) (Int32, CUnsignedLong, UnsafeMutablePointer) -> Int32 57 | 58 | public func fcntlVi(_ fildes: Int32, _ cmd: Int32, _ val: Int32) -> Int32 { 59 | // this works on Linux x64 and OSX 10.11/Intel, but obviously this depends on 60 | // the ABI and is pure luck aka Wrong 61 | let fp = unsafeBitCast(fnFcntl, to: fcntlViType.self) 62 | return fp(fildes, cmd, val) 63 | } 64 | public func ioctlVip(_ fildes: Int32, _ cmd: CUnsignedLong, 65 | _ val: UnsafeMutablePointer) -> Int32 66 | { 67 | // this works on Linux x64 and OSX 10.11/Intel, but obviously this depends on 68 | // the ABI and is pure luck aka Wrong 69 | let fp = unsafeBitCast(fnIoctl, to: ioctlVipType.self) 70 | return fp(fildes, cmd, val) 71 | } 72 | 73 | #endif // !os(Windows) 74 | -------------------------------------------------------------------------------- /Sources/xsys/misc.swift: -------------------------------------------------------------------------------- 1 | // 2 | // misc.swift 3 | // Noze.io / Macro 4 | // 5 | // Created by Helge Heß on 4/27/16. 6 | // Copyright © 2016-2021 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | // TODO: This file triggers a weird warning on Swift 3 2016-05-09: 10 | // :0: warning: will never be executed 11 | // :0: note: a call to a noreturn function 12 | 13 | #if os(Windows) 14 | import WinSDK 15 | 16 | public typealias size_t = WinSDK.size_t // TBD 17 | public let memcpy = WinSDK.memcpy 18 | public let strlen = WinSDK.strlen 19 | public let strchr = WinSDK.strchr 20 | #elseif os(Linux) 21 | import Glibc 22 | 23 | public typealias size_t = Glibc.size_t 24 | public let memcpy = Glibc.memcpy 25 | public let strlen = Glibc.strlen 26 | public let strchr = Glibc.strchr 27 | 28 | // Looks like todays Linux Swift doesn't have arc4random either. 29 | // Emulate it (badly). 30 | public func arc4random_uniform(_ v : UInt32) -> UInt32 { // sigh 31 | // FIXME: use new Swift Random stuff! 32 | return UInt32(rand() % Int32(v)) 33 | } 34 | 35 | public let kill = Glibc.kill 36 | public let chdir = Glibc.chdir 37 | public let rmdir = Glibc.rmdir 38 | public let unlink = Glibc.unlink 39 | public let mkdir = Glibc.mkdir 40 | public let getcwd = Glibc.getcwd 41 | public let getegid = Glibc.getegid 42 | public let geteuid = Glibc.geteuid 43 | public let getgid = Glibc.getgid 44 | public let getuid = Glibc.getuid 45 | public typealias pid_t = Glibc.pid_t 46 | public let posix_spawn = Glibc.posix_spawn 47 | public let posix_spawnp = Glibc.posix_spawnp 48 | public let waitpid = Glibc.waitpid 49 | 50 | public let getenv = Glibc.getenv 51 | 52 | // signals 53 | public let SIGTERM = Glibc.SIGTERM 54 | public let SIGHUP = Glibc.SIGHUP 55 | public let SIGINT = Glibc.SIGINT 56 | public let SIGQUIT = Glibc.SIGQUIT 57 | public let SIGKILL = Glibc.SIGKILL 58 | public let SIGSTOP = Glibc.SIGSTOP 59 | 60 | // stdio 61 | public let STDIN_FILENO = Glibc.STDIN_FILENO 62 | public let STDOUT_FILENO = Glibc.STDOUT_FILENO 63 | public let STDERR_FILENO = Glibc.STDERR_FILENO 64 | // public let NOFILE = Glibc.NOFILE // missing on Linux 65 | public typealias mode_t = Glibc.mode_t 66 | public let O_RDONLY = Glibc.O_RDONLY 67 | 68 | // rlimit 69 | public typealias rlimit = Glibc.rlimit 70 | public let getrlimit = Glibc.getrlimit 71 | public let RLIMIT_NOFILE = Glibc.RLIMIT_NOFILE 72 | public let _SC_OPEN_MAX = Glibc._SC_OPEN_MAX 73 | public let sysconf = Glibc.sysconf 74 | #else 75 | import Darwin 76 | 77 | public typealias size_t = Darwin.size_t 78 | public let memcpy = Darwin.memcpy 79 | public let strlen = Darwin.strlen 80 | public let strchr = Darwin.strchr 81 | public let arc4random_uniform = Darwin.arc4random_uniform 82 | 83 | public let kill = Darwin.kill 84 | public let chdir = Darwin.chdir 85 | public let rmdir = Darwin.rmdir 86 | public let unlink = Darwin.unlink 87 | public let mkdir = Darwin.mkdir 88 | public let getcwd = Darwin.getcwd 89 | public let getegid = Darwin.getegid 90 | public let geteuid = Darwin.geteuid 91 | public let getgid = Darwin.getgid 92 | public let getuid = Darwin.getuid 93 | public typealias pid_t = Darwin.pid_t 94 | public let posix_spawn = Darwin.posix_spawn 95 | public let posix_spawnp = Darwin.posix_spawnp 96 | public let waitpid = Darwin.waitpid 97 | 98 | public let getenv = Darwin.getenv 99 | 100 | // signals 101 | public let SIGTERM = Darwin.SIGTERM 102 | public let SIGHUP = Darwin.SIGHUP 103 | public let SIGINT = Darwin.SIGINT 104 | public let SIGQUIT = Darwin.SIGQUIT 105 | public let SIGKILL = Darwin.SIGKILL 106 | public let SIGSTOP = Darwin.SIGSTOP 107 | 108 | // stdio 109 | public let STDIN_FILENO = Darwin.STDIN_FILENO 110 | public let STDOUT_FILENO = Darwin.STDOUT_FILENO 111 | public let STDERR_FILENO = Darwin.STDERR_FILENO 112 | public let NOFILE = Darwin.NOFILE 113 | public typealias mode_t = Darwin.mode_t 114 | public let O_RDONLY = Darwin.O_RDONLY 115 | 116 | // rlimit 117 | public typealias rlimit = Darwin.rlimit 118 | public let getrlimit = Darwin.getrlimit 119 | public let RLIMIT_NOFILE = Darwin.RLIMIT_NOFILE 120 | public let _SC_OPEN_MAX = Darwin._SC_OPEN_MAX 121 | public let sysconf = Darwin.sysconf 122 | #endif 123 | 124 | 125 | // MARK: - noreturn funcs 126 | 127 | // Those trigger a `warning: will never be executed` even though 128 | // nothing is executed ;-) 129 | // public let abort = Darwin.abort 130 | // public let exit = Darwin.exit 131 | #if os(Windows) 132 | public func exit(_ code: Int32) -> Never { WinSDK.exit(code) } 133 | #elseif os(Linux) 134 | public func abort() -> Never { Glibc.abort() } 135 | public func exit(_ code: Int32) -> Never { Glibc.exit(code) } 136 | #else // Darwin 137 | public func abort() -> Never { Darwin.abort() } 138 | public func exit(_ code: Int32) -> Never { Darwin.exit(code) } 139 | #endif // Darwin 140 | 141 | 142 | #if !os(Windows) 143 | 144 | // MARK: - process status macros 145 | 146 | private func _WSTATUS (_ x: CInt) -> CInt { return x & 0x7F } 147 | public func WSTOPSIG (_ x: CInt) -> CInt { return x >> 8 } 148 | public func WIFEXITED(_ x: CInt) -> Bool { return _WSTATUS(x) == 0 } 149 | 150 | public func WIFSTOPPED (_ x: CInt) -> Bool { 151 | return _WSTATUS(x) == 0x7F && WSTOPSIG(x) != 0x13 152 | } 153 | 154 | public func WIFSIGNALED (_ x: CInt) -> Bool { 155 | return _WSTATUS(x) != 0x7F && _WSTATUS(x) != 0 156 | } 157 | 158 | public func WEXITSTATUS(_ x: CInt) -> CInt { return (x >> 8) & 0xFF } 159 | public func WTERMSIG (_ x: CInt) -> CInt { return _WSTATUS(x) } 160 | 161 | #endif // !os(Windows) 162 | -------------------------------------------------------------------------------- /Sources/xsys/ntohs.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ntohs.swift 3 | // Noze.io 4 | // 5 | // Created by Helge Hess on 11/04/16. 6 | // Copyright © 2016 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | // FIXME: we now have .littleEndian, .bigEndian 10 | 11 | public func ntohs(_ value: CUnsignedShort) -> CUnsignedShort { 12 | // hm, htons is not a func in OSX and the macro is not mapped 13 | return (value << 8) + (value >> 8); 14 | } 15 | public let htons = ntohs // same thing, swap bytes :-) 16 | -------------------------------------------------------------------------------- /Sources/xsys/sockaddr_any.swift: -------------------------------------------------------------------------------- 1 | // 2 | // sockaddr_any.swift 3 | // Noze.io / Macro 4 | // 5 | // Created by Helge Hess on 12/04/16. 6 | // Copyright © 2016-2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | #if os(Windows) 10 | import WinSDK 11 | 12 | // TODO: port via WinSock2 13 | #elseif os(Linux) 14 | import Glibc 15 | #else 16 | import Darwin 17 | #endif 18 | 19 | // Note: This cannot conform to SocketAddress because it doesn't have a static 20 | // domain. 21 | public enum sockaddr_any { 22 | 23 | #if !os(Windows) // TODO: port me using WinSock2 24 | case AF_INET (sockaddr_in) 25 | case AF_INET6(sockaddr_in6) 26 | case AF_LOCAL(sockaddr_un) 27 | 28 | public var domain: Int32 { 29 | switch self { 30 | case .AF_INET: return xsys.AF_INET 31 | case .AF_INET6: return xsys.AF_INET6 32 | case .AF_LOCAL: return xsys.AF_LOCAL 33 | } 34 | } 35 | 36 | public var len: __uint8_t { 37 | #if os(Linux) 38 | switch self { 39 | case .AF_INET: return __uint8_t(MemoryLayout.stride) 40 | case .AF_INET6: return __uint8_t(MemoryLayout.stride) 41 | case .AF_LOCAL: 42 | // TODO: just abort for now? 43 | return __uint8_t(MemoryLayout.stride) // TODO:wrong 44 | } 45 | #else 46 | switch self { 47 | case .AF_INET (let addr): return addr.sin_len 48 | case .AF_INET6(let addr): return addr.sin6_len 49 | case .AF_LOCAL(let addr): return addr.sun_len 50 | } 51 | #endif 52 | } 53 | 54 | public var port : Int? { 55 | get { 56 | switch self { 57 | case .AF_INET (let addr): return Int(ntohs(addr.sin_port)) 58 | case .AF_INET6(let addr): return Int(ntohs(addr.sin6_port)) 59 | case .AF_LOCAL: return nil 60 | } 61 | } 62 | set { 63 | let lPort = port != nil ? htons(CUnsignedShort(newValue!)) : 0 64 | switch self { 65 | case .AF_INET (var addr): addr.sin_port = lPort; self = .AF_INET(addr) 66 | case .AF_INET6(var addr): addr.sin6_port = lPort; self = .AF_INET6(addr) 67 | case .AF_LOCAL: break 68 | } 69 | } 70 | } 71 | 72 | 73 | // initializers (can this be done in a better way?) 74 | 75 | public init(_ address: sockaddr_in) { 76 | self = .AF_INET(address) 77 | } 78 | public init(_ address: sockaddr_in6) { 79 | self = .AF_INET6(address) 80 | } 81 | public init(_ address: sockaddr_un) { 82 | self = .AF_LOCAL(address) 83 | } 84 | 85 | public init?(_ address: T?) { 86 | guard let address = address else { return nil } 87 | 88 | // a little hacky ... 89 | switch T.domain { 90 | case xsys.AF_INET: 91 | let lAddress = unsafeBitCast(address, to: xsys_sockaddr_in.self) 92 | self = .AF_INET(lAddress) 93 | 94 | case xsys.AF_INET6: 95 | let lAddress = unsafeBitCast(address, to: xsys_sockaddr_in6.self) 96 | self = .AF_INET6(lAddress) 97 | 98 | case xsys.AF_LOCAL: // TODO: this is likely wrong too (variable length!) 99 | let lAddress = unsafeBitCast(address, to: xsys_sockaddr_un.self) 100 | self = .AF_LOCAL(lAddress) 101 | 102 | default: 103 | print("Unexpected socket address: \(address)") 104 | return nil 105 | } 106 | } 107 | 108 | 109 | // TODO: how to implement this? Is it even possible? (is the associated value 110 | // memory-stable, or do we get a local copy?) 111 | // public var genericPointer : UnsafePointer { .. } 112 | #endif // !os(Windows) 113 | } 114 | 115 | #if !os(Windows) // TODO: port me using WinSock2 116 | extension sockaddr_any: CustomStringConvertible { 117 | 118 | public var description: String { 119 | // description is added to the addresses in SocketAddress.swift 120 | switch self { 121 | case .AF_INET (let addr): return "\(addr)" 122 | case .AF_INET6(let addr): return "\(addr)" 123 | case .AF_LOCAL(let addr): return "\(addr)" 124 | } 125 | } 126 | } 127 | #endif // !os(Windows) 128 | -------------------------------------------------------------------------------- /Sources/xsys/socket.swift: -------------------------------------------------------------------------------- 1 | // 2 | // socket.swift 3 | // Noze.io / Macro 4 | // 5 | // Created by Helge Hess on 11/04/16. 6 | // Copyright © 2016-2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | #if os(Windows) 10 | import WinSDK 11 | 12 | // TODO: port me using WinSock2 13 | #elseif os(Linux) 14 | import Glibc 15 | 16 | public let socket = Glibc.socket 17 | public let poll = Glibc.poll 18 | public let bind = Glibc.bind 19 | public let connect = Glibc.connect 20 | public let listen = Glibc.listen 21 | public let accept = Glibc.accept 22 | public let shutdown = Glibc.shutdown 23 | 24 | public let getsockname = Glibc.getsockname 25 | public let getpeername = Glibc.getpeername 26 | 27 | public let setsockopt = Glibc.setsockopt 28 | 29 | public let getaddrinfo = Glibc.getaddrinfo 30 | public let freeaddrinfo = Glibc.freeaddrinfo 31 | 32 | public let SOCK_STREAM : Int32 = Int32(Glibc.SOCK_STREAM.rawValue) 33 | public let SOCK_DGRAM : Int32 = Int32(Glibc.SOCK_DGRAM.rawValue) 34 | public let SHUT_RD : Int32 = Int32(Glibc.SHUT_RD) 35 | public let SHUT_WR : Int32 = Int32(Glibc.SHUT_WR) 36 | 37 | public typealias sa_family_t = Glibc.sa_family_t 38 | public let AF_UNSPEC = Glibc.AF_UNSPEC 39 | public let AF_INET = Glibc.AF_INET 40 | public let AF_INET6 = Glibc.AF_INET6 41 | public let AF_LOCAL = Glibc.AF_LOCAL 42 | public let IPPROTO_TCP = Glibc.IPPROTO_TCP 43 | public let PF_UNSPEC = Glibc.PF_UNSPEC 44 | public let SOL_SOCKET = Glibc.SOL_SOCKET 45 | public let SO_REUSEADDR = Glibc.SO_REUSEADDR 46 | public let SO_REUSEPORT = Glibc.SO_REUSEPORT 47 | 48 | // using an exact alias gives issues with sizeof() 49 | public typealias xsys_sockaddr = Glibc.sockaddr 50 | public typealias xsys_sockaddr_in = Glibc.sockaddr_in 51 | public typealias xsys_sockaddr_in6 = Glibc.sockaddr_in6 52 | public typealias xsys_sockaddr_un = Glibc.sockaddr_un 53 | 54 | public typealias addrinfo = Glibc.addrinfo 55 | public typealias socklen_t = Glibc.socklen_t 56 | #else 57 | import Darwin 58 | 59 | public let socket = Darwin.socket 60 | public let poll = Darwin.poll 61 | public let bind = Darwin.bind 62 | public let connect = Darwin.connect 63 | public let listen = Darwin.listen 64 | public let accept = Darwin.accept 65 | public let shutdown = Darwin.shutdown 66 | 67 | public let getsockname = Darwin.getsockname 68 | public let getpeername = Darwin.getpeername 69 | 70 | public let setsockopt = Darwin.setsockopt 71 | 72 | public let getaddrinfo = Darwin.getaddrinfo 73 | public let freeaddrinfo = Darwin.freeaddrinfo 74 | 75 | public let SOCK_STREAM = Darwin.SOCK_STREAM 76 | public let SOCK_DGRAM = Darwin.SOCK_DGRAM 77 | public let SHUT_RD = Darwin.SHUT_RD 78 | public let SHUT_WR = Darwin.SHUT_WR 79 | 80 | public typealias sa_family_t = Darwin.sa_family_t 81 | public let AF_UNSPEC = Darwin.AF_UNSPEC 82 | public let AF_INET = Darwin.AF_INET 83 | public let AF_INET6 = Darwin.AF_INET6 84 | public let AF_LOCAL = Darwin.AF_LOCAL 85 | public let IPPROTO_TCP = Darwin.IPPROTO_TCP 86 | public let PF_UNSPEC = Darwin.PF_UNSPEC 87 | public let SOL_SOCKET = Darwin.SOL_SOCKET 88 | public let SO_REUSEADDR = Darwin.SO_REUSEADDR 89 | public let SO_REUSEPORT = Darwin.SO_REUSEPORT 90 | 91 | // using an exact alias gives issues with sizeof() 92 | public typealias xsys_sockaddr = Darwin.sockaddr 93 | public typealias xsys_sockaddr_in = Darwin.sockaddr_in 94 | public typealias xsys_sockaddr_in6 = Darwin.sockaddr_in6 95 | public typealias xsys_sockaddr_un = Darwin.sockaddr_un 96 | 97 | public typealias addrinfo = Darwin.addrinfo 98 | public typealias socklen_t = Darwin.socklen_t 99 | #endif 100 | -------------------------------------------------------------------------------- /Sources/xsys/time.swift: -------------------------------------------------------------------------------- 1 | // 2 | // time.swift 3 | // Noze.io / Macro 4 | // 5 | // Created by Helge Hess on 19/05/16. 6 | // Copyright © 2016-2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | #if os(Windows) 10 | import WinSDK 11 | #elseif os(Linux) 12 | import Glibc 13 | 14 | public typealias struct_tm = Glibc.tm 15 | public typealias time_t = Glibc.time_t 16 | 17 | public let time = Glibc.time 18 | public let gmtime_r = Glibc.gmtime_r 19 | public let localtime_r = Glibc.localtime_r 20 | public let strftime = Glibc.strftime 21 | 22 | #else 23 | import Darwin 24 | 25 | public typealias struct_tm = Darwin.tm 26 | public typealias time_t = Darwin.time_t 27 | 28 | public let time = Darwin.time 29 | public let gmtime_r = Darwin.gmtime_r 30 | public let localtime_r = Darwin.localtime_r 31 | public let strftime = Darwin.strftime 32 | #endif 33 | 34 | #if !os(Windows) 35 | 36 | // MARK: - Time Helpers 37 | 38 | /// Unix timestamp. `time_t` has the Y2038 issue and its granularity is limited 39 | /// to seconds. 40 | /// Unix timestamps are counted in seconds starting Jan 1st 1970 00:00:00, UTC. 41 | public extension time_t { 42 | 43 | /// Returns the current time. 44 | static var now : time_t { return xsys.time(nil) } 45 | 46 | /// Initialize the `time_t` value from Unix `tm` value (date components). 47 | /// Assumes the values are given in *local time*. 48 | /// Remember that the `time_t` itself is in UTC. 49 | init(_ tm: xsys.struct_tm) { 50 | self = tm.localTime 51 | } 52 | /// Initialize the `time_t` value from Unix `tm` value (date components). 53 | /// Assumes the values are given in *UTC time*. 54 | /// Remember that the `time_t` itself is in UTC. 55 | init(utc tm: xsys.struct_tm) { 56 | self = tm.utcTime 57 | } 58 | 59 | /// Converts the `time_t` timestamp into date components (`tz` struct) living 60 | /// in the UTC timezone. 61 | /// Remember that the `time_t` itself is in UTC. 62 | var componentsInUTC : xsys.struct_tm { 63 | var t = self 64 | var tm = xsys.struct_tm() 65 | _ = xsys.gmtime_r(&t, &tm) 66 | return tm 67 | } 68 | 69 | /// Converts the `time_t` timestamp into date components (`tz` struct) living 70 | /// in the local timezone of the Unix environment. 71 | /// Remember that the `time_t` itself is in UTC. 72 | var componentsInLocalTime : xsys.struct_tm { 73 | var t = self 74 | var tm = xsys.struct_tm() 75 | _ = xsys.localtime_r(&t, &tm) 76 | return tm 77 | } 78 | 79 | /// Example `strftime` format: 80 | /// "%a, %d %b %Y %H:%M:%S GMT" 81 | /// 82 | /// This function converts the timestamp into UTC time components to format 83 | /// the value. 84 | /// 85 | /// Example call: 86 | /// 87 | /// xsys.time(nil).format("%a, %d %b %Y %H:%M:%S %Z") 88 | /// 89 | func format(_ sf: String) -> String { 90 | return self.componentsInUTC.format(sf) 91 | } 92 | } 93 | 94 | /// The Unix `tm` struct is essentially NSDateComponents PLUS some timezone 95 | /// information (isDST, offset, tz abbrev name). 96 | public extension xsys.struct_tm { 97 | 98 | /// Create a Unix date components structure from a timestamp. This variant 99 | /// creates components in the local timezone. 100 | init(_ tm: time_t) { 101 | self = tm.componentsInLocalTime 102 | } 103 | 104 | /// Create a Unix date components structure from a timestamp. This variant 105 | /// creates components in the UTC timezone. 106 | init(utc tm: time_t) { 107 | self = tm.componentsInUTC 108 | } 109 | 110 | var utcTime : time_t { 111 | var tm = self 112 | return timegm(&tm) 113 | } 114 | var localTime : time_t { 115 | var tm = self 116 | return mktime(&tm) 117 | } 118 | 119 | /// Example `strftime` format (`man strftime`): 120 | /// "%a, %d %b %Y %H:%M:%S GMT" 121 | /// 122 | func format(_ sf: String, defaultCapacity: Int = 100) -> String { 123 | var tm = self 124 | 125 | // Yes, yes, I know. 126 | let attempt1Capacity = defaultCapacity 127 | let attempt2Capacity = defaultCapacity > 1024 ? defaultCapacity * 2 : 1024 128 | var capacity = attempt1Capacity 129 | 130 | var buf = UnsafeMutablePointer.allocate(capacity: capacity) 131 | #if swift(>=4.1) 132 | defer { buf.deallocate() } 133 | #else 134 | defer { buf.deallocate(capacity: capacity) } 135 | #endif 136 | 137 | let rc = xsys.strftime(buf, capacity, sf, &tm) 138 | 139 | if rc == 0 { 140 | #if swift(>=4.1) 141 | buf.deallocate() 142 | #else 143 | buf.deallocate(capacity: capacity) 144 | #endif 145 | capacity = attempt2Capacity 146 | buf = UnsafeMutablePointer.allocate(capacity: capacity) 147 | 148 | let rc = xsys.strftime(buf, capacity, sf, &tm) 149 | assert(rc != 0) 150 | guard rc != 0 else { return "" } 151 | } 152 | 153 | return String(cString: buf); 154 | } 155 | } 156 | 157 | #endif // !os(Windows) 158 | -------------------------------------------------------------------------------- /Sources/xsys/timespec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // timespec.swift 3 | // Noze.io / Macro 4 | // 5 | // Created by Helge Hess on 31/05/16. 6 | // Copyright © 2016-2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | #if os(Windows) 10 | import WinSDK 11 | #elseif os(Linux) 12 | import Glibc 13 | 14 | public typealias timespec = Glibc.timespec 15 | public typealias timeval = Glibc.timeval 16 | 17 | public extension timespec { 18 | 19 | static func monotonic() -> timespec { 20 | var ts = timespec() 21 | clock_gettime(CLOCK_MONOTONIC, &ts) 22 | return ts 23 | } 24 | 25 | } 26 | #else // Darwin 27 | import Darwin 28 | 29 | public typealias timespec = Darwin.timespec 30 | public typealias timeval = Darwin.timeval 31 | 32 | public extension timespec { 33 | 34 | init(_ mts: mach_timespec_t) { 35 | #if swift(>=4.1) 36 | self.init() 37 | #endif 38 | tv_sec = __darwin_time_t(mts.tv_sec) 39 | tv_nsec = Int(mts.tv_nsec) 40 | } 41 | 42 | static func monotonic() -> timespec { 43 | var cclock = clock_serv_t() 44 | var mts = mach_timespec_t() 45 | 46 | host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock); 47 | clock_get_time(cclock, &mts); 48 | mach_port_deallocate(mach_task_self_, cclock); 49 | 50 | return timespec(mts) 51 | } 52 | } 53 | #endif // Darwin 54 | 55 | #if !os(Windows) 56 | 57 | public func -(left: timespec, right: timespec) -> timespec { 58 | var result = timespec() 59 | 60 | if (left.tv_nsec - right.tv_nsec) < 0 { 61 | result.tv_sec = left.tv_sec - right.tv_sec - 1 62 | result.tv_nsec = 1000000000 + left.tv_nsec - right.tv_nsec 63 | } 64 | else { 65 | result.tv_sec = left.tv_sec - right.tv_sec 66 | result.tv_nsec = left.tv_nsec - right.tv_nsec 67 | } 68 | 69 | return result 70 | } 71 | 72 | public func -(left: timeval, right: timeval) -> timeval { 73 | var result = timeval() 74 | 75 | if (left.tv_usec - right.tv_usec) < 0 { 76 | result.tv_sec = left.tv_sec - right.tv_sec - 1 77 | result.tv_usec = 1000000 + left.tv_usec - right.tv_usec 78 | } 79 | else { 80 | result.tv_sec = left.tv_sec - right.tv_sec 81 | result.tv_usec = left.tv_usec - right.tv_usec 82 | } 83 | 84 | return result 85 | } 86 | 87 | extension timespec { 88 | public var description : String { 89 | switch ( tv_sec, tv_nsec ) { 90 | case ( 0, 0 ): return "timespec()" 91 | case ( _, 0 ): return "timespec(\(tv_sec)s)" 92 | case ( 0, _ ): return "timespec(\(tv_nsec)ns)" 93 | default: return "timespec(\(tv_sec)s, \(tv_nsec)ns)" 94 | } 95 | } 96 | } 97 | 98 | extension timeval { 99 | public var description : String { 100 | switch ( tv_sec, tv_usec ) { 101 | case ( 0, 0 ): return "timeval()" 102 | case ( _, 0 ): return "timeval(\(tv_sec)s)" 103 | case ( 0, _ ): return "timeval(\(tv_usec)micro)" 104 | default: return "timeval(\(tv_sec)s, \(tv_usec)micro)" 105 | } 106 | } 107 | } 108 | 109 | #if compiler(>=6) 110 | extension timespec : @retroactive CustomStringConvertible {} 111 | extension timeval : @retroactive CustomStringConvertible {} 112 | #else 113 | extension timespec : CustomStringConvertible {} 114 | extension timeval : CustomStringConvertible {} 115 | #endif 116 | 117 | #endif // !os(Windows) 118 | -------------------------------------------------------------------------------- /Sources/xsys/timeval_any.swift: -------------------------------------------------------------------------------- 1 | // 2 | // timeval_any.swift 3 | // Noze.io / Macro 4 | // 5 | // Created by Helge Hess on 21/07/16. 6 | // Copyright © 2016-2020 ZeeZide GmbH. All rights reserved. 7 | // 8 | 9 | #if os(Windows) 10 | import WinSDK 11 | #elseif os(Linux) 12 | import func Glibc.gettimeofday 13 | #else 14 | import func Darwin.gettimeofday 15 | #endif 16 | 17 | 18 | /// Stuff common to any of the three(?) Unix time value structures: 19 | /// - timeval_t (sec/microsec granularity) 20 | /// - timespec_t (sec/nanosec granularity) 21 | /// - time_t (sec granularity) 22 | /// 23 | /// The values in this protocol are not components (as they are stored in tv_ 24 | /// like struct fields), but they overflow. 25 | /// E.g. 26 | /// 27 | /// timeval(seconds: 10, milliseconds: 2000) 28 | /// 29 | /// Will create a value of 12 seconds. 30 | /// 31 | public protocol timeval_any { 32 | 33 | static var now : Self { get } 34 | 35 | init() 36 | init(seconds: Int, milliseconds: Int) 37 | 38 | var seconds : Int { get } 39 | var milliseconds : Int { get } 40 | 41 | #if !os(Windows) 42 | var componentsInUTC : xsys.struct_tm { get } 43 | var componentsInLocalTime : xsys.struct_tm { get } 44 | #endif 45 | 46 | static func -(left: Self, right: Self) -> Self 47 | } 48 | 49 | #if !os(Windows) 50 | public extension timeval_any { 51 | 52 | var componentsInUTC : xsys.struct_tm { 53 | return time_t(seconds).componentsInUTC 54 | } 55 | var componentsInLocalTime : xsys.struct_tm { 56 | return time_t(seconds).componentsInLocalTime 57 | } 58 | } 59 | 60 | 61 | extension time_t : timeval_any { 62 | 63 | public init(seconds: Int, milliseconds: Int = 0) { 64 | assert(milliseconds == 0) // just print a warning, the user should know 65 | self = seconds 66 | } 67 | 68 | public var seconds : Int { return self } 69 | public var milliseconds : Int { return self * 1000 } 70 | } 71 | 72 | 73 | extension timeval : timeval_any { 74 | 75 | public static var now : timeval { 76 | var now = timeval() 77 | _ = gettimeofday(&now, nil) 78 | return now 79 | } 80 | 81 | public init(_ ts: timespec) { 82 | #if swift(>=4.1) 83 | self.init() 84 | #endif 85 | tv_sec = ts.seconds 86 | #if os(Linux) 87 | tv_usec = ts.tv_nsec / 1000 88 | #else 89 | tv_usec = Int32(ts.tv_nsec / 1000) 90 | #endif 91 | } 92 | 93 | public init(seconds: Int, milliseconds: Int = 0) { 94 | #if swift(>=4.1) 95 | self.init() 96 | #endif 97 | tv_sec = seconds + (milliseconds / 1000) 98 | #if os(Linux) 99 | tv_usec = (milliseconds % 1000) * 1000 100 | #else 101 | tv_usec = Int32(milliseconds % 1000) * 1000 102 | #endif 103 | } 104 | 105 | public var seconds : Int { 106 | // TBD: rounding on tv_usec? 107 | return Int(tv_sec) 108 | } 109 | 110 | public var milliseconds : Int { 111 | return (tv_sec * 1000) + (Int(tv_usec) / 1000) 112 | } 113 | 114 | } 115 | 116 | extension timespec : timeval_any { 117 | 118 | public static var now : timespec { return timespec(timeval.now) } 119 | 120 | public init(_ tv: timeval) { 121 | #if swift(>=4.1) 122 | self.init() 123 | #endif 124 | tv_sec = tv.seconds 125 | tv_nsec = Int(tv.tv_usec) * 1000 126 | } 127 | 128 | public init(seconds: Int, milliseconds: Int = 0) { 129 | #if swift(>=4.1) 130 | self.init() 131 | #endif 132 | tv_sec = seconds + (milliseconds / 1000) 133 | tv_nsec = (milliseconds % 1000) * 1000000 134 | } 135 | 136 | public var seconds : Int { 137 | // TBD: rounding on tv_nsec? 138 | return tv_sec 139 | } 140 | 141 | public var milliseconds : Int { 142 | return (tv_sec * 1000) + (tv_nsec / 1000000) 143 | } 144 | 145 | } 146 | #endif // !os(Windows) 147 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #error("Swift 5.5 requires --enable-test-discovery") 4 | -------------------------------------------------------------------------------- /Tests/MacroTests/AgentTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import http 3 | @testable import Macro 4 | @testable import MacroTestUtilities 5 | 6 | final class AgentTests: XCTestCase { 7 | 8 | let runsInCI = env["CI"] == "true" 9 | 10 | override class func setUp() { 11 | disableAtExitHandler() 12 | super.setUp() 13 | } 14 | 15 | func testSimpleGet() { 16 | let exp = expectation(description: "get result") 17 | 18 | http.get("https://zeezide.de") { res in 19 | XCTAssertEqual(res.statusCode, 200, "Status code is not 200!") 20 | 21 | res.onError { error in 22 | XCTAssert(false, "an error happened: \(error)") 23 | } 24 | 25 | res | concat { buffer in 26 | do { 27 | let s = try buffer.toString() 28 | XCTAssert(s.contains("") 95 | } 96 | 97 | do { 98 | let buf = Buffer.from([ UInt8 ](repeating: 0x42, count: 100)) 99 | let s = buf.description 100 | XCTAssert(s.hasPrefix("")) 103 | XCTAssert(s.count < 200) 104 | } 105 | } 106 | 107 | static var allTests = [ 108 | ( "testIndexOf" , testIndexOf ), 109 | ( "testLastIndexOf" , testLastIndexOf ), 110 | ( "testSlice" , testSlice ), 111 | ( "testJSON" , testJSON ), 112 | ( "testDescription" , testDescription ) 113 | ] 114 | } 115 | -------------------------------------------------------------------------------- /Tests/MacroTests/ByteBufferTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import MacroCore 3 | import NIO 4 | 5 | final class ByteBufferTests: XCTestCase { 6 | 7 | override class func setUp() { 8 | disableAtExitHandler() 9 | super.setUp() 10 | } 11 | 12 | func testByteBufferAssumptions() { 13 | var bb = ByteBuffer() 14 | XCTAssertEqual(bb.readerIndex , 0) 15 | XCTAssertEqual(bb.readableBytes , 0) 16 | XCTAssertEqual(bb.writerIndex , 0) 17 | 18 | bb.writeBytes([ 10, 20, 30, 40, 50, 60 ]) 19 | XCTAssertEqual(bb.readerIndex , 0) 20 | XCTAssertEqual(bb.readableBytes , 6) 21 | XCTAssertEqual(bb.writerIndex , 6) 22 | 23 | XCTAssertEqual(bb.readableBytesView.count , 6) 24 | XCTAssertEqual(bb.readableBytesView.startIndex , 0) 25 | 26 | let b0 = bb.readBytes(length: 1) ?? [] 27 | XCTAssertEqual(b0.count , 1) 28 | XCTAssertEqual(b0.first , 10) 29 | XCTAssertEqual(bb.readerIndex , 1) 30 | XCTAssertEqual(bb.readableBytes , 5) 31 | XCTAssertEqual(bb.writerIndex , 6) 32 | 33 | XCTAssertEqual(bb.readableBytesView.count , 5) 34 | XCTAssertEqual(bb.readableBytesView.startIndex , 1) 35 | } 36 | 37 | static var allTests = [ 38 | ( "testByteBufferAssumptions" , testByteBufferAssumptions ) 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /Tests/MacroTests/CollectionTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import MacroCore 3 | import struct NIO.ByteBuffer 4 | 5 | final class CollectionTests: XCTestCase { 6 | 7 | override class func setUp() { 8 | disableAtExitHandler() 9 | super.setUp() 10 | } 11 | 12 | func testByteBufferSearch() { 13 | var bb = ByteBuffer() 14 | bb.writeBytes([ 10, 20, 30, 30, 40, 50, 60 ]) 15 | 16 | let idxMaybe = bb.readableBytesView.firstIndex(of: [ 30, 40 ]) 17 | XCTAssertNotNil(idxMaybe) 18 | guard let idx = idxMaybe else { return } 19 | XCTAssertEqual(idx, 3) 20 | } 21 | 22 | func testByteBufferSearchNoMatch() { 23 | var bb = ByteBuffer() 24 | bb.writeBytes([ 10, 20, 30, 30, 40, 50, 60 ]) 25 | 26 | let idxMaybe = bb.readableBytesView.firstIndex(of: [ 30, 50 ]) 27 | XCTAssertNil(idxMaybe) 28 | } 29 | 30 | func testByteBufferSearchEmpty() { 31 | let bb = ByteBuffer() 32 | let idxMaybe = bb.readableBytesView.firstIndex(of: [ 30, 50 ]) 33 | XCTAssertNil(idxMaybe) 34 | } 35 | 36 | func testByteBufferSearchMatchEmpty() { 37 | var bb = ByteBuffer() 38 | bb.writeBytes([ 10, 20, 30, 30, 40, 50, 60 ]) 39 | let idxMaybe = bb.readableBytesView.firstIndex(of: []) 40 | XCTAssertNotNil(idxMaybe) 41 | guard let idx = idxMaybe else { return } 42 | XCTAssertEqual(idx, 0) 43 | } 44 | 45 | func testByteBufferSearchLongerMatch() { 46 | var bb = ByteBuffer() 47 | bb.writeBytes([ 30, 50 ]) 48 | 49 | let idxMaybe = bb.readableBytesView.firstIndex(of: [ 10, 20, 30, 30, 40 ]) 50 | XCTAssertNil(idxMaybe) 51 | } 52 | 53 | func testByteBufferSearchStableIndices() { 54 | var bb = ByteBuffer() 55 | bb.writeBytes([ 10, 20, 30, 30, 40, 50, 60 ]) 56 | 57 | let idxMaybe = bb.readableBytesView.firstIndex(of: [ 30, 40 ]) 58 | XCTAssertNotNil(idxMaybe) 59 | guard let idx = idxMaybe else { return } 60 | XCTAssertEqual(idx, 3) 61 | 62 | _ = bb.readBytes(length: 3) 63 | let idxMaybe2 = bb.readableBytesView.firstIndex(of: [ 30, 40 ]) 64 | XCTAssertNotNil(idxMaybe2) 65 | guard let idx2 = idxMaybe2 else { return } 66 | XCTAssertEqual(idx2, 3) 67 | } 68 | 69 | func testByteBufferSearchLeftEdge() { 70 | var bb = ByteBuffer() 71 | bb.writeBytes([ 30, 40, 50, 60, 10, 20, 30 ]) 72 | 73 | let idxMaybe = bb.readableBytesView.firstIndex(of: [ 30, 40 ]) 74 | XCTAssertNotNil(idxMaybe) 75 | guard let idx = idxMaybe else { return } 76 | XCTAssertEqual(idx, 0) 77 | } 78 | 79 | func testByteBufferSearchRightEdge() { 80 | var bb = ByteBuffer() 81 | bb.writeBytes([ 10, 20, 30, 50, 60, 30, 40 ]) 82 | 83 | let idxMaybe = bb.readableBytesView.firstIndex(of: [ 30, 40 ]) 84 | XCTAssertNotNil(idxMaybe) 85 | guard let idx = idxMaybe else { return } 86 | XCTAssertEqual(idx, 5) 87 | } 88 | 89 | func testByteBufferRemainingMatch() { 90 | do { 91 | var bb = ByteBuffer() 92 | bb.writeBytes([ 10, 20, 30, 50, 60, 30, 40 ]) 93 | // ^^ ^^ remaining match 94 | 95 | let idxMaybe = bb.readableBytesView 96 | .firstIndex(of: [ 30, 40, 50, 50 ], options: .partialSuffixMatch) 97 | XCTAssertNotNil(idxMaybe) 98 | guard let idx = idxMaybe else { return } 99 | XCTAssertEqual(idx, 5) 100 | } 101 | do { 102 | var bb = ByteBuffer() 103 | bb.writeBytes([ 30, 40, 50, 40, 60, 30, 40 ]) 104 | 105 | let idxMaybe = bb.readableBytesView 106 | .firstIndex(of: [ 30, 40, 50, 50 ], options: .partialSuffixMatch) 107 | XCTAssertNotNil(idxMaybe) 108 | guard let idx = idxMaybe else { return } 109 | XCTAssertEqual(idx, 5) 110 | } 111 | } 112 | 113 | func testByteBufferSingleItemRemainingMatch() { 114 | var bb = ByteBuffer() 115 | bb.writeBytes([ 45 ]) 116 | 117 | let idxMaybe = bb.readableBytesView 118 | .firstIndex(of: [ 45, 45, 50, 50 ], options: .partialSuffixMatch) 119 | XCTAssertNotNil(idxMaybe) 120 | guard let idx = idxMaybe else { return } 121 | XCTAssertEqual(idx, 0) 122 | } 123 | 124 | func testByteBufferRemainingMatchPerformance() { 125 | let needle : [ UInt8 ] = [ 30, 50, 60, 42, 22, 13, 37, 98, 12 ] 126 | 127 | var bb = ByteBuffer(repeating: 0, count: 32 * 1024) 128 | bb.writeBytes(needle.dropLast(2)) 129 | bb.writeBytes(ByteBuffer(repeating: 0, count: 32 * 1024).readableBytesView) 130 | assert(bb.readableBytes > 64 * 1024) 131 | 132 | let start = Date() 133 | measure { 134 | for _ in 0..<10 { 135 | let idxMaybe = bb.readableBytesView 136 | .firstIndex(of: needle, options: .partialSuffixMatch) 137 | XCTAssertNil(idxMaybe) 138 | } 139 | } 140 | print("TOOK:", -start.timeIntervalSinceNow) 141 | } 142 | 143 | static var allTests = [ 144 | ( "testByteBufferSearch" , testByteBufferSearch ), 145 | ( "testByteBufferSearchNoMatch" , testByteBufferSearchNoMatch ), 146 | ( "testByteBufferSearchEmpty" , testByteBufferSearchEmpty ), 147 | ( "testByteBufferSearchMatchEmpty" , testByteBufferSearchMatchEmpty ), 148 | ( "testByteBufferSearchLongerMatch" , testByteBufferSearchLongerMatch ), 149 | ( "testByteBufferSearchStableIndices" , testByteBufferSearchStableIndices ), 150 | ( "testByteBufferSearchLeftEdge" , testByteBufferSearchLeftEdge ), 151 | ( "testByteBufferSearchRightEdge" , testByteBufferSearchRightEdge ), 152 | ( "testByteBufferRemainingMatch" , testByteBufferRemainingMatch ), 153 | ( "testByteBufferSingleItemRemainingMatch", 154 | testByteBufferSingleItemRemainingMatch ), 155 | ( "testByteBufferRemainingMatchPerformance", 156 | testByteBufferRemainingMatchPerformance ) 157 | ] 158 | } 159 | -------------------------------------------------------------------------------- /Tests/MacroTests/MacroTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Macro 3 | @testable import MacroTestUtilities 4 | 5 | final class MacroBaseTests: XCTestCase { 6 | 7 | override class func setUp() { 8 | disableAtExitHandler() 9 | super.setUp() 10 | } 11 | 12 | func testDirname() throws { 13 | XCTAssertEqual(path.dirname("/usr/local/bin"), "/usr/local") 14 | } 15 | func testBasename() throws { 16 | XCTAssertEqual(path.basename("/usr/local/bin"), "bin") 17 | } 18 | 19 | func testTestResponse() throws { 20 | let res = TestServerResponse() 21 | XCTAssertFalse(res.writableEnded) 22 | 23 | res.writeHead(200, "OK") 24 | XCTAssertFalse(res.writableEnded) 25 | XCTAssertEqual(res.statusCode, 200) 26 | XCTAssertTrue(res.writtenContent.isEmpty) 27 | 28 | res.write("Hello World") 29 | XCTAssertFalse(res.writableEnded) 30 | XCTAssertEqual(try res.writtenContent.toString(), "Hello World") 31 | 32 | res.end() 33 | XCTAssertTrue(res.writableEnded) 34 | 35 | XCTAssertEqual(res.statusCode, 200) 36 | XCTAssertEqual(try res.writtenContent.toString(), "Hello World") 37 | } 38 | 39 | func testResponseCorking() throws { 40 | let res = http.ServerResponse(unsafeChannel: nil, log: MacroTestLogger) 41 | XCTAssertFalse(res.writableEnded) 42 | 43 | res.cork() 44 | 45 | res.writeHead(200, "OK") 46 | XCTAssertFalse(res.writableEnded) 47 | XCTAssertEqual(res.statusCode, 200) 48 | XCTAssertTrue(res.writableBuffer?.isEmpty ?? true) 49 | 50 | res.write("Hello World") 51 | XCTAssertFalse(res.writableEnded) 52 | XCTAssertEqual(try res.writableBuffer?.toString() ?? "", "Hello World") 53 | 54 | res.end() 55 | XCTAssertTrue(res.writableEnded) 56 | 57 | XCTAssertEqual(res.statusCode, 200) 58 | XCTAssertEqual(try res.writableBuffer?.toString() ?? "", "Hello World") 59 | } 60 | 61 | static var allTests = [ 62 | ( "testDirname" , testDirname ), 63 | ( "testBasename" , testBasename ), 64 | ( "testTestResponse" , testTestResponse ), 65 | ( "testResponseCorking" , testResponseCorking ), 66 | ] 67 | } 68 | -------------------------------------------------------------------------------- /Tests/MacroTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [ XCTestCaseEntry ] { 5 | return [ 6 | testCase(BufferTests .allTests), 7 | testCase(ByteBufferTests.allTests), 8 | testCase(CollectionTests.allTests), 9 | testCase(MacroBaseTests .allTests), 10 | testCase(AgentTests .allTests) 11 | ] 12 | } 13 | #endif 14 | --------------------------------------------------------------------------------