├── .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 = "" }
158 |
159 | let count = writableBuffer.count
160 | if writableCorked {
161 | if count > 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 = "" }
161 |
162 | ms += " \(statusCode)"
163 | if writableEnded { ms += " ended" }
164 | if writableCorked { ms += " corked" }
165 |
166 | for ( key, value ) in environment.loggingDictionary {
167 | ms += " \(key)=\(value)"
168 | }
169 |
170 | if !writtenContent.isEmpty {
171 | ms += " #written=\(writtenContent.count)"
172 | }
173 |
174 | return ms
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/Sources/fs/Directory.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Directory.swift
3 | // Noze.io / Macro
4 | //
5 | // Created by Helge Hess on 04/05/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 | @inlinable
16 | public func readdir(_ path: String,
17 | yield: @escaping ( Error?, [ String ]? ) -> 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 |
--------------------------------------------------------------------------------