├── .github
├── release-drafter.yml
└── workflows
│ ├── docs.yml
│ ├── release-drafter.yml
│ ├── release.yml
│ ├── swift.yml
│ └── swiftlint.yml
├── .gitignore
├── .jazzy.yml
├── .swiftlint.yml
├── Images
├── helloworld-screenshot.png
├── swiftweb-architecture.png
└── swiftweb.png
├── LICENSE
├── Package.swift
├── README.md
├── Sources
├── SwiftWeb
│ ├── Extensions
│ │ └── Sequence+keypath.swift
│ ├── HTML
│ │ ├── HTMLNode.swift
│ │ └── HTMLTemplate.swift
│ ├── Server
│ │ ├── InputEvent.swift
│ │ ├── InputEventResponder.swift
│ │ └── SwiftWebServer.swift
│ ├── Utility
│ │ ├── Edge.swift
│ │ ├── Font.swift
│ │ ├── LayoutAxis.swift
│ │ └── PreviewProvider.swift
│ ├── View Modifiers
│ │ ├── ColorFilterModifier.swift
│ │ ├── FontModifier.swift
│ │ └── ModifiedContent.swift
│ ├── View Tree
│ │ ├── Binding.swift
│ │ ├── GrowingAxesModifying.swift
│ │ ├── PreferenceProvider.swift
│ │ ├── State.swift
│ │ ├── StateStorageNode.swift
│ │ ├── ViewNode+CustomStringConvertible.swift
│ │ ├── ViewNode.swift
│ │ └── ViewTree.swift
│ ├── View.swift
│ └── Views
│ │ ├── AnyView.swift
│ │ ├── Button.swift
│ │ ├── Color.swift
│ │ ├── ForEach.swift
│ │ ├── Form.swift
│ │ ├── Frame.swift
│ │ ├── GlobalOverlayView.swift
│ │ ├── HStack.swift
│ │ ├── Image.swift
│ │ ├── List.swift
│ │ ├── NavigationView.swift
│ │ ├── Picker.swift
│ │ ├── PreferenceChangeListenerView.swift
│ │ ├── PreferenceProvidingView.swift
│ │ ├── Section.swift
│ │ ├── Sheet.swift
│ │ ├── Slider.swift
│ │ ├── Spacer.swift
│ │ ├── Stack.swift
│ │ ├── TabItem.swift
│ │ ├── TabView.swift
│ │ ├── TaggedView.swift
│ │ ├── TapGestureView.swift
│ │ ├── Text.swift
│ │ ├── TextField.swift
│ │ ├── TupleView.swift
│ │ ├── VStack.swift
│ │ ├── ViewBuilder.swift
│ │ └── ZStack.swift
└── SwiftWebScript
│ └── JavaScriptClient.swift
└── Tests
├── LinuxMain.swift
└── SwiftWebTests
├── SwiftWebTests.swift
└── XCTestManifests.swift
/.github/release-drafter.yml:
--------------------------------------------------------------------------------
1 | branches: [release]
2 | name-template: '$NEXT_PATCH_VERSION'
3 | tag-template: '$NEXT_PATCH_VERSION'
4 | categories:
5 | - title: '🚀 Features'
6 | labels:
7 | - 'feature'
8 | - 'enhancement'
9 | - title: '🐛 Bug Fixes'
10 | labels:
11 | - 'fix'
12 | - 'bugfix'
13 | - 'bug'
14 | - title: '🧰 Maintenance'
15 | label: 'chore'
16 | change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
17 | template: |
18 | ## Changes
19 |
20 | $CHANGES
21 |
--------------------------------------------------------------------------------
/.github/workflows/docs.yml:
--------------------------------------------------------------------------------
1 | name: Publish Documentation
2 |
3 | on:
4 | release:
5 | types: [published]
6 |
7 | jobs:
8 | deploy_docs:
9 | runs-on: macos-latest
10 | steps:
11 | - uses: actions/checkout@v1
12 | - name: Publish Jazzy Docs
13 | uses: steven0351/publish-jazzy-docs@v1
14 | with:
15 | personal_access_token: ${{ secrets.ACCESS_TOKEN }}
16 | config: .jazzy.yml
17 |
--------------------------------------------------------------------------------
/.github/workflows/release-drafter.yml:
--------------------------------------------------------------------------------
1 | name: Release Drafter
2 |
3 | on:
4 | push:
5 | # branches to consider in the event; optional, defaults to all
6 | branches:
7 | - release
8 |
9 | jobs:
10 | update_release_draft:
11 | runs-on: ubuntu-latest
12 | steps:
13 | # Drafts your next Release notes as Pull Requests are merged into "release"
14 | - uses: release-drafter/release-drafter@v5
15 | env:
16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
17 | - name: Notify Example Project
18 | run: |
19 | curl -XPOST -u "${{ secrets.PAT_USERNAME}}:${{secrets.ACCESS_TOKEN}}"\
20 | -H "Accept: application/vnd.github.everest-preview+json"\
21 | -H "Content-Type: application/json" https://api.github.com/repos/YOURNAME/APPLICATION_NAME/dispatches --data '{"event_type": "build_application"}'
22 |
23 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Create Release
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*.*.*'
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: GH Release
13 | uses: softprops/action-gh-release@v0.1.5
14 | env:
15 | GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }}
16 |
--------------------------------------------------------------------------------
/.github/workflows/swift.yml:
--------------------------------------------------------------------------------
1 | name: Build and Test
2 |
3 | on:
4 | push:
5 | branches: [ develop, release ]
6 | pull_request:
7 | branches: [ develop, release ]
8 |
9 | jobs:
10 | build:
11 | runs-on: macos-latest
12 | steps:
13 | - uses: actions/checkout@v2
14 | - name: Xcode version check
15 | run: xcodebuild -version
16 | - name: Check version
17 | run: swift --version
18 | - name: Build
19 | run: swift build
20 | - name: Test
21 | run: swift test
22 |
--------------------------------------------------------------------------------
/.github/workflows/swiftlint.yml:
--------------------------------------------------------------------------------
1 | name: SwiftLint
2 |
3 | on:
4 | pull_request:
5 | paths:
6 | - '.github/workflows/swiftlint.yml'
7 | - '.swiftlint.yml'
8 | - '**/*.swift'
9 |
10 | jobs:
11 | swiftlint:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v1
15 | - name: GitHub Action for SwiftLint
16 | uses: norio-nomura/action-swiftlint@3.1.0
17 |
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Apodini .gitignore File
2 |
3 | # Swift Package Manager
4 | Package.resolved
5 | *.xcodeproj
6 | .swiftpm
7 | .build/
8 |
9 | # Xcode User settings
10 | xcuserdata/
11 |
12 | # Other files
13 | .DS_Store
14 |
15 | # Fastlane
16 | fastlane/report.xml
17 | fastlane/Preview.html
18 | fastlane/screenshots/**/*.png
19 | fastlane/test_output
--------------------------------------------------------------------------------
/.jazzy.yml:
--------------------------------------------------------------------------------
1 | module: SwiftWeb
2 | author: Quirin Schweigert
3 | theme: fullwidth
4 | output: ./docs
5 | documentation: ./*.md
6 | author_url: https://github.com/Apodini
7 | github_url: https://github.com/Apodini/SwiftWeb
8 |
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | # Apodini SwiftLint file Apodini
2 |
3 | # The whitelist_rules configuration also includes rules that are enabled by default to provide a good overview of all rules.
4 | whitelist_rules:
5 | - anyobject_protocol
6 | # Prefer using AnyObject over class for class-only protocols.
7 | - array_init
8 | # Prefer using Array(seq) over seq.map { $0 } to convert a sequence into an Array.
9 | - block_based_kvo
10 | # Prefer the new block based KVO API with keypaths when using Swift 3.2 or later.
11 | - class_delegate_protocol
12 | # Delegate protocols should be class-only so they can be weakly referenced.
13 | - closing_brace
14 | # Closing brace with closing parenthesis should not have any whitespaces in the middle.
15 | # - closure_body_length
16 | # Closure bodies should not span too many lines.
17 | - closure_end_indentation
18 | # Closure end should have the same indentation as the line that started it.
19 | # - closure_parameter_position
20 | # Closure parameters should be on the same line as opening brace.
21 | - closure_spacing
22 | # Closure expressions should have a single space inside each brace.
23 | - collection_alignment
24 | # All elements in a collection literal should be vertically aligned
25 | - colon
26 | # Colons should be next to the identifier when specifying a type and next to the key in dictionary literals.
27 | - comma
28 | # There should be no space before and one after any comma.
29 | - compiler_protocol_init
30 | # The initializers declared in compiler protocols such as ExpressibleByArrayLiteral shouldn't be called directly.
31 | - conditional_returns_on_newline
32 | # Conditional statements should always return on the next line
33 | - contains_over_filter_count
34 | # Prefer contains over comparing filter(where:).count to 0.
35 | - contains_over_filter_is_empty
36 | # Prefer contains over using filter(where:).isEmpty
37 | - contains_over_first_not_nil
38 | # Prefer `contains` over `first(where:) != nil`
39 | - contains_over_range_nil_comparison
40 | # Prefer contains over range(of:) != nil and range(of:) == nil
41 | - control_statement
42 | # if, for, guard, switch, while, and catch statements shouldn't unnecessarily wrap their conditionals or arguments in parentheses.
43 | # - convenience_type
44 | # Types used for hosting only static members should be implemented as a caseless enum to avoid instantiation.
45 | - cyclomatic_complexity
46 | # Complexity of function bodies should be limited.
47 | - discarded_notification_center_observer
48 | # When registering for a notification using a block, the opaque observer that is returned should be stored so it can be removed later.
49 | - discouraged_direct_init
50 | # Discouraged direct initialization of types that can be harmful. e.g. UIDevice(), Bundle()
51 | - discouraged_optional_boolean
52 | # Prefer non-optional booleans over optional booleans.
53 | # - discouraged_optional_collection # Enable as soon as https://github.com/realm/SwiftLint/issues/2298 is fixed
54 | # Prefer empty collection over optional collection.
55 | - duplicate_imports
56 | # Duplicate Imports
57 | - dynamic_inline
58 | # Avoid using 'dynamic' and '@inline(__always)' together.
59 | - empty_collection_literal
60 | # Prefer checking isEmpty over comparing collection to an empty array or dictionary literal.
61 | - empty_count
62 | # Prefer checking `isEmpty` over comparing `count` to zero.
63 | - empty_enum_arguments
64 | # Arguments can be omitted when matching enums with associated types if they are not used.
65 | - empty_parameters
66 | # Prefer () -> over Void ->.
67 | - empty_parentheses_with_trailing_closure
68 | # When using trailing closures, empty parentheses should be avoided after the method call.
69 | - empty_string
70 | # Prefer checking `isEmpty` over comparing string to an empty string literal.
71 | - empty_xctest_method
72 | # Empty XCTest method should be avoided.
73 | - enum_case_associated_values_count
74 | # Number of associated values in an enum case should be low
75 | - explicit_init
76 | # Explicitly calling .init() should be avoided.
77 | - fatal_error_message
78 | # A fatalError call should have a message.
79 | - file_length
80 | # Files should not span too many lines.
81 | # See file_length below for the exact configuration.
82 | - first_where
83 | # Prefer using ``.first(where:)`` over ``.filter { }.first` in collections.
84 | - flatmap_over_map_reduce
85 | # Prefer flatMap over map followed by reduce([], +).
86 | - for_where
87 | # where clauses are preferred over a single if inside a for.
88 | - force_cast
89 | # Force casts should be avoided.
90 | - force_try
91 | # Force tries should be avoided.
92 | - force_unwrapping
93 | # Force unwrapping should be avoided.
94 | # - function_body_length
95 | # Functions bodies should not span too many lines.
96 | # See function_body_length below for the exact configuration.
97 | - function_parameter_count
98 | # Number of function parameters should be low.
99 | # See function_parameter_count below for the exact configuration.
100 | - generic_type_name
101 | # Generic type name should only contain alphanumeric characters, start with an uppercase character and span between 1 and 20 characters in length.
102 | - identical_operands
103 | # Comparing two identical operands is likely a mistake.
104 | - identifier_name
105 | # Identifier names should only contain alphanumeric characters and start with a lowercase character or should only contain capital letters.
106 | # In an exception to the above, variable names may start with a capital letter when they are declared static and immutable.
107 | # Variable names should not be too long or too short. Excluded names are listed below.
108 | - implicit_getter
109 | # Computed read-only properties and subscripts should avoid using the get keyword.
110 | - implicit_return
111 | # Prefer implicit returns in closures.
112 | - implicitly_unwrapped_optional
113 | # Implicitly unwrapped optionals should be avoided when possible.
114 | - inert_defer
115 | # If defer is at the end of its parent scope, it will be executed right where it is anyway.
116 | - is_disjoint
117 | # Prefer using Set.isDisjoint(with:) over Set.intersection(_:).isEmpty.
118 | - joined_default_parameter
119 | # Discouraged explicit usage of the default separator.
120 | - large_tuple
121 | # Tuples shouldn't have too many members. Create a custom type instead.
122 | # See large_tuple below for the exact configuration.
123 | - last_where
124 | # Prefer using .last(where:) over .filter { }.last in collections.
125 | - leading_whitespace
126 | # Files should not contain leading whitespace.
127 | - legacy_cggeometry_functions
128 | # CGGeometry: Struct extension properties and methods are preferred over legacy functions
129 | - legacy_constant
130 | # Struct-scoped constants are preferred over legacy global constants (CGSize, CGRect, NSPoint, ...).
131 | - legacy_constructor
132 | # Swift constructors are preferred over legacy convenience functions (CGPointMake, CGSizeMake, UIOffsetMake, ...).
133 | - legacy_hashing
134 | # Prefer using the hash(into:) function instead of overriding hashValue
135 | - legacy_multiple
136 | # Prefer using the isMultiple(of:) function instead of using the remainder operator (%).
137 | - legacy_nsgeometry_functions
138 | # Struct extension properties and methods are preferred over legacy functions
139 | - legacy_random
140 | # Prefer using type.random(in:) over legacy functions.
141 | - line_length
142 | # Lines should not span too many characters.
143 | # See line_length below for the exact configuration.
144 | - literal_expression_end_indentation
145 | # Array and dictionary literal end should have the same indentation as the line that started it.
146 | - lower_acl_than_parent
147 | # Ensure definitions have a lower access control level than their enclosing parent
148 | - mark
149 | # MARK comment should be in valid format. e.g. '// MARK: ...' or '// MARK: - ...'
150 | # - missing_docs
151 | # Declarations should be documented.
152 | - modifier_order
153 | # Modifier order should be consistent.
154 | - multiline_arguments
155 | # Arguments should be either on the same line, or one per line.
156 | - multiline_function_chains
157 | # Chained function calls should be either on the same line, or one per line.
158 | - multiline_literal_brackets
159 | # Multiline literals should have their surrounding brackets in a new line.
160 | - multiline_parameters
161 | # Functions and methods parameters should be either on the same line, or one per line.
162 | - nesting
163 | # Types and statements should only be nested to a certain level deep.
164 | # See nesting below for the exact configuration.
165 | - nimble_operator
166 | # Prefer Nimble operator overloads over free matcher functions.
167 | - no_fallthrough_only
168 | # Fallthroughs can only be used if the case contains at least one other statement.
169 | - no_space_in_method_call
170 | # Don’t add a space between the method name and the parentheses.
171 | - notification_center_detachment
172 | # An object should only remove itself as an observer in deinit.
173 | - nslocalizedstring_key
174 | # Static strings should be used as key in NSLocalizedString in order to genstrings work.
175 | - nsobject_prefer_isequal
176 | # NSObject subclasses should implement isEqual instead of ==.
177 | - object_literal
178 | # Prefer object literals over image and color inits.
179 | - opening_brace
180 | # Opening braces should be preceded by a single space and on the same line as the declaration.
181 | - operator_usage_whitespace
182 | # Operators should be surrounded by a single whitespace when they are being used.
183 | - operator_whitespace
184 | # Operators should be surrounded by a single whitespace when defining them.
185 | - optional_enum_case_matching
186 | # Matching an enum case against an optional enum without ‘?’ is supported on Swift 5.1 and above.
187 | - orphaned_doc_comment
188 | # A doc comment should be attached to a declaration.
189 | - overridden_super_call
190 | # Some overridden methods should always call super
191 | - pattern_matching_keywords
192 | # Combine multiple pattern matching bindings by moving keywords out of tuples.
193 | - prefer_self_type_over_type_of_self
194 | # Prefer Self over type(of: self) when accessing properties or calling methods.
195 | - private_action
196 | # IBActions should be private.
197 | - private_outlet
198 | # IBOutlets should be private to avoid leaking UIKit to higher layers.
199 | - private_over_fileprivate
200 | # Prefer private over fileprivate declarations.
201 | - private_unit_test
202 | # Unit tests marked private are silently skipped.
203 | - prohibited_super_call
204 | # Some methods should not call super (
205 | # NSFileProviderExtension: providePlaceholder(at:completionHandler:)
206 | # NSTextInput doCommand(by:)
207 | # NSView updateLayer()
208 | # UIViewController loadView())
209 | - protocol_property_accessors_order
210 | # When declaring properties in protocols, the order of accessors should be get set.
211 | - reduce_boolean
212 | # Prefer using .allSatisfy() or .contains() over reduce(true) or reduce(false)
213 | - reduce_into
214 | # Prefer reduce(into:_:) over reduce(_:_:) for copy-on-write types
215 | - redundant_discardable_let
216 | # Prefer _ = foo() over let _ = foo() when discarding a result from a function.
217 | - redundant_nil_coalescing
218 | # nil coalescing operator is only evaluated if the lhs is nil, coalescing operator with nil as rhs is redundant
219 | - redundant_objc_attribute
220 | # Objective-C attribute (@objc) is redundant in declaration.
221 | - redundant_optional_initialization
222 | # Initializing an optional variable with nil is redundant.
223 | - redundant_set_access_control
224 | # Property setter access level shouldn't be explicit if it's the same as the variable access level.
225 | - redundant_string_enum_value
226 | # String enum values can be omitted when they are equal to the enumcase name.
227 | - redundant_type_annotation
228 | # Variables should not have redundant type annotation
229 | - redundant_void_return
230 | # Returning Void in a function declaration is redundant.
231 | - return_arrow_whitespace
232 | # Return arrow and return type should be separated by a single space or on a separate line.
233 | - shorthand_operator
234 | # Prefer shorthand operators (+=, -=, *=, /=) over doing the operation and assigning.
235 | - single_test_class
236 | # Test files should contain a single QuickSpec or XCTestCase class.
237 | - sorted_first_last
238 | # Prefer using `min()`` or `max()`` over `sorted().first` or `sorted().last`
239 | - statement_position
240 | # Else and catch should be on the same line, one space after the previous declaration.
241 | - static_operator
242 | # Operators should be declared as static functions, not free functions.
243 | - superfluous_disable_command
244 | # SwiftLint ‘disable’ commands are superfluous when the disabled rule would not have triggered a violation in the disabled region. Use “ - ” if you wish to document a command.
245 | - switch_case_alignment
246 | # Case statements should vertically align with their enclosing switch statement, or indented if configured otherwise.
247 | - syntactic_sugar
248 | # Shorthand syntactic sugar should be used, i.e. [Int] instead of Array.
249 | # - todo
250 | # TODOs and FIXMEs should be resolved.
251 | - toggle_bool
252 | # Prefer someBool.toggle() over someBool = !someBool.
253 | - trailing_closure
254 | # Trailing closure syntax should be used whenever possible.
255 | - trailing_comma
256 | # Trailing commas in arrays and dictionaries should be avoided/enforced.
257 | - trailing_newline
258 | # Files should have a single trailing newline.
259 | - trailing_semicolon
260 | # Lines should not have trailing semicolons.
261 | - trailing_whitespace
262 | # Lines should not have trailing whitespace.
263 | # Ignored lines are specified below.
264 | - type_body_length
265 | # Type bodies should not span too many lines.
266 | # See large_tuple below for the exact configuration.
267 | - type_name
268 | # Type name should only contain alphanumeric characters, start with an uppercase character and span between 3 and 40 characters in length.
269 | # Excluded types are listed below.
270 | - unavailable_function
271 | # Unimplemented functions should be marked as unavailable.
272 | - unneeded_break_in_switch
273 | # Avoid using unneeded break statements.
274 | - unneeded_parentheses_in_closure_argument
275 | # Parentheses are not needed when declaring closure arguments.
276 | - untyped_error_in_catch
277 | # Catch statements should not declare error variables without type casting.
278 | - unused_capture_list
279 | # Unused reference in a capture list should be removed.
280 | - unused_closure_parameter
281 | # Unused parameter in a closure should be replaced with _.
282 | - unused_control_flow_label
283 | # Unused control flow label should be removed.
284 | - unused_declaration
285 | # Declarations should be referenced at least once within all files linted.
286 | - unused_enumerated
287 | # When the index or the item is not used, .enumerated() can be removed.
288 | - unused_import
289 | # All imported modules should be required to make the file compile.
290 | - unused_optional_binding
291 | # Prefer != nil over let _ =
292 | - unused_setter_value
293 | # Setter value is not used.
294 | - valid_ibinspectable
295 | # @IBInspectable should be applied to variables only, have its type explicit and be of a supported type
296 | - vertical_parameter_alignment
297 | # Function parameters should be aligned vertically if they're in multiple lines in a declaration.
298 | - vertical_parameter_alignment_on_call
299 | # Function parameters should be aligned vertically if they're in multiple lines in a method call.
300 | - vertical_whitespace
301 | # Limit vertical whitespace to a single empty line.
302 | # See vertical_whitespace below for the exact configuration.
303 | - vertical_whitespace_closing_braces
304 | # Don’t include vertical whitespace (empty line) before closing braces.
305 | - vertical_whitespace_opening_braces
306 | # Don’t include vertical whitespace (empty line) after opening braces.
307 | - void_return
308 | # Prefer -> Void over -> ().
309 | - weak_delegate
310 | # Delegates should be weak to avoid reference cycles.
311 | - xctfail_message
312 | # An XCTFail call should include a description of the assertion.
313 | - yoda_condition
314 | # The variable should be placed on the left, the constant on the right of a comparison operator.
315 |
316 | excluded: # paths to ignore during linting. Takes precedence over `included`.
317 | - Carthage
318 | - Pods
319 | - .build
320 | - .swiftpm
321 | - R.generated.swift
322 |
323 | closure_body_length: # Closure bodies should not span too many lines.
324 | - 35 # warning - default: 20
325 | - 35 # error - default: 100
326 |
327 | enum_case_associated_values_count: # Number of associated values in an enum case should be low
328 | - 5 # warning - default: 5
329 | - 5 # error - default: 6
330 |
331 | file_length: # Files should not span too many lines.
332 | - 500 # warning - default: 400
333 | - 500 # error - default: 1000
334 |
335 | function_body_length: # Functions bodies should not span too many lines.
336 | - 50 # warning - default: 40
337 | - 50 # error - default: 100
338 |
339 | function_parameter_count: # Number of function parameters should be low.
340 | - 5 # warning - default: 5
341 | - 5 # error - default: 8
342 |
343 | identifier_name:
344 | excluded: # excluded names
345 | - id
346 | - ok
347 | - or
348 | - x
349 | - y
350 | - px
351 |
352 | large_tuple: # Tuples shouldn't have too many members. Create a custom type instead.
353 | - 4 # warning - default: 2
354 | - 5 # error - default: 3
355 |
356 | line_length: # Lines should not span too many characters.
357 | warning: 150 # default: 120
358 | error: 150 # default: 200
359 | ignores_comments: true # default: false
360 | ignores_urls: true # default: false
361 | ignores_function_declarations: false # default: false
362 | ignores_interpolated_strings: true # default: false
363 |
364 | nesting: # Types should be nested at most 2 level deep, and statements should be nested at most 5 levels deep.
365 | type_level:
366 | warning: 2 # warning - default: 1
367 | statement_level:
368 | warning: 5 # warning - default: 5
369 |
370 | trailing_closure:
371 | only_single_muted_parameter: true
372 |
373 | type_body_length: # Type bodies should not span too many lines.
374 | - 250 # warning - default: 200
375 | - 250 # error - default: 200
376 |
377 | type_name:
378 | excluded: # excluded names
379 | - ID
380 |
381 | trailing_whitespace:
382 | ignores_empty_lines: true # default: false
383 | ignores_comments: true # default: false
384 |
385 | unused_optional_binding:
386 | ignore_optional_try: true
387 |
388 | vertical_whitespace: # Limit vertical whitespace to a single empty line.
389 | max_empty_lines: 2 # warning - default: 1
390 |
--------------------------------------------------------------------------------
/Images/helloworld-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Apodini/SwiftWeb/54823c204b5dfdea97d570cb65c5e32cfafc0b7f/Images/helloworld-screenshot.png
--------------------------------------------------------------------------------
/Images/swiftweb-architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Apodini/SwiftWeb/54823c204b5dfdea97d570cb65c5e32cfafc0b7f/Images/swiftweb-architecture.png
--------------------------------------------------------------------------------
/Images/swiftweb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Apodini/SwiftWeb/54823c204b5dfdea97d570cb65c5e32cfafc0b7f/Images/swiftweb.png
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Quirin Schweigert
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.2
2 | import PackageDescription
3 |
4 | let package = Package(
5 | name: "SwiftWeb",
6 | platforms: [
7 | .macOS(.v10_15)
8 | ],
9 | products: [
10 | .library(name: "SwiftWeb", targets: ["SwiftWeb"]),
11 | .library(name: "SwiftWebScript", targets: ["SwiftWebScript"])
12 | ],
13 | targets: [
14 | .target(name: "SwiftWeb"),
15 | .target(name: "SwiftWebScript"),
16 | .testTarget(name: "SwiftWebTests", dependencies: ["SwiftWeb"])
17 | ]
18 | )
19 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ### SwiftWeb
4 | **Run Your SwiftUI App on a Swift Server. Serve Many Clients with Your SwiftUI Web App.**
5 |
6 | ---
7 |
8 |
9 |
10 | With **SwiftWeb**, you can easily provide a web interface to your existing SwiftUI app. SwiftWeb renders SwiftUI code to HTML and CSS and keeps a WebSocket connection to connected Browsers. User input events are sent to your Swift server which runs your application logic. Screen updates are sent back to connected clients.
11 |
12 | ## Requirements
13 |
14 | The SwiftWeb framework is intentionally kept independant of any HTTP / WebSocket server implementation. In order to provide a user interface over the web, you need to
15 | 1. provide the SwiftWeb HTML template (`HTMLTemplate.withContent("")`) under a URL of your desire,
16 | 2. provide the JavaScript client script (`JavaScriptClient.script`) under `/script.js` and
17 | 3. implement a WebSocket endpoint under `/websocket` on your server and connect it to a `SwiftWebServer` instance.
18 |
19 | Have a look at the [example implementation](https://github.com/Apodini/SwiftWeb-Example) of an XCode project running an HTTP and WebSocket server together with SwiftWeb.
20 |
21 | ## Usage
22 |
23 | Simply instantiate a server instance with a view instance:
24 |
25 | ```let swiftWebServer = SwiftWebServer(contentView: Text("Hello World!")```
26 |
27 |
28 |
29 |
(forKey preferenceKey: P.Type) -> P.Value? where P: PreferenceKey {
185 | let preferenceValueOfSubviews: P.Value? = subnodes.reduce(nil) {
186 | accumulatorValue, subnode -> P.Value? in
187 |
188 | let preferenceValueOfSubnode = subnode.findPreferenceValue(forKey: preferenceKey)
189 |
190 | // do we need to combine preference values because both accumulator and
191 | // preferenceValueOfSubnode are present?
192 | if let previousValue = accumulatorValue,
193 | let valueOfSubnode = preferenceValueOfSubnode {
194 | var value: P.Value = previousValue
195 |
196 | // reduce sibling preference values
197 | P.reduce(value: &value) {
198 | valueOfSubnode
199 | }
200 |
201 | // return the combined value
202 | return value
203 | }
204 |
205 | // return the one which is present
206 | return preferenceValueOfSubnode ?? accumulatorValue
207 | }
208 |
209 | let preferenceValueOfThisView: P.Value?
210 |
211 | // is the view that this node is holding providing a preference value for the supplied key?
212 | if let preferenceProviderView = view as? PreferenceProvider,
213 | preferenceProviderView.preferenceKeyType == P.self {
214 | preferenceValueOfThisView = preferenceProviderView.preferenceValue as? P.Value
215 | } else {
216 | preferenceValueOfThisView = nil
217 | }
218 |
219 | if let preferenceValueOfThisView = preferenceValueOfThisView,
220 | let preferenceValueOfSubviews = preferenceValueOfSubviews {
221 | var value: P.Value = preferenceValueOfSubviews
222 |
223 | // reduce preference value of subnodes and parent node
224 | P.reduce(value: &value) {
225 | preferenceValueOfThisView
226 | }
227 |
228 | return value
229 | }
230 |
231 | return preferenceValueOfThisView ?? preferenceValueOfSubviews
232 | }
233 |
234 | // func provideViewPreferences() {
235 | // if let preferenceChangeListener = view as? PreferenceChangeListener {
236 | //// preferenceChangeListener.onPreferenceChange(
237 | //// preferenceValue: findPreferenceValue(forKey: preferenceChangeListener.preferenceKey)
238 | //// )
239 | // let preferenceValue = findPreferenceValue(forKey: preferenceChangeListener.)
240 | // }
241 | // }
242 | }
243 |
--------------------------------------------------------------------------------
/Sources/SwiftWeb/View Tree/ViewTree.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewTree.swift
3 | //
4 | //
5 | // Created by Quirin Schweigert on 05.03.20.
6 | //
7 |
8 | import Foundation
9 |
10 | class ViewTree {
11 | let rootNode: ViewNode
12 |
13 | init(withRootView rootView: TypeErasedView) {
14 | rootNode = ViewNode(forView: rootView)
15 | }
16 |
17 | func render() -> HTMLNode {
18 | rootNode.render()
19 | }
20 |
21 | func handle(inputEvent: InputEvent) {
22 | rootNode.handle(inputEvent: inputEvent)
23 | }
24 | }
25 |
26 | extension ViewTree: CustomStringConvertible {
27 | var description: String {
28 | rootNode.description
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Sources/SwiftWeb/View.swift:
--------------------------------------------------------------------------------
1 | //
2 | // View.swift
3 | //
4 | //
5 | // Created by Quirin Schweigert on 12.12.19.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Type-erased `View` w.r.t. the `Body` associated type.
11 | public protocol TypeErasedView {
12 | /// The primary layout axis of the view. This is used by the SwiftWeb layout system to generate CSS properties mimicking the
13 | /// SwiftUI layout system. By default, all HTML elements in SwiftWeb are aligned in the `.vertical` axis.
14 | var layoutAxis: LayoutAxis { get }
15 |
16 | /// Defines the generation of HTML representing the view.
17 | func html(forHTMLOfSubnodes: [HTMLNode]) -> HTMLNode
18 |
19 | /// Transforms this view with the supplied closure argument. Can be overridden by `View`s by implementing the
20 | /// `CustomMappable` protocol in order to represent multiple subviews. This method is used by SwiftWeb for instantiating the
21 | /// view tree during runtime.
22 | func map