Repository: realm/jazzy Branch: master Commit: 5ff729211cc7 Files: 90 Total size: 527.4 KB Directory structure: gitextract_xj36c4n0/ ├── .github/ │ └── workflows/ │ └── Tests.yml ├── .gitignore ├── .gitmodules ├── .rubocop.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Dangerfile ├── Gemfile ├── LICENSE ├── ObjectiveC.md ├── README.md ├── Rakefile ├── bin/ │ ├── jazzy │ └── sourcekitten ├── images/ │ └── logo.sketch ├── jazzy.gemspec ├── js/ │ └── package.json ├── lib/ │ ├── jazzy/ │ │ ├── config.rb │ │ ├── doc.rb │ │ ├── doc_builder.rb │ │ ├── doc_index.rb │ │ ├── docset_builder/ │ │ │ └── info_plist.mustache │ │ ├── docset_builder.rb │ │ ├── documentation_generator.rb │ │ ├── executable.rb │ │ ├── gem_version.rb │ │ ├── grouper.rb │ │ ├── highlighter.rb │ │ ├── jazzy_markdown.rb │ │ ├── podspec_documenter.rb │ │ ├── search_builder.rb │ │ ├── source_declaration/ │ │ │ ├── access_control_level.rb │ │ │ └── type.rb │ │ ├── source_declaration.rb │ │ ├── source_document.rb │ │ ├── source_host.rb │ │ ├── source_mark.rb │ │ ├── source_module.rb │ │ ├── sourcekitten.rb │ │ ├── stats.rb │ │ ├── symbol_graph/ │ │ │ ├── constraint.rb │ │ │ ├── ext_key.rb │ │ │ ├── ext_node.rb │ │ │ ├── graph.rb │ │ │ ├── relationship.rb │ │ │ ├── sym_node.rb │ │ │ └── symbol.rb │ │ ├── symbol_graph.rb │ │ └── themes/ │ │ ├── apple/ │ │ │ ├── assets/ │ │ │ │ ├── css/ │ │ │ │ │ ├── highlight.css.scss │ │ │ │ │ └── jazzy.css.scss │ │ │ │ └── js/ │ │ │ │ ├── jazzy.js │ │ │ │ ├── jazzy.search.js │ │ │ │ └── typeahead.jquery.js │ │ │ └── templates/ │ │ │ ├── deprecation.mustache │ │ │ ├── doc.mustache │ │ │ ├── footer.mustache │ │ │ ├── header.mustache │ │ │ ├── nav.mustache │ │ │ ├── parameter.mustache │ │ │ ├── task.mustache │ │ │ └── tasks.mustache │ │ ├── fullwidth/ │ │ │ ├── assets/ │ │ │ │ ├── css/ │ │ │ │ │ ├── highlight.css.scss │ │ │ │ │ └── jazzy.css.scss │ │ │ │ └── js/ │ │ │ │ ├── jazzy.js │ │ │ │ ├── jazzy.search.js │ │ │ │ └── typeahead.jquery.js │ │ │ └── templates/ │ │ │ ├── deprecation.mustache │ │ │ ├── doc.mustache │ │ │ ├── footer.mustache │ │ │ ├── header.mustache │ │ │ ├── nav.mustache │ │ │ ├── parameter.mustache │ │ │ ├── task.mustache │ │ │ └── tasks.mustache │ │ └── jony/ │ │ ├── assets/ │ │ │ ├── css/ │ │ │ │ ├── highlight.css.scss │ │ │ │ └── jazzy.css.scss │ │ │ └── js/ │ │ │ └── jazzy.js │ │ └── templates/ │ │ ├── deprecation.mustache │ │ ├── doc.mustache │ │ ├── footer.mustache │ │ ├── header.mustache │ │ ├── nav.mustache │ │ ├── parameter.mustache │ │ ├── task.mustache │ │ └── tasks.mustache │ └── jazzy.rb └── spec/ ├── Moya.podspec ├── integration_spec.rb ├── spec_helper/ │ └── pre_flight.rb └── spec_helper.rb ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/Tests.yml ================================================ name: Tests on: push: branches: [master] pull_request: branches: ['*'] jobs: danger_and_rubocop: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: ruby-version: 3.3 bundler-cache: true - name: Rubocop run: | bundle exec rake rubocop - name: Danger env: DANGER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | bundle exec danger --verbose spec: runs-on: macos-15 continue-on-error: true strategy: matrix: spec: ["objc_spec", "swift_spec", "cocoapods_spec"] steps: - uses: actions/checkout@v4 with: submodules: recursive persist-credentials: false - uses: maxim-lobanov/setup-xcode@v1 with: xcode-version: '26.2' - uses: ruby/setup-ruby@v1 with: ruby-version: 3.3 bundler-cache: true - name: Cache cocoapods uses: actions/cache@v4 env: cache-name: cocoapods with: path: ~/.cocoapods key: ${{ matrix.spec }}-${{ env.cache-name }} - name: Test run: | bundle exec rake ${{ matrix.spec }} ================================================ FILE: .gitignore ================================================ jazzy-docs/ /docs/ node_modules *.gem *.rbc /.config /coverage/ /InstalledFiles /pkg/ /spec/reports/ /test/tmp/ /test/version_tmp/ /tmp/ ## Specific to RubyMotion: .dat* .repl_history build/ ## Documentation cache and generated files: /.yardoc/ /_yardoc/ /doc/ /rdoc/ ## Environment normalisation: /.bundle/ /lib/bundler/man/ # for a library or gem, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: # Gemfile.lock # .ruby-version # .ruby-gemset # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: .rvmrc .DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear on external disk .Spotlight-V100 .Trashes # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk vendor ================================================ FILE: .gitmodules ================================================ [submodule "SourceKitten"] path = SourceKitten url = https://github.com/jpsim/SourceKitten.git [submodule "spec/integration_specs"] path = spec/integration_specs url = https://github.com/realm/jazzy-integration-specs.git ================================================ FILE: .rubocop.yml ================================================ AllCops: Exclude: - ./spec/integration_specs/**/* - ./spec/Moya.podspec - ./vendor/**/* - ./tmp/**/* - ./SourceKitten/**/* SuggestExtensions: false TargetRubyVersion: 2.6 #- Pending Cops as of 1.81.1 ---------------------------------------------# Gemspec/AddRuntimeDependency: # new in 1.65 Enabled: true Gemspec/AttributeAssignment: # new in 1.77 Enabled: true Gemspec/DeprecatedAttributeAssignment: # new in 1.30 Enabled: true Gemspec/DevelopmentDependencies: # new in 1.44 Enabled: false Gemspec/RequireMFA: # new in 1.23 Enabled: true Layout/EmptyLinesAfterModuleInclusion: # new in 1.79 Enabled: true Layout/LineContinuationLeadingSpace: # new in 1.31 Enabled: false Layout/LineContinuationSpacing: # new in 1.31 Enabled: true Layout/SpaceBeforeBrackets: # (new in 1.7) Enabled: true Lint/AmbiguousAssignment: # (new in 1.7) Enabled: true Lint/AmbiguousRange: # (new in 1.19) Enabled: true Lint/ArrayLiteralInRegexp: # new in 1.71 Enabled: true Lint/ConstantOverwrittenInRescue: # new in 1.31 Enabled: true Lint/ConstantReassignment: # new in 1.70 Enabled: true Lint/CopDirectiveSyntax: # new in 1.72 Enabled: true Lint/DeprecatedConstants: # (new in 1.8) Enabled: true Lint/DuplicateBranch: # (new in 1.3) Enabled: true Lint/DuplicateMagicComment: # new in 1.37 Enabled: true Lint/DuplicateMatchPattern: # new in 1.50 Enabled: true Lint/DuplicateRegexpCharacterClassElement: # (new in 1.1) Enabled: true Lint/DuplicateSetElement: # new in 1.67 Enabled: true Lint/EmptyBlock: # (new in 1.1) Enabled: true Lint/EmptyClass: # (new in 1.3) Enabled: true Lint/EmptyInPattern: # (new in 1.16) Enabled: true Lint/HashNewWithKeywordArgumentsAsDefault: # new in 1.69 Enabled: true Lint/IncompatibleIoSelectWithFiberScheduler: # new in 1.21 Enabled: true Lint/ItWithoutArgumentsInBlock: # new in 1.59 Enabled: true Lint/LambdaWithoutLiteralBlock: # (new in 1.8) Enabled: true Lint/LiteralAssignmentInCondition: # new in 1.58 Enabled: true Lint/MixedCaseRange: # new in 1.53 Enabled: true Lint/NonAtomicFileOperation: # new in 1.31 Enabled: true Lint/NoReturnInBeginEndBlocks: # (new in 1.2) Enabled: true Lint/NumberedParameterAssignment: # (new in 1.9) Enabled: true Lint/NumericOperationWithConstantResult: # new in 1.69 Enabled: true Lint/OrAssignmentToConstant: # (new in 1.9) Enabled: true Lint/RedundantDirGlobSort: # (new in 1.8) Enabled: true Lint/RedundantRegexpQuantifiers: # new in 1.53 Enabled: true Lint/RedundantTypeConversion: # new in 1.72 Enabled: true Lint/RefinementImportMethods: # new in 1.27 Enabled: true Lint/RequireRangeParentheses: # new in 1.32 Enabled: true Lint/RequireRelativeSelfPath: # new in 1.22 Enabled: true Lint/SharedMutableDefault: # new in 1.70 Enabled: true Lint/SuppressedExceptionInNumberConversion: # new in 1.72 Enabled: true Lint/SymbolConversion: # (new in 1.9) Enabled: true Lint/ToEnumArguments: # (new in 1.1) Enabled: true Lint/TripleQuotes: # (new in 1.9) Enabled: true Lint/UnescapedBracketInRegexp: # new in 1.68 Enabled: true Lint/UnexpectedBlockArity: # (new in 1.5) Enabled: true Lint/UnmodifiedReduceAccumulator: # (new in 1.1) Enabled: true Lint/UselessConstantScoping: # new in 1.72 Enabled: true Lint/UselessDefaultValueArgument: # new in 1.76 Enabled: true Lint/UselessDefined: # new in 1.69 Enabled: true Lint/UselessNumericOperation: # new in 1.66 Enabled: true Lint/UselessOr: # new in 1.76 Enabled: true Lint/UselessRescue: # new in 1.43 Enabled: true Lint/UselessRuby2Keywords: # new in 1.23 Enabled: true Metrics/CollectionLiteralLength: # new in 1.47 Enabled: true Naming/BlockForwarding: # new in 1.24 Enabled: true Naming/InclusiveLanguage: # (new in 1.18) Enabled: true Naming/PredicateMethod: # new in 1.76 Enabled: true Security/CompoundHash: # new in 1.28 Enabled: true Style/BitwisePredicate: # new in 1.68 Enabled: true Style/CombinableDefined: # new in 1.68 Enabled: true Style/ComparableBetween: # new in 1.74 Enabled: true Style/ConcatArrayLiterals: # new in 1.41 Enabled: true Security/IoMethods: # new in 1.22 Enabled: true Style/AmbiguousEndlessMethodDefinition: # new in 1.68 Enabled: true Style/ArgumentsForwarding: # (new in 1.1) Enabled: true Style/ArrayIntersect: # new in 1.40 Enabled: true Style/ArrayIntersectWithSingleElement: # new in 1.81 Enabled: true Style/CollectionCompact: # (new in 1.2) Enabled: true Style/CollectionQuerying: # new in 1.77 Enabled: true Style/ComparableClamp: # new in 1.44 Enabled: true Style/DataInheritance: # new in 1.49 Enabled: true Style/DigChain: # new in 1.69 Enabled: true Style/DirEmpty: # new in 1.48 Enabled: true Style/DocumentDynamicEvalDefinition: # (new in 1.1) Enabled: true Style/EmptyHeredoc: # new in 1.32 Enabled: true Style/EmptyStringInsideInterpolation: # new in 1.76 Enabled: true Style/EndlessMethod: # (new in 1.8) Enabled: true Style/EnvHome: # new in 1.29 Enabled: true Style/ExactRegexpMatch: # new in 1.51 Enabled: true Style/FetchEnvVar: # new in 1.28 Enabled: true Style/FileEmpty: # new in 1.48 Enabled: true Style/FileNull: # new in 1.69 Enabled: true Style/FileRead: # new in 1.24 Enabled: true Style/FileTouch: # new in 1.69 Enabled: true Style/FileWrite: # new in 1.24 Enabled: true Style/HashConversion: # (new in 1.10) Enabled: true Style/HashExcept: # (new in 1.7) Enabled: true Style/HashFetchChain: # new in 1.75 Enabled: true Style/HashSlice: # new in 1.71 Enabled: true Style/IfWithBooleanLiteralBranches: # (new in 1.9) Enabled: true Style/InPatternThen: # (new in 1.16) Enabled: true Style/ItAssignment: # new in 1.70 Enabled: true Style/ItBlockParameter: # new in 1.75 Enabled: true Style/KeywordArgumentsMerging: # new in 1.68 Enabled: true Style/MagicCommentFormat: # new in 1.35 Enabled: true Style/MapCompactWithConditionalBlock: # new in 1.30 Enabled: true Style/MapIntoArray: # new in 1.63 Enabled: true Style/MapToHash: # new in 1.24 Enabled: true Style/MapToSet: # new in 1.42 Enabled: true Style/MinMaxComparison: # new in 1.42 Enabled: true Style/MultilineInPatternThen: # (new in 1.16) Enabled: true Style/NegatedIfElseCondition: # (new in 1.2) Enabled: true Style/NestedFileDirname: # new in 1.26 Enabled: true Style/NilLambda: # (new in 1.3) Enabled: true Style/NumberedParameters: # new in 1.22 Enabled: true Style/NumberedParametersLimit: # new in 1.22 Enabled: true Style/ObjectThen: # new in 1.28 Enabled: true Style/OpenStructUse: # new in 1.23 Enabled: true Style/OperatorMethodCall: # new in 1.37 Enabled: true Style/QuotedSymbols: # (new in 1.16) Enabled: true Style/RedundantArgument: # (new in 1.4) Enabled: true Style/RedundantArrayConstructor: # new in 1.52 Enabled: true Style/RedundantArrayFlatten: # new in 1.76 Enabled: true Style/RedundantConstantBase: # new in 1.40 Enabled: true Style/RedundantCurrentDirectoryInPath: # new in 1.53 Enabled: true Style/RedundantDoubleSplatHashBraces: # new in 1.41 Enabled: true Style/RedundantEach: # new in 1.38 Enabled: true Style/RedundantFilterChain: # new in 1.52 Enabled: true Style/RedundantFormat: # new in 1.72 Enabled: true Style/RedundantHeredocDelimiterQuotes: # new in 1.45 Enabled: true Style/RedundantInitialize: # new in 1.27 Enabled: true Style/RedundantInterpolationUnfreeze: # new in 1.66 Enabled: true Style/RedundantLineContinuation: # new in 1.49 Enabled: true Style/RedundantRegexpArgument: # new in 1.53 Enabled: true Style/RedundantRegexpConstructor: # new in 1.52 Enabled: true Style/RedundantSelfAssignmentBranch: # (new in 1.19) Enabled: true Style/RedundantStringEscape: # new in 1.37 Enabled: true Style/ReturnNilInPredicateMethodDefinition: # new in 1.53 Enabled: true Style/SafeNavigationChainLength: # new in 1.68 Enabled: true Style/SelectByRegexp: # new in 1.22 Enabled: true Style/SendWithLiteralMethodName: # new in 1.64 Enabled: true Style/SingleLineDoEndBlock: # new in 1.57 Enabled: true Style/StringChars: # (new in 1.12) Enabled: true Style/SuperArguments: # new in 1.64 Enabled: true Style/SuperWithArgsParentheses: # new in 1.58 Enabled: true Style/SwapValues: # (new in 1.1) Enabled: true Style/YAMLFileRead: # new in 1.53 Enabled: true # At the moment not ready to be used # https://github.com/bbatsov/rubocop/issues/947 Style/Documentation: Enabled: false #- Jazzy -----------------------------------------------------------------# # 20 lines is more reasonable than rubocop's default of 10 Metrics/MethodLength: Max: 20 Metrics/AbcSize: Enabled: false Metrics/ModuleLength: Enabled: false Metrics/BlockLength: Enabled: false Metrics/ParameterLists: CountKeywordArgs: false Style/NumericPredicate: Enabled: false Layout/HeredocIndentation: Enabled: false # We adopted raise instead of fail. Style/SignalException: EnforcedStyle: only_raise # They are idiomatic Lint/AssignmentInCondition: Enabled: false # Allow backticks Style/AsciiComments: Enabled: false # Indentation clarifies logic branches in implementations Style/IfUnlessModifier: Enabled: false # No enforced convention here. Style/SingleLineBlockParams: Enabled: false # We only add the comment when needed. Style/Encoding: Enabled: false # Having these make it easier to *not* forget to add one when adding a new # value and you can simply copy the previous line. Style/TrailingCommaInArrayLiteral: EnforcedStyleForMultiline: comma Style/TrailingCommaInHashLiteral: EnforcedStyleForMultiline: comma Style/TrailingCommaInArguments: EnforcedStyleForMultiline: comma Style/SpecialGlobalVars: Enabled: false Style/MultilineBlockChain: Enabled: false # We prefer explicit `a, _ = arr` to `a, = arr` (which could be misread as a stray comma) Style/TrailingUnderscoreVariable: Enabled: false # For lambdas nested within certain expressions, this rule forces either ugly # parens or curly braces that violate the "do/end for multiline blocks" rule. Style/Lambda: Enabled: false # Disallowing indented "when" clauses destroys readability when using the # single-line "when/then" style. Layout/CaseIndentation: Enabled: false # These are both subjective judgements that depend on the situation, and are # not appropriate as absolute rules. Style/GuardClause: Enabled: false Style/Next: Enabled: false # Avoid the least-readable varieties of regular expressions. Style/RegexpLiteral: EnforcedStyle: mixed # Sometimes easier to read either way Style/AccessorGrouping: Enabled: false # Avoid mandatory wide indentation Layout/MultilineMethodCallIndentation: EnforcedStyle: indented Layout/LineEndStringConcatenationIndentation: Enabled: true EnforcedStyle: indented # Avoid false positives with `Pathname` Style/StringConcatenation: Mode: conservative # Compatibility with earlier Rubocops Metrics/CyclomaticComplexity: Max: 10 Metrics/PerceivedComplexity: Max: 10 # Too much firing on basic arithmetic Lint/AmbiguousOperatorPrecedence: Enabled: false #- Jazzy specs -----------------------------------------------------------# # Allow for `should.match /regexp/`. Lint/AmbiguousRegexpLiteral: Exclude: - spec/**/* # Allow `object.should == object` syntax. Lint/Void: Exclude: - spec/**/* Style/ClassAndModuleChildren: Exclude: - spec/**/* Lint/BinaryOperatorWithIdenticalOperands: Exclude: - spec/**/* ================================================ FILE: CHANGELOG.md ================================================ ## Master ##### Breaking * None. ##### Enhancements * None. ##### Bug Fixes * None. ## 0.15.4 ##### Breaking * None. ##### Enhancements * Allow `custom_categories` to specify a regex for a child. [Enrico Zannini](https://github.com/Enricoza) [#688](https://github.com/realm/jazzy/issues/688) * Update Javascript: KaTeX 0.16.25 [John Fairhurst](https://github.com/johnfairh) ##### Bug Fixes * Don't call extension members that do not need documentation 'undocumented'. [John Fairhurst](https://github.com/johnfairh) * Work around activesupport vs. concurrent-ruby crash. [John Fairhurst](https://github.com/johnfairh) [#1414](https://github.com/realm/jazzy/issues/1414) * Better identify default implementations. [John Fairhurst](https://github.com/johnfairh) [#1420](https://github.com/realm/jazzy/issues/1420) * Suppress warning on extensions providing default implementations. [John Fairhurst](https://github.com/johnfairh) [#1396](https://github.com/realm/jazzy/issues/1396) ## 0.15.3 ##### Breaking * None. ##### Enhancements * None. ##### Bug Fixes * Don't crash in SourceKitten when the Swift 6 compiler reports educational notes. [John Fairhurst](https://github.com/johnfairh) [#1399](https://github.com/realm/jazzy/issues/1399) ## 0.15.2 ##### Breaking * None. ##### Enhancements * Support Swift 6.0 / Xcode 16.0 [John Fairhurst](https://github.com/johnfairh) ##### Bug Fixes * None. ## 0.15.1 ##### Breaking * None. ##### Enhancements * None. ##### Bug Fixes * Restore compatibility with Ruby 2.6 [John Fairhurst](https://github.com/johnfairh) [#1388](https://github.com/realm/jazzy/issues/1388) ## 0.15.0 ##### Breaking * None. ##### Enhancements * Update Javascript: typeahead.js 1.3.4, KaTeX 0.16.10 [John Fairhurst](https://github.com/johnfairh) * Support Swift 5.10 with Swift Package Manager projects. [John Fairhurst](https://github.com/johnfairh) [#1381](https://github.com/realm/jazzy/issues/1381) * Support documentation of multiple modules in a single website. Use `--modules` or the config-file `modules` for more control. See the README 'Documenting multiple modules' for more details. [Argjira Mala](https://github.com/argjiramala-tomtom) [Pedro Alcobia](https://github.com/PedroAlcobia-TomTom) [John Fairhurst](https://github.com/johnfairh) [#564](https://github.com/realm/jazzy/issues/564) * Improve page breadcrumbs to include parent pages and indicate source module of extensions from other modules. [John Fairhurst](https://github.com/johnfairh) * Add `--readme-title` and `--docset-title` to set the titles of the readme docs page and the Dash docset independently of the module name. [John Fairhurst](https://github.com/johnfairh) * Support Swift 5.9 symbolgraph extension symbols. [John Fairhurst](https://github.com/johnfairh) [#1368](https://github.com/realm/jazzy/issues/1368) ##### Bug Fixes * Fix incorrect activesupport usage. [John Fairhurst](https://github.com/johnfairh) ## 0.14.4 ##### Breaking * None. ##### Enhancements * Update Javascript: jQuery 3.7.1, KaTeX 0.16.8 [John Fairhurst](https://github.com/johnfairh) * Support Swift `package` access control level. [John Fairhurst](https://github.com/johnfairh) * Initial support for Swift macro declarations. Requires `--swift-build-tool symbolgraph`. [John Fairhurst](https://github.com/johnfairh) * Support Swift `@_documentation(visibility:)` attribute. Requires `--swift-build-tool spm|xcodebuild`. [John Fairhurst](https://github.com/johnfairh) ##### Bug Fixes * Issue a warning instead of crashing on declarations without names. [#1325](https://github.com/realm/jazzy/issues/1325) [John Fairhurst](https://github.com/johnfairh) * Fix extension ordering in symbolgraph mode. [John Fairhurst](https://github.com/johnfairh) * Fix symbolgraph mode crash involving tuple generic constraints. [John Fairhurst](https://github.com/johnfairh) ## 0.14.3 ##### Breaking * None. ##### Enhancements * Support Swift 5.7 and Xcode 14. [John Fairhurst](https://github.com/johnfairh) * Update Javascript: jQuery 3.6.1, KaTeX 0.13.5 [John Fairhurst](https://github.com/johnfairh) ##### Bug Fixes * None. ## 0.14.2 ##### Breaking * When building with Swift 5.6 and not passing `—-module` to Jazzy, declarations may not be correctly identified as undocumented and docs may include unwanted extensions. Pass `—-module MyModuleName` to fix this. [John Fairhurst](https://github.com/johnfairh) ##### Enhancements * Support using pre-generated symbolgraph files in Swift symbolgraph mode. [Nathan Wong](https://github.com/esteluk) * Issue a warning on some combinations of Objective-C flags. [John Fairhurst](https://github.com/johnfairh) [#900](https://github.com/realm/jazzy/issues/900) * Support Swift 5.6. The bundled `sourcekitten` is a universal binary. [John Fairhurst](https://github.com/johnfairh) ##### Bug Fixes * In Swift symbolgraph mode, stop including extensions to types that are beneath the minimum ACL. [John Fairhurst](https://github.com/johnfairh) [#1291](https://github.com/realm/jazzy/issues/1291) ## 0.14.1 ##### Breaking * Support Swift SPI groups. Swift declarations marked `@_spi` are no longer included in docs when `--min-acl` is set to `public` or `open`. Use `--include-spi-declarations` to include docs for these declarations. [John Fairhurst](https://github.com/johnfairh) [#1263](https://github.com/realm/jazzy/issues/1263) ##### Enhancements * Correct line number references with Xcode 13. [John Fairhurst](https://github.com/johnfairh) * Support `union` declarations in Objective-C headers. [Brian Osborn](https://github.com/bosborn) [John Fairhurst](https://github.com/johnfairh) * Support Swift concurrency features: identify actors and asynchronous methods. [John Fairhurst](https://github.com/johnfairh) ##### Bug Fixes * Improve HTML5 correctness, all themes. [John Fairhurst](https://github.com/johnfairh) [#1280](https://github.com/realm/jazzy/issues/1280) ## 0.14.0 ##### Breaking * Require at least Ruby 2.6.3. ##### Enhancements * Support DocC-style autolinks and callouts in markdown. [John Fairhurst](https://github.com/johnfairh) * Add `--source-host` option to support projects hosted on GitLab and Bitbucket as well as GitHub. Options `--source-host-url` and `--source-host-files-url` and new Mustache tags replace the 'github' versions which remain as back-compatibility aliases. [John Fairhurst](https://github.com/johnfairh) [#314](https://github.com/realm/jazzy/issues/314) * Add `rel="noopener"` to all `target="_blank"` links. [JP Simard](https://github.com/jpsim) ##### Bug Fixes * Fix source-host line number references in Swift symbolgraph mode, and in ObjC mode for references to one-line declarations. [John Fairhurst](https://github.com/johnfairh) * Fix crash with `` ` ` `` in markdown. [John Fairhurst](https://github.com/johnfairh) [#1270](https://github.com/realm/jazzy/issues/1270) * Fix symbolgraph mode with Xcode 13. [John Fairhurst](https://github.com/johnfairh) ## 0.13.7 The next release of Jazzy will require a minimum of Ruby 2.6. ##### Breaking * None. ##### Enhancements * Update JavaScript libraries: jQuery 3.6.0, Lunr 2.3.9, KaTeX 0.13.5. [John Fairhurst](https://github.com/johnfairh) * Support the markdown [footnotes](https://www.markdownguide.org/extended-syntax/#footnotes) extension in all themes. [John Fairhurst](https://github.com/johnfairh) [#1246](https://github.com/realm/jazzy/issues/1246) ##### Bug Fixes * Fix parameter doc comments in Swift symbolgraph mode. [John Fairhurst](https://github.com/johnfairh) [#1244](https://github.com/realm/jazzy/issues/1244) ## 0.13.6 ##### Breaking * None. ##### Enhancements * Support documentation generation from `.swiftmodule` binaries using `--swift-build-tool symbolgraph` with Swift 5.3. [John Fairhurst](https://github.com/johnfairh) ##### Bug Fixes * Always bypass codesigning when building Xcode projects. [John Fairhurst](https://github.com/johnfairh) [#1183](https://github.com/realm/jazzy/issues/1183) ## 0.13.5 ##### Breaking * None. ##### Enhancements * Add search function to `apple` theme. [Giles Payne](https://github.com/komakai) [#726](https://github.com/realm/jazzy/issues/726) * Add option `--[no-]separate-global-declarations` to always create separate documentation pages for top-level declarations as well as classes, structures, enums etc. even if they don't have members. As part of this, improve the main page declaration in all modes. [Nikolay Volosatov](https://github.com/bamx23) [John Fairhurst](https://github.com/johnfairh) ##### Bug Fixes * Style fixes for `apple` and `jony` themes to codeblocks inside lists and links. [John Fairhurst](https://github.com/johnfairh) [#818](https://github.com/realm/jazzy/issues/818) [#1177](https://github.com/realm/jazzy/issues/1177) ## 0.13.4 ##### Breaking * None. ##### Enhancements * Update JavaScript libraries: jQuery 3.5.1 (all themes), Lunr 2.3.8, typeahead.js 1.3.1 (`fullwidth` theme only). [John Fairhurst](https://github.com/johnfairh) ##### Bug Fixes * Fix warnings from Ruby 2.7. [John Fairhurst](https://github.com/johnfairh) [#1185](https://github.com/realm/jazzy/issues/1185) * Accept `root_url` without trailing slash. [John Fairhurst](https://github.com/johnfairh) [#1188](https://github.com/realm/jazzy/issues/1188) ## 0.13.3 ##### Breaking * None. ##### Enhancements * Added a config option to provide sources of privately hosted pod dependencies when using the `--podspec` option. `--pod-sources url1,url2,…urlN`. [Jonathan Bailey](https://github.com/jon889) [#650](https://github.com/realm/jazzy/issues/650) * Improve Dash docset support: support online redirection when `--root-url` is set, and provide `--docset-playground-url` to support docset playground links. [John Fairhurst](https://github.com/johnfairh) ##### Bug Fixes * Fix module version not being used from podspec. [Jonathan Bailey](https://github.com/jon889) * Autolink Swift custom attributes/property wrappers. [John Fairhurst](https://github.com/johnfairh) ## 0.13.2 ##### Breaking * None. ##### Enhancements * Support Xcode 11.4. Default Objective-C property attributes are now stripped from declarations: turn this off with `--keep-default-property-attributes`. [John Fairhurst](https://github.com/johnfairh) [#829](https://github.com/realm/jazzy/issues/829) * Render LaTeX expressions written using `$equation$` or `$$equation$$` syntax. [Arthur Guiot](https://github.com/arguiot) [John Fairhurst](https://github.com/johnfairh) [#1156](https://github.com/realm/jazzy/issues/1156) * Wrap long method names on category pages. Use `name_html` in custom mustache templates to take advantage of this. [John Fairhurst](https://github.com/johnfairh) [#995](https://github.com/realm/jazzy/issues/995) * Support Dash-style `apple_ref` links to specific API items, for more stable and human-readable links from external docs. [Paul Cantrell](https://github.com/pcantrell) ##### Bug Fixes * Don't generate documentation if the `xcodebuild` command fails. [John Fairhurst](https://github.com/johnfairh) [jpsim/SourceKitten#643](https://github.com/jpsim/SourceKitten/issues/643) * Use multi-line parsed declarations in more places including protocol methods and typealiases. [John Fairhurst](https://github.com/johnfairh) [#896](https://github.com/realm/jazzy/issues/896) ## 0.13.1 ##### Breaking * None. ##### Enhancements * Allow inline html tags in ObjC doc comments. [Chris Williams](https://github.com/ultramiraculous) [#976](https://github.com/realm/jazzy/issues/976) * Support code formatting in ObjC doc comments with `@c`, `@code` and `@endcode`. [Bryce Pauken](https://github.com/brycepauken) [jpsim/SourceKitten#631](https://github.com/jpsim/SourceKitten/issues/631) * Add `custom_categories_unlisted_prefix` configuration setting. This is the prefix for navigation section names that aren't explicitly listed in `custom_categories`. Defaults to 'Other '. [JP Simard](https://github.com/jpsim) * Add `hide_unlisted_documentation` configuration setting. Setting this to `true` hides documentation entries in the sidebar from the `documentation` config value that aren't explicitly listed in `custom_categories`. [JP Simard](https://github.com/jpsim) ##### Bug Fixes * Fix crash when SourceKit returns out of bounds string byte offsets. [JP Simard](https://github.com/jpsim) * Pick the right version of declarations with type attributes. [John Fairhurst](https://github.com/johnfairh) [#1148](https://github.com/realm/jazzy/issues/1148) ## 0.13.0 ##### Breaking * None. ##### Enhancements * Add section headings for members added by Swift conditional conformances. [John Fairhurst](https://github.com/johnfairh) [#717](https://github.com/realm/jazzy/issues/717) * Parse markdown in MARK comments, make the html available to themes via `name_html` mustache tag key for section headings. [John Fairhurst](https://github.com/johnfairh) * Include protocol conformances added by extensions in Swift docs. [John Fairhurst](https://github.com/johnfairh) ##### Bug Fixes * Render bullet lists correctly when followed by a callout. [John Fairhurst](https://github.com/johnfairh) [#785](https://github.com/realm/jazzy/issues/785) * Render markup of text inside double quotes. [John Fairhurst](https://github.com/johnfairh) [#992](https://github.com/realm/jazzy/issues/992) * Fix `sourcekitten_sourcefile` used from config file. [John Fairhurst](https://github.com/johnfairh) [#1137](https://github.com/realm/jazzy/issues/1137) ## 0.12.0 ##### Breaking * None. ##### Enhancements * Support for mixed Swift-ObjC modules: generate two sets of SourceKitten json and pass them on using `--sourcekitten-sourcefile`. [Joe Susnick](https://github.com/joesus) [John Fairhurst](https://github.com/johnfairh) [#447](https://github.com/realm/jazzy/issues/447) ##### Bug Fixes * Stop displaying type attributes on extension declarations. [John Fairhurst](https://github.com/johnfairh) * Show ObjC and Swift classes (etc.) in the same category. [John Fairhurst](https://github.com/johnfairh) * Merge Swift extensions into ObjC classes. [John Fairhurst](https://github.com/johnfairh) [Joe Susnick](https://github.com/joesus) ## 0.11.2 ##### Breaking * None. ##### Enhancements * None. ##### Bug Fixes * Generate Swift docs with Xcode 11 and paths with spaces. [John Fairhurst](https://github.com/johnfairh) [#1108](https://github.com/realm/jazzy/issues/1108) * Reinstate guessing of module name from podspec, broken in 0.11.0. [John Fairhurst](https://github.com/johnfairh) [#1109](https://github.com/realm/jazzy/issues/1109) ## 0.11.1 ##### Breaking * None. ##### Enhancements * None. ##### Bug Fixes * Don't use SwiftPM if there is an Xcode workspace or project in a non-root directory. [John Fairhurst](https://github.com/johnfairh) [#1103](https://github.com/realm/jazzy/issues/1103) ## 0.11.0 ##### Breaking * None. ##### Enhancements * Sass support now provided by `libsass` via `sassc` instead of the deprecated Ruby Sass gem. [John Fairhurst](https://github.com/johnfairh) * Update bundled jQuery to 3.4.1 (all themes). [Paul Idstein](https://github.com/idstein) * Support Xcode 11 Swift projects that pass a response file to the Swift compiler. [John Fairhurst](https://github.com/johnfairh) [#1087](https://github.com/realm/jazzy/issues/1087) * Generate Swift docs from a Swift Package Manager package without requiring an Xcode project file. Add `--swift-build-tool` to choose the build method if both `.xcodeproj` and `Package.swift` files are present. Add `--build-tool-flags` as a preferred alias for `--xcodebuild-flags`. [John Fairhurst](https://github.com/johnfairh) [#487](https://github.com/realm/jazzy/issues/487) ##### Bug Fixes * Preserve non-latin characters in guide filenames and heading IDs. [John Fairhurst](https://github.com/johnfairh) [#1091](https://github.com/realm/jazzy/issues/1091) * Generate correct html for custom categories containing special characters. [John Fairhurst](https://github.com/johnfairh) [#945](https://github.com/realm/jazzy/issues/945) * Fix crash on files with misplaced documentation comments. [John Fairhurst](https://github.com/johnfairh) [#1083](https://github.com/realm/jazzy/issues/1083) ## 0.10.0 ##### Breaking * The included `sourcekitten` binary is built with Xcode 10.2. This means it does not run on macOS earlier than 10.14.4 without the *Swift 5 Runtime Support for Command Line Tools* being installed. [John Fairhurst](https://github.com/johnfairh) ##### Enhancements * Support CocoaPods 1.6+. Use the `swift_version[s]` dsl in `--podspec` mode to set the Swift language version. [John Fairhurst](https://github.com/johnfairh) * Show the extension declaration when documenting Swift extensions. [John Fairhurst](https://github.com/johnfairh) * Allow docs title customization. Include `--module-version` when it is set and support `--title` to fully customize the title. Pass `{{module_version}}` and `{{docs_title}}` to templates. [Maximilian Alexander](https://github.com/mbalex99) [John Fairhurst](https://github.com/johnfairh) [#666](https://github.com/realm/jazzy/issues/666) [#411](https://github.com/realm/jazzy/issues/411) ##### Bug Fixes * Unfold member documentation when linked to from current web page. [John Fairhurst](https://github.com/johnfairh) [#788](https://github.com/realm/jazzy/issues/788) * Generate docs when there are unusual characters in source pathnames. [John Fairhurst](https://github.com/johnfairh) [#1049](https://github.com/realm/jazzy/issues/1049) * Generate docs for signed modules with Xcode 10.2. [John Fairhurst](https://github.com/johnfairh) [#1057](https://github.com/realm/jazzy/issues/1057) * Use correct module name when only target name is supplied. [Chris Zielinski](https://github.com/chriszielinski) [#422](https://github.com/realm/jazzy/issues/422) ## 0.9.6 This is (probably) the last release to support Ruby earlier than 2.3. This is due to a change in a dependency. This is (probably) the last release to support macOS earlier than 10.14.4 without the *Swift 5 Runtime Support for Command Line Tools* package installed. This is a consequence of Swift 5 ABI stability. ##### Breaking * None. ##### Enhancements * Swift 5 support: suppress unwanted newlines and `deinit` declarations. [John Fairhurst](https://github.com/johnfairh) * Update JavaScript libraries: jQuery 3.3.1 (all themes), Lunr 2.3.5, typeahead.js 1.2.1 (`fullwidth` theme only). [John Fairhurst](https://github.com/johnfairh) [#901](https://github.com/realm/jazzy/issues/901) * Avoid `clean build` when using the new Xcode build system. [Norio Nomura](https://github.com/norio-nomura) ##### Bug Fixes * None. ## 0.9.5 ##### Breaking * None. ##### Enhancements * Link to documentation pages from contents pages. [John Fairhurst](https://github.com/johnfairh) [#730](https://github.com/realm/jazzy/issues/730) * Call out unavailable and deprecated Objective-C declarations. [Stefan Kieleithner](https://github.com/steviki) [John Fairhurst](https://github.com/johnfairh) [#843](https://github.com/realm/jazzy/issues/843) ##### Bug Fixes * Support Swift 4.2 with `--podspec`. [John Fairhurst](https://github.com/johnfairh) [#1015](https://github.com/realm/jazzy/issues/1015) * Fix multiline copyright for `apple` theme. [Fabien Lydoire](https://github.com/fabienlydoire) [John Fairhurst](https://github.com/johnfairh) [#1016](https://github.com/realm/jazzy/issues/1016) ## 0.9.4 ##### Breaking * None. ##### Enhancements * None. ##### Bug Fixes * Fix crash with pre-existing `Docs` directory. [John Fairhurst](https://github.com/johnfairh) [#965](https://github.com/realm/jazzy/issues/965) * Fix crash with unicode scalars in string literals. [John Fairhurst](https://github.com/johnfairh) [#972](https://github.com/realm/jazzy/issues/972) * Fix error compiling a Swift podspec in Xcode 10. [Minh Nguyễn](https://github.com/1ec5) [#970](https://github.com/realm/jazzy/issues/970) ## 0.9.3 ##### Breaking * None. ##### Enhancements * None. ##### Bug Fixes * Fix crash when specifying empty Swift version. Now correctly uses the default Swift version. [JP Simard](https://github.com/jpsim) * Fix jony theme selection. [John Fairhurst](https://github.com/johnfairh) [#962](https://github.com/realm/jazzy/issues/962) ## 0.9.2 ##### Breaking * None. ##### Enhancements * Add a new 'jony' theme similar to the 2017 Apple documentation style. [Harshil Shah](https://github.com/HarshilShah) * Add the ability to limit documentation to certain files by passing in an `-i`/`--include` argument. [Nick Fox](https://github.com/nicholasffox) [#949](https://github.com/realm/jazzy/issues/949) * Improve Swift declarations to look more like the Xcode Quick Help version, for example including `{ get set }`, and include all attributes. [John Fairhurst](https://github.com/johnfairh) [#768](https://github.com/realm/jazzy/issues/768) [#591](https://github.com/realm/jazzy/issues/591) ##### Bug Fixes * Preserve `MARK` comment headings associated with extensions and enum cases. [John Fairhurst](https://github.com/johnfairh) * Fix issue where Overview items were invalidly being referenced with NULL types in the generated Dash docset index. [Andrew De Ponte](https://github.com/cyphactor) * Don't display FIXME or TODO comments as section markers. [John Fairhurst](https://github.com/johnfairh) [#658](https://github.com/realm/jazzy/issues/658) ## 0.9.1 ##### Breaking * None. ##### Enhancements * Added a config option (`--undocumented-text UNDOCUMENTED_TEXT`) to set the default text for undocumented symbols. [Akhil Batra](https://github.com/akhillies) [#913](https://github.com/realm/jazzy/issues/913) * Added a config option to hide Objective-C or Swift declarations: `--hide-declarations [objc|swift]`. [Ibrahim Ulukaya](https://github.com/ulukaya) [#828](https://github.com/realm/jazzy/issues/828) * Automatically use Swift or Objective-C syntax highlighting for code blocks in documentation comments. Improve Swift highlighting with latest Rouge. [John Fairhurst](https://github.com/johnfairh) [#218](https://github.com/realm/jazzy/issues/218) ##### Bug Fixes * Fix Swift declarations when generating Objective-C docs for generic types. [John Fairhurst](https://github.com/johnfairh) [#910](https://github.com/realm/jazzy/issues/910) * Don't create documentation nodes for generic type parameters. [John Fairhurst](https://github.com/johnfairh) [#878](https://github.com/realm/jazzy/issues/878) ## 0.9.0 ##### Breaking * Generate documentation coverage badge locally. Since this avoids the failable HTTP request to shields.io previously used to obtain the badge, we've removed the `--[no-]download-badge` flag and the corresponding `download_badge` YAML configuration key. [Samuel Giddins](https://github.com/segiddins) ##### Enhancements * None. ##### Bug Fixes * Fixed issue that prevented Jazzy from running on case sensitive file systems. [Jeremy David Giesbrecht](https://github.com/SDGGiesbrecht) [#891](https://github.com/realm/jazzy/issues/891) * Fixed issue preventing `--podspec` from working with `test_spec`s. [John Fairhurst](https://github.com/johnfairh) [#894](https://github.com/realm/jazzy/issues/894) * Always display correct declaration for undocumented symbols. [John Fairhurst](https://github.com/johnfairh) [#864](https://github.com/realm/jazzy/issues/864) * Trim common indentation in multiline declarations. [John Fairhurst](https://github.com/johnfairh) [#836](https://github.com/realm/jazzy/issues/836) ## 0.8.4 ##### Breaking * None. ##### Enhancements * Align jazzy terminology with Apple usage. [Xiaodi Wu](https://github.com/xwu) [John Fairhurst](https://github.com/johnfairh) * Add `url` attribute that can be more accurate than `{{section}}.html` as a URL in custom templates. [John Fairhurst](https://github.com/johnfairh) ##### Bug Fixes * Fix crash when specifying `swift_version` as a floating point value in `.jazzy.yaml` rather than a string. [JP Simard](https://github.com/jpsim) [#860](https://github.com/realm/jazzy/issues/860) * Autolink from parameter documentation and from external markdown documents including README. Autolink to symbols containing & < >. [John Fairhurst](https://github.com/johnfairh) [#715](https://github.com/realm/jazzy/issues/715) [#789](https://github.com/realm/jazzy/issues/789) [#805](https://github.com/realm/jazzy/issues/805) * Fix Swift 4 declarations containing ampersands (`&`) being truncated. [JP Simard](https://github.com/jpsim) ## 0.8.3 ##### Breaking * None. ##### Enhancements * Generate Swift declaration for more Objective-C declarations. [Zheng Li](https://github.com/ainopara) * Improve quality & accuracy of Swift interfaces for Objective-C declarations when generating Objective-C docs. [Norio Nomura](https://github.com/norio-nomura) * Process Swift 3.2/4 doc comments. [John Fairhurst](https://github.com/johnfairh) ##### Bug Fixes * Fix missing doc comments on some extensions. [John Fairhurst](https://github.com/johnfairh) [#454](https://github.com/realm/jazzy/issues/454) * Fix failure when attempting to download documentation coverage badge with jazzy using macOS system Ruby, or a Ruby built with outdated versions of OpenSSL. [JP Simard](https://github.com/jpsim) [#824](https://github.com/realm/jazzy/issues/824) * Stop `--skip-undocumented` from skipping documented items nested inside extensions of types from other modules. [John Fairhurst](https://github.com/johnfairh) [#502](https://github.com/realm/jazzy/issues/502) * Fix members added to extensions of a nested type showing up in the parent. [John Fairhurst](https://github.com/johnfairh) [#333](https://github.com/realm/jazzy/issues/333) ## 0.8.2 ##### Breaking * None. ##### Enhancements * Report number of included and skipped declarations in CLI output. [John Fairhurst](https://github.com/johnfairh) [#238](https://github.com/realm/jazzy/issues/238) * Build ObjC docs with clang modules enabled by default (`-fmodules` flag). [Maksym Grebenets](https://github.com/mgrebenets) [#636](https://github.com/realm/jazzy/issues/636) * Shave ~1MB from jazzy's gem distribution. [JP Simard](https://github.com/jpsim) ##### Bug Fixes * Fix support for Ruby 2.2. [John Fairhurst](https://github.com/johnfairh) [#801](https://github.com/realm/jazzy/issues/801) * Fix many cases of incorrect, missing or superfluous docs on Swift declarations. [John Fairhurst](https://github.com/johnfairh) ## 0.8.1 ##### Breaking * None. ##### Enhancements * Allow all markdown in returns and parameter description callouts. [John Fairhurst](https://github.com/johnfairh) [#476](https://github.com/realm/jazzy/issues/476) ##### Bug Fixes * Fix a crash that occurred when a documentation comment ended with an extended grapheme cluster. [Lukas Stührk](https://github.com/Lukas-Stuehrk) [#794](https://github.com/realm/jazzy/issues/794) [SourceKitten#350](https://github.com/jpsim/SourceKitten/issues/350) ## 0.8.0 ##### Breaking * `undocumented.json` is now only in the output directory and is no longer copied into docsets. [Jeremy David Giesbrecht](https://github.com/SDGGiesbrecht) [#754](https://github.com/realm/jazzy/issues/754) ##### Enhancements * Add `--[no-]download-badge` flag to skip downloading the documentation coverage badge from shields.io. Useful if generating docs offline. [JP Simard](https://github.com/jpsim) [#765](https://github.com/realm/jazzy/issues/765) ##### Bug Fixes * Blank line no longer needed before lists or code blocks. [John Fairhurst](https://github.com/johnfairh) [#546](https://github.com/realm/jazzy/issues/546) * Linking to headers in apple theme gives correct vertical alignment. [John Fairhurst](https://github.com/johnfairh) * Headers in source code markdown no longer cause corruption. [John Fairhurst](https://github.com/johnfairh) [#628](https://github.com/realm/jazzy/issues/628) ## 0.7.5 ##### Breaking * None. ##### Enhancements * None. ##### Bug Fixes * Fix issue where using a custom theme would crash jazzy when using Ruby 2.4. [Jason Wray](https://github.com/friedbunny) [#752](https://github.com/realm/jazzy/issues/752) * Fix support for Ruby 2.0.0. [Jason Wray](https://github.com/friedbunny) [#747](https://github.com/realm/jazzy/issues/747) * Fix issue where header files are not found if inside subdirectories of the framework_root specified folder. [Christopher Gretzki](https://github.com/gretzki) [#518](https://github.com/realm/jazzy/issues/518) ## 0.7.4 ##### Breaking * None. ##### Enhancements * Generate shields.io badge for documentation coverage, unless `hide_documentation_coverage` is set. [Harlan Haskins](https://github.com/harlanhaskins) [#723](https://github.com/realm/jazzy/issues/723) * Add support for searching docs when using the `fullwidth` theme. A new option, `--disable-search`, lets you turn this off. [Esad Hajdarevic](https://github.com/esad) [Tom MacWright](https://github.com/tmcw) [Nadia Barbosa](https://github.com/captainbarbosa) [#14](https://github.com/realm/jazzy/issues/14) * New config option `use_safe_filenames` encodes unsafe characters when generating filenames. By default, documentation may receive filenames like `/(_:_:).html`. With `use_safe_filenames`, the same file will receive the name `_2F_28_5F_3A_5F_3A_29.html` instead. [Jeremy David Giesbrecht](https://github.com/SDGGiesbrecht) [#699](https://github.com/realm/jazzy/issues/699) [#146](https://github.com/realm/jazzy/issues/146) [#361](https://github.com/realm/jazzy/issues/361) [#547](https://github.com/realm/jazzy/issues/547) * References to Objective-C methods are now autolinked. [Minh Nguyễn](https://github.com/1ec5) [#362](https://github.com/realm/jazzy/issues/362) * Print documentation coverage percentage and the number of undocumented methods to the command line when running jazzy. [Jason Wray](https://github.com/friedbunny) ##### Bug Fixes * Fix issue where existing abstracts for non custom sections would be completely overwritten when using extra abstract injection with --abstract. [Thibaud Robelain](https://github.com/thibaudrobelain) [#600](https://github.com/realm/jazzy/issues/600) * Fix issue where generic type parameters registered as undocumented symbols. [Jeremy David Giesbrecht](https://github.com/SDGGiesbrecht) [#429](https://github.com/realm/jazzy/issues/429) * Fix issue where parameter and return callouts were duplicated in documentation. [Jeremy David Giesbrecht](https://github.com/SDGGiesbrecht) [#673](https://github.com/realm/jazzy/issues/673) * Fix issue where Objective-C superclass in declaration was unlinked. [Minh Nguyễn](https://github.com/1ec5) [#706](https://github.com/realm/jazzy/issues/706) * Fix issue where multiple Objective-C categories of the same external class in different files were merged into one and named after the first category found. [Minh Nguyễn](https://github.com/1ec5) [#539](https://github.com/realm/jazzy/issues/539) * String literals in code listings are no longer wrapped in `` tags (`apple` and `fullwidth` themes only). [Minh Nguyễn](https://github.com/1ec5) [#714](https://github.com/realm/jazzy/issues/714) * Fix issue where passing a `--podspec` argument would use a malformed `SWIFT_VERSION` value, causing compilation to fail. [JP Simard](https://github.com/jpsim) ## 0.7.3 ##### Breaking * None. ##### Enhancements * Podspec-based documentation will take trunk's `pushed_with_swift_version` attribute into account when generating documentation by default. [Orta Therox](https://github.com/orta) * Podspec-based documentation respects the `swift-version` config option. [Orta Therox](https://github.com/orta) ##### Enhancements * Support Objective-C class properties. [Jérémie Girault](https://github.com/jeremiegirault) [JP Simard](https://github.com/jpsim) * Support documenting Swift 3 operator precedence groups. [JP Simard](https://github.com/jpsim) ##### Bug Fixes * Rename Dash typedef type from "Alias" to "Type". [Bogdan Popescu](https://github.com/Kapeli) * Fix crash when sorting multiple identically named declarations with no USR, which is very common when generating docs for podspecs supporting multiple platforms. [JP Simard](https://github.com/jpsim) [#661](https://github.com/realm/jazzy/issues/661) * Fix Xcode not being found when specifying a custom Swift version (`--swift-version`). [Samuel Giddins](https://github.com/segiddins) [Paul Cantrell](https://github.com/pcantrell) [#656](https://github.com/realm/jazzy/issues/656) * Fix crash when generating Objective-C docs for projects with "@" directives in documentation comments with Xcode 8.1 or later. [Jérémie Girault](https://github.com/jeremiegirault) ## 0.7.2 ##### Breaking * None. ##### Enhancements * None. ##### Bug Fixes * Declarations marked `@available(..., unavailable, ...)` are no longer documented. [JP Simard](https://github.com/jpsim) [#654](https://github.com/realm/jazzy/issues/654) * Treat the `open` ACL as more public than `public`. [JP Simard](https://github.com/jpsim) ## 0.7.1 ##### Breaking * None. ##### Enhancements * Added support for the new access control specifiers of fileprivate and open. [Shmuel Kallner](https://github.com/shmuelk) [#645](https://github.com/realm/jazzy/issues/645) [#646](https://github.com/realm/jazzy/issues/646) ##### Bug Fixes * Fix issue where jazzy could not be installed from Gemfile due to SourceKitten symlinks already being present. [William Meleyal](https://github.com/meleyal) [#438](https://github.com/realm/jazzy/issues/438) * The lint report in `undocumented.json` is more human-readable: includes fully qualified symbol names, pretty printed. [Paul Cantrell](https://github.com/pcantrell) [#598](https://github.com/realm/jazzy/issues/598) * The `exclude` option now properly supports wildcards. [Paul Cantrell](https://github.com/pcantrell) [#640](https://github.com/realm/jazzy/issues/640) ## 0.7.0 ##### Breaking * The `docset_platform` option is no longer available. The module name will now be used instead of `jazzy`. [JP Simard](https://github.com/jpsim) [#423](https://github.com/realm/jazzy/issues/423) ##### Enhancements * Improved auto-linking behavior to link declarations within declarations and fix cases where declarations would link to themselves or their current page. [Esad Hajdarevic](https://github.com/esad) [#483](https://github.com/realm/jazzy/issues/483) ##### Bug Fixes * Fix issue where single-line declaration + bodies in Swift would include the body in the parsed declaration. [JP Simard](https://github.com/jpsim) [#226](https://github.com/realm/jazzy/issues/226) * Fix issue where some sections would become empty when using custom groups. [JP Simard](https://github.com/jpsim) [#475](https://github.com/realm/jazzy/issues/475) * Fix issue where directories ending with `.swift` would be considered Swift source files. [JP Simard](https://github.com/jpsim) [#586](https://github.com/realm/jazzy/issues/586) ## 0.6.3 ##### Breaking * None. ##### Enhancements * `--exclude` flag now supports excluding directories in addition to files. [Gurrinder](https://github.com/gurrinder) [#503](https://github.com/realm/jazzy/issues/503) * The `cocoapods` gem was updated to 1.0.1 and `rouge` to 1.11.0. [Samuel Giddins](https://github.com/segiddins) [#568](https://github.com/realm/jazzy/issues/568) * Extra markdown documentation can now be included as their own pages in the sidebar using the `--documentation` option and in the generated Dash docset as Guides. [Karl Bowden](https://github.com/agentk) [#435](https://github.com/realm/jazzy/issues/435) * Section headings can now include additional markdown content using the `--abstract` option. [Karl Bowden](https://github.com/agentk) [#435](https://github.com/realm/jazzy/issues/435) * If Swift version is not specified, look for Swift toolchain or clang location in the following order: * `$XCODE_DEFAULT_TOOLCHAIN_OVERRIDE` * `$TOOLCHAIN_DIR` * `xcrun -find swift` * `/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain` * `/Applications/Xcode-beta.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain` * `~/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain` * `~/Applications/Xcode-beta.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain` This will be especially useful once jazzy supports generating docs for Swift Package Manager modules with a toolchain not tied to an Xcode release. [JP Simard](https://github.com/jpsim) ##### Bug Fixes * Don't document clang-unexposed Objective-C declarations. [JP Simard](https://github.com/jpsim) [#573](https://github.com/realm/jazzy/issues/573) ## 0.6.2 ##### Breaking * None. ##### Enhancements * Include one level of nested classes, structs, protocols and enums in the navigation bar. [JP Simard](https://github.com/jpsim) [#64](https://github.com/realm/jazzy/issues/64) ##### Bug Fixes * None. ## 0.6.1 ##### Breaking * None. ##### Enhancements * Objective-C documentation now also includes Swift declarations. [JP Simard](https://github.com/jpsim) [#136](https://github.com/realm/jazzy/issues/136) * Default to the Xcode version selected in `xcode-select` if no Swift version is specified. [Samuel Giddins](https://github.com/segiddins) [#427](https://github.com/realm/jazzy/issues/427) ##### Bug Fixes * Uses GitHub-Flavored Markdown syntax for anchors when rendering README pages. [Zachary Waldowski](https://github.com/zwaldowski) [#524](https://github.com/realm/jazzy/issues/524) * Fix crash when using unexposed declarations in Objective-C. [JP Simard](https://github.com/jpsim) [#543](https://github.com/realm/jazzy/issues/543) * No longer document Swift extensions on types with an ACL lower than `min-acl` when they contain `MARK`s. [JP Simard](https://github.com/jpsim) [#544](https://github.com/realm/jazzy/issues/544) ## 0.6.0 ##### Breaking * Config files now use the same option names as the command line. If you are using one of the keys that has changed in your `.jazzy.yaml`, you will receive a warning. See the [pull request](https://github.com/realm/jazzy/pull/456) for a complete list of changed options. As always, you can get a list of all options with `jazzy --help config`. [Paul Cantrell](https://github.com/pcantrell) * Jazzy's undocumented.txt has been replaced with undocumented.json. This new format includes contextual information that one might use to lint documentation in an automated fashion. [Jeff Verkoeyen](https://github.com/jverkoey) * `--swift-version` now defaults to 2.2 instead of 2.1.1. [Tamar Nachmany](https://github.com/tamarnachmany) ##### Enhancements * Add `--skip-documentation` flag. Skips site generation phase. `undocumented.json` is still generated. [Jeff Verkoeyen](https://github.com/jverkoey) * Merge Objective-C categories into their parent type documentation to match Swift behavior. [Esad Hajdarevic](https://github.com/esad) [#457](https://github.com/realm/jazzy/issues/457) * Add support for documenting Swift 2.2 `associatedtype`s and infix, postfix & prefix operators. [JP Simard](https://github.com/jpsim) ##### Bug Fixes * Add support for Objective-C module imports. [JP Simard](https://github.com/jpsim) [#452](https://github.com/realm/jazzy/issues/452) * Workaround for an apparent SourceKit bug which sometimes caused extensions to be merged into the wrong type. [Paul Cantrell](https://github.com/pcantrell) [#459](https://github.com/realm/jazzy/issues/459) [#460](https://github.com/realm/jazzy/issues/460) ## 0.5.0 ##### Breaking * `--swift-version` now defaults to 2.1.1 instead of 2.1. [Nikita Lutsenko](https://github.com/nlutsenko) [#416](https://github.com/realm/jazzy/pull/416) * Swift 1.x is no longer supported. * `--templates-directory` and `--assets-directory` have been deprecated in favor of `--theme`. Specify either 'apple' (default), 'fullwidth' or the path to your mustache templates and other assets for a custom theme. [Karl Bowden](https://github.com/agentk) [JP Simard](https://github.com/jpsim) [#130](https://github.com/realm/jazzy/issues/130) ##### Enhancements * Add `--sdk [iphone|watch|appletv][os|simulator]|macosx` option for Objective-C projects. [Jeff Verkoeyen](https://github.com/jverkoey) * Add `--head` option to inject custom HTML into ``. [JP Simard](https://github.com/jpsim) ##### Bug Fixes * Fix an issue where extension documentation would use the original type documentation block rather than the comment immediately preceding the extension. [JP Simard](https://github.com/jpsim) [#230](https://github.com/realm/jazzy/issues/230) [#313](https://github.com/realm/jazzy/issues/313) [#334](https://github.com/realm/jazzy/issues/334) * Fix multi-byte documentation issues. [Norio Nomura](https://github.com/norio-nomura) [#403](https://github.com/realm/jazzy/issues/403) ## 0.4.1 *Note: this is the last official release of jazzy supporting Swift 1.x.* ##### Breaking * None. ##### Enhancements * Support "wall of asterisk" documentation comments. [Jeff Verkoeyen](https://github.com/jverkoey) [#347](https://github.com/realm/jazzy/issues/347) * Expanding a token no longer causes the document to 'jump' to the hash. [Jeff Verkoeyen](https://github.com/jverkoey) [#352](https://github.com/realm/jazzy/issues/352) * Autolinking improvements: - Autolinks only match `` `ThingsInBackticks` ``, and must match the entire string. This prevents spurious matching in prose and sample code. - Autolinks supports siblings, ancestors, top-level elements, and dot-separated chains starting with any of the above: `someProperty`, `SomeType.NestedType.someMethod(_:)`. - New `...` wildcard prevents you from having to list all method parameters: `someMethod(...)` [Paul Cantrell](https://github.com/pcantrell) [#327](https://github.com/realm/jazzy/issues/327) [#329](https://github.com/realm/jazzy/issues/329) [#359](https://github.com/realm/jazzy/issues/359) * Miscellaneous minor font size, weight, and color adjustments. [Jeff Verkoeyen](https://github.com/jverkoey) * In-page anchors now appear below the header. [Jeff Verkoeyen](https://github.com/jverkoey) ##### Bug Fixes * Fix an out-of-bounds exception when generating pragma marks. [JP Simard](https://github.com/jpsim) [#370](https://github.com/realm/jazzy/issues/370) * Add support for C/C++ struct, field & ivar types. [JP Simard](https://github.com/jpsim) [#374](https://github.com/realm/jazzy/issues/374) [#387](https://github.com/realm/jazzy/issues/387) * Links to source files on GitHub are no longer broken when `source_directory` does not point to the current working directory. [Paul Cantrell](https://github.com/pcantrell) * When `excluded_files` is specified in a config file, it is now resolved relative to the file (like other options) instead of relative to the working directory. [Paul Cantrell](https://github.com/pcantrell) ## 0.4.0 ##### Breaking * `--swift-version` now defaults to 2.1 instead of 2.0. [JP Simard](https://github.com/jpsim) ##### Enhancements * Support for documenting Objective-C projects! 🎉 Pass `--objc`, `--umbrella-header ...` and `-framework-root ...`. [JP Simard](https://github.com/jpsim) [#56](https://github.com/realm/jazzy/issues/56) * Mentions of top-level declarations in documentation comments are now automatically hyperlinked to their reference. [JP Simard](https://github.com/jpsim) * Jazzy can now read options from a configuration file. The command line provides comprehensive help for available options via `jazzy -h config`. [Paul Cantrell](https://github.com/pcantrell) [#310](https://github.com/realm/jazzy/pull/310) * Render special list items (e.g. Throws, See, etc.). See https://ericasadun.com/2015/06/14/swift-header-documentation-in-xcode-7/ for a complete list. [JP Simard](https://github.com/jpsim) [#317](https://github.com/realm/jazzy/issues/317) * Support for Swift 2.1. [JP Simard](https://github.com/jpsim) * Swift extensions are now merged with their extended type, rendering a note to describe extension default implementations and extension methods. [Paul Cantrell](https://github.com/pcantrell) ##### Bug Fixes * None. ## 0.3.2 ##### Breaking * None. ##### Enhancements * None. ##### Bug Fixes * Fixed an issue that prevented building projects with different schema & module names. [JP Simard](https://github.com/jpsim) [#259](https://github.com/realm/jazzy/issues/259) * Hide documentation coverage from header using `--hide-documentation-coverage`. [mbogh](https://github.com/mbogh) [#129](https://github.com/realm/jazzy/issues/297) * Print a more informative error when unable to find an Xcode that has the requested Swift version. [Samuel Giddins](https://github.com/segiddins) ## 0.3.1 ##### Breaking * None. ##### Enhancements * None. ##### Bug Fixes * Added missing Swift 2 declaration types. [JP Simard](https://github.com/jpsim) ## 0.3.0 ##### Breaking * `--swift-version` now defaults to 2.0 instead of 1.2. [JP Simard](https://github.com/jpsim) ##### Enhancements * Now supports Swift 2.0 (previous Swift versions are still supported). [JP Simard](https://github.com/jpsim) [Samuel Giddins](https://github.com/segiddins) * Declarations can now be grouped by custom categories defined in a JSON or YAML file passed to `--categories`. [Paul Cantrell](https://github.com/pcantrell) ##### Bug Fixes * "View on GitHub" is now only generated if a GitHub URL is specified. [mbogh](https://github.com/mbogh) [#244](https://github.com/realm/jazzy/issues/244) * Empty extensions are no longer documented. [Paul Cantrell](https://github.com/pcantrell) * Undocumented enum cases are now supported. [JP Simard](https://github.com/jpsim) [#74](https://github.com/realm/jazzy/issues/74) ## 0.2.4 ##### Breaking * None. ##### Enhancements * Improved how SourceKitten is vendored. [JP Simard](https://github.com/jpsim) * Show type declaration under its title. [Paul Cantrell](https://github.com/pcantrell) * Added support for custom assets: pass `--assets-directory` to jazzy. [gurkendoktor](https://github.com/gurkendoktor) * Added support for custom copyright text: pass `--copyright` to jazzy. [emaloney](https://github.com/emaloney) ##### Bug Fixes * Fixed a crash when parsing an empty documentation comment. [JP Simard](https://github.com/jpsim) [#236](https://github.com/realm/jazzy/issues/236) * `--exclude` now works properly if its argument is a relative path. [Paul Cantrell](https://github.com/pcantrell) ## 0.2.3 ##### Breaking * None. ##### Enhancements * The `jazzy` CLI now accepts a `--swift-version` option (defaulting to 1.2), and will automatically find an appropriate Xcode installation. [Samuel Giddins](https://github.com/segiddins) [#214](https://github.com/realm/jazzy/issues/214) ##### Bug Fixes * Declarations with no USR will no longer be documented. [JP Simard](https://github.com/jpsim) ## 0.2.2 ##### Breaking * None. ##### Enhancements * Added support for custom templates: use the `-t`/`--template-directory` argument to jazzy. [JP Simard](https://github.com/jpsim) [#20](https://github.com/realm/jazzy/issues/20) ##### Bug Fixes * None. ## 0.2.1 ##### Breaking * None. ##### Enhancements * Added the ability to ignore certain files by passing in an `-e`/`--exclude` argument to jazzy. [JP Simard](https://github.com/jpsim) [#173](https://github.com/realm/jazzy/issues/173) ##### Bug Fixes * None. ## 0.2.0 ##### Breaking * Jazzy now only supports projects using Swift 1.2. [JP Simard](https://github.com/jpsim) [#170](https://github.com/realm/jazzy/issues/170) * Increase default minimum ACL to public. [JP Simard](https://github.com/jpsim) [#186](https://github.com/realm/jazzy/issues/186) ##### Enhancements * Use `key.accessibility` to determine ACL (value coming from SourceKit, which is generally more accurate than parsing the declaration for an accessibility keyword). [JP Simard](https://github.com/jpsim) [#185](https://github.com/realm/jazzy/issues/185) ##### Bug Fixes * None. ## 0.1.6 ##### Breaking * None. ##### Enhancements * None. ##### Bug Fixes * Make the gem installable. [Samuel Giddins](https://github.com/segiddins) ## 0.1.5 ##### Breaking * None. ##### Enhancements * Added `--readme` command line option. [segiddins](https://github.com/segiddins) [#196](https://github.com/realm/jazzy/issues/196) * Cleaned up front end HTML & CSS. [JP Simard](https://github.com/jpsim) [#95](https://github.com/realm/jazzy/issues/95) * "Show on GitHub" links now link to line-ranges for multi-line definitions. [JP Simard](https://github.com/jpsim) [#198](https://github.com/realm/jazzy/issues/198) ##### Bug Fixes * Fixed issue where docset would contain duplicate files. [JP Simard](https://github.com/jpsim) [#204](https://github.com/realm/jazzy/issues/204) * Fixed installation issues on case-sensitive file systems. [kishikawakatsumi](https://github.com/kishikawakatsumi) * Fixed out-of-bounds exception when parsing the declaration in files starting with a declaration. [JP Simard](https://github.com/jpsim) [#30](https://github.com/jpsim/SourceKitten/issues/30) * Fixed out-of-bounds exception and inaccurate parsed declarations when using multibyte characters. [JP Simard](https://github.com/jpsim) [#35](https://github.com/jpsim/SourceKitten/issues/35) * Fixed parsing issues with keyword functions such as `subscript`, `init` and `deinit`. [JP Simard](https://github.com/jpsim) [#27](https://github.com/jpsim/SourceKitten/issues/27) * Fixed issues where USR wasn't accurate because dependencies couldn't be resolved. [JP Simard](https://github.com/jpsim) * Allow using a version of Xcode that is symlinked to `/Applications/Xcode.app`. [Samuel Giddins](https://github.com/segiddins) ## 0.1.4 ##### Breaking * None. ##### Enhancements * None. ##### Bug Fixes * No longer count undocumented externally declared tokens as undocumented. [JP Simard](https://github.com/jpsim) [#188](https://github.com/realm/jazzy/issues/188) ## 0.1.3 ##### Breaking * None. ##### Enhancements * Improve the styling of `dl` elements (parsed key-value pairs). [segiddins](https://github.com/segiddins) * Raise exceptions if Xcode requirements aren't met. [JP Simard](https://github.com/jpsim) ##### Bug Fixes * No longer count initializers with parameters as undocumented. [JP Simard](https://github.com/jpsim) [#183](https://github.com/realm/jazzy/issues/183) * No longer crash when a token is missing a USR. [JP Simard](https://github.com/jpsim) [#171](https://github.com/realm/jazzy/issues/171) * Fixed encoding issues in some environments. [James Barrow](https://github.com/Baza207) [#152](https://github.com/realm/jazzy/issues/152) * No longer count undocumented externally declared tokens as undocumented. [JP Simard](https://github.com/jpsim) [#188](https://github.com/realm/jazzy/issues/188) * Fixed `--source-directory` CLI option. [JP Simard](https://github.com/jpsim) [#177](https://github.com/realm/jazzy/issues/177) ## 0.1.2 ##### Breaking * None. ##### Enhancements * Use Menlo for code everywhere. [beltex](https://github.com/beltex) [#137](https://github.com/realm/jazzy/issues/137) ##### Bug Fixes * (Really) fixes installation as a RubyGem. [beltex](https://github.com/beltex) [#159](https://github.com/realm/jazzy/issues/159) ## 0.1.1 ##### Breaking * None. ##### Enhancements * None. ##### Bug Fixes * Fixes installation as a RubyGem. [Samuel Giddins](https://github.com/segiddins) [#159](https://github.com/realm/jazzy/issues/159) ## 0.1.0 [sourcekitten](https://github.com/jpsim/sourcekitten/compare/0.2.7...0.3.1) ##### Breaking * None. ##### Enhancements * Add the ability to document a Pod from just a podspec, which allows jazzy to run on cocoadocs.org. [Samuel Giddins](https://github.com/segiddins) [#58](https://github.com/realm/jazzy/issues/58) ##### Bug Fixes * De-duplicate the sidebar list of extensions and show all children for an extension, regardless of how many extensions on a type there are. [Samuel Giddins](https://github.com/segiddins) ## 0.0.20 [sourcekitten](https://github.com/jpsim/sourcekitten/compare/0.2.6...0.2.7) ##### Breaking * Don't skip declarations with no documentation comments by default. Allow skipping using `--skip-undocumented`. [JP Simard](https://github.com/jpsim) [#129](https://github.com/realm/jazzy/issues/129) ##### Enhancements * Combine abstract and discussion in page overview. [JP Simard](https://github.com/jpsim) [#115](https://github.com/realm/jazzy/issues/115) ##### Bug Fixes * Don't show 'Show in Github' link for types declared in system frameworks. [JP Simard](https://github.com/jpsim) [#110](https://github.com/realm/jazzy/issues/110) ## 0.0.19 [sourcekitten](https://github.com/jpsim/sourcekitten/compare/0.2.3...0.2.6) ##### Breaking None. ##### Enhancements * Added CHANGELOG.md. [JP Simard](https://github.com/jpsim) [#125](https://github.com/realm/jazzy/issues/125) * Include parse errors in the JSON output & print to STDERR. [JP Simard](https://github.com/jpsim) [jpsim/sourcekitten#16](https://github.com/jpsim/sourcekitten/issues/16) ##### Bug Fixes * Fixed crash when files contained a declaration on the first line. [JP Simard](https://github.com/jpsim) [jpsim/sourcekitten#14](https://github.com/jpsim/sourcekitten/issues/14) * Fixed invalid JSON issue when last file in an Xcode project failed to parse. [JP Simard](https://github.com/jpsim) * Fixed crash when attempting to parse the declaration of `extension Array`. [JP Simard](https://github.com/jpsim) [#126](https://github.com/realm/jazzy/issues/126) ================================================ FILE: CONTRIBUTING.md ================================================ ## Code of Conduct This project adheres to the [Contributor Covenant Code of Conduct](https://realm.io/conduct). By participating, you are expected to uphold this code. Please report unacceptable behavior to [info@realm.io](mailto:info@realm.io). ## Tracking changes All changes should be made via pull requests on GitHub. When issuing a pull request, please add a summary of your changes to the `CHANGELOG.md` file. We follow the same syntax as CocoaPods' CHANGELOG.md: 1. One Markdown unnumbered list item describing the change. 2. Two trailing spaces on the last line describing the change. 3. A list of Markdown hyperlinks to the contributors to the change. One entry per line (and there's usually just one contributor). 4. A list of Markdown hyperlinks to the issues the change addresses. One entry per line (and there's usually just one hyperlink). Don't link to PRs here. 5. All CHANGELOG.md content is hard-wrapped at 80 characters. ## Updating the integration specs Jazzy heavily relies on integration tests, but since they're considerably large and noisy, we keep them in a separate repo ([realm/jazzy-integration-specs](https://github.com/realm/jazzy-integration-specs)). If you're making a PR towards jazzy that affects the generated docs, please update the integration specs using the following process: ```shell git checkout master git pull git checkout - git rebase master rake bootstrap bundle exec rake rebuild_integration_fixtures cd spec/integration_specs git checkout -b $jazzy_branch_name git commit -a -m "update for $jazzy_branch_name" git push cd ../../ git commit -a -m "update integration specs" git push ``` You'll need push access to the integration specs repo to do this. You can request access from one of the maintainers when filing your PR. You must have Xcode 26.1 beta 1 installed to build the integration specs. ## Making changes to SourceKitten When changes are landed in the https://github.com/jpsim/SourceKitten repo the SourceKitten framework located in jazzy must be updated. The following may be executed from your `jazzy/` directory. ``` cd SourceKitten git checkout master git pull cd .. rake sourcekitten git add . git commit -m "..." ``` ================================================ FILE: Dangerfile ================================================ # frozen_string_literal: true # Warn when there is a big PR warn('Big PR') if git.lines_of_code > 500 # Don't let testing shortcuts get into master by accident (git.modified_files + git.added_files - %w[Dangerfile]).each do |file| next unless File.file?(file) contents = File.read(file) if file.start_with?('spec') failure("`xit` or `fit` left in tests (#{file})") if contents =~ /^\w*[xf]it/ failure("`fdescribe` left in tests (#{file})") if contents =~ /^\w*fdescribe/ end end # Sometimes its a README fix, or something like that - which isn't relevant for # including in a CHANGELOG for example has_app_changes = !git.modified_files.grep(/lib/).empty? has_test_changes = !git.modified_files.grep(/spec/).empty? # Add a CHANGELOG entry for app changes if !git.modified_files.include?('CHANGELOG.md') && has_app_changes failure "Please include a CHANGELOG entry. \nYou can find it at [CHANGELOG.md](https://github.com/realm/jazzy/blob/master/CHANGELOG.md)." message 'Note, we hard-wrap at 80 chars and use 2 spaces after the last line.' end # Non-trivial amounts of app changes without tests if git.lines_of_code > 50 && has_app_changes && !has_test_changes warn 'This PR may need tests.' end ================================================ FILE: Gemfile ================================================ # frozen_string_literal: true source 'https://rubygems.org' gemspec group :development do # Code style gem 'rubocop', '~> 1.18' # Tests gem 'bacon' gem 'mocha' gem 'mocha-on-bacon' gem 'prettybacon' gem 'webmock' # Integration tests gem 'clintegracon', git: 'https://github.com/mrackwitz/CLIntegracon.git' gem 'diffy' # Code Review gem 'danger' end ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2014 Realm Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: ObjectiveC.md ================================================ This document is about Jazzy's Objective-C documentation generation. It's intended for users who are having problems after trying to follow the examples in the [README](README.md#objective-c). It gives some solutions to common problems and explains how the system works to help users work through uncommon problems. * [How it works](#how-objective-c-docs-generation-works) * Common problems: * [Apple SDK include failure](#problem-apple-sdk-importinclude-failure) * [Non-SDK include failure](#problem-non-sdk-include-failure) * [Argument list too long](#problem-argument-list-too-long-e2big-and-more) * [Enum cases with wrong doc comment](#problem-enum-cases-have-the-wrong-doc-comment) * [Swift API versions missing](#problem-swift-api-versions-are-all-missing) * [Swift API versions use `Any`](#problem-swift-api-versions-have-any-instead-of-type-name) * [Structural `NS_SWIFT_NAME` not working](#problem-structural-ns_swift_name-not-working) # How Objective-C docs generation works Jazzy uses `libclang` to generate docs for Objective-C projects. You can think of this as running some parts of the `clang` compiler against a header file. Jazzy refers to this header file as the _umbrella header_ but it does not have to be the umbrella header from a Clang module: it's just the header file that includes everything to be documented. This means there are two problems to solve: 1. What `clang` flags are required; and 2. How to pass them to the tools. Jazzy has two modes here: a smart mode that covers 90% of projects and a direct mode where the user provides all the flags. > *Important*: Jazzy does _not_ use any Objective-C build settings from your Xcode project or `Package.swift`. If your project needs special settings such as `#define`s then you need to repeat those in the Jazzy invocation. ## Direct mode Passing a basic set of `clang` flags looks like this: ```shell jazzy ... --objc --build-tool-arguments --objc,MyProject/MyProject.h,--,-x,objective-c, -isysroot,$(xcrun --show-sdk-path), -I,$(pwd), -fmodules ``` The `--build-tool-arguments` are arguments to `sourcekitten`. Everything after the `--` are the `clang` flags that will be used with the header file given before the `--`. You can try these flags outside of Jazzy's environment: ```shell clang -c -x objective-c -isysroot $(xcrun --show-sdk-path) -I $(pwd) MyProject/MyProject.h -fmodules ``` (The `-c` stops `clang` from trying to link an executable.) This is a good method of experimenting with compiler flags to get a working build without getting bogged down in the Jazzy and SourceKitten layers. ## Smart mode The smart mode takes the variable parts of the basic set of flags and maps them from Jazzy flags: ```shell jazzy ... --objc --umbrella-header MyProject/MyProject.h --framework-root $(pwd) [--sdk ] ``` The `--umbrella-header` maps directly to the file passed to `sourcekitten`. The `--framework-root` is used for the `-I` include path, as well as every directory beneath it, recursively. So if your project structure looks like: ``` MyProject/ Sources/ Main/ Extension/ ``` ... and you pass `--framework-root MyProject`, then the `-I` flags passed to `clang` are `-I MyProject -I MyProject/Sources -I MyProject/Sources/Main -I MyProject/Sources/Extension`. This feature helps some projects resolve `#include` directives. Finally the `--sdk` option is passed through instead of the default `macosx` to find the SDK. ## Mixing modes Do not mix modes. For example do not set both `--umbrella-header` and `--build-tool-arguments`. Jazzy does not flag this as an error for historical compatibility reasons, but the results are at best confusing. # Problem: Apple SDK import/include failure For example `fatal error: module 'UIKit' not found`. This means Jazzy is using the wrong SDK: the default is for macOS which does not have `UIKit`. Use `--sdk iphonesimulator`. # Problem: Non-SDK include failure For example `fatal error: 'MyModule/SomeHeader.h' file not found`. This means `clang` is not being passed the right flags to resolve a `#include` / `#import`. Start by finding the header file in the filesystem. You might be able to fix the problem just by adding extra `-I ` flags. Usually though the problem is that Xcode has done something clever that needs to be worked around or replicated. Xcode uses technology including Clang header maps to let files be found using lines like `#import ` even when there is no such filesystem directory. To make the Jazzy build work you need to make these `#include`s work. The solution depends on your project structure. Some suggestions in rough order of complexity: * Use symlinks to create an equivalent directory tree. For example if `Header.h` is inside `Sources/include` then symlink that directory to `ModuleName` and pass `-I $(pwd)`. * Copy/link your header files into a throwaway directory tree that matches the required structure and is used just for building docs. * Create a 'docs header file' separate to the framework's regular umbrella header that contains only filesystem-correct `#import`s. * If you are happy to build the framework project before generating docs and all the problematic paths have the form `ModuleName/PublicHeader.h` then have `clang` resolve those includes to the built framework by passing `-F `. * If you are happy to build the project before generating docs then you may be able to use the header maps Xcode has generated. Check the build log in Xcode to find them and the invocation syntax. * Manually construct an equivalent header map. This is complex not least because Apple does not make tools or proper documentation available. [An open-source starting point](https://milen.me/writings/swift-module-maps-vfs-overlays-header-maps/). # Problem: Argument list too long `E2BIG` (and more...) For example ``...open4.rb:49:in `exec': Argument list too long - [...]/bin/sourcekitten (Errno::E2BIG)`` Can also manifest as 'generally weird' errors such as `sourcekitten` just crashing and `fatal error: could not build module 'Foundation'`. This means `--framework-root` is trying to add too many include directories: there are too many subdirectories of its parameter. If you cannot change this to something more specific that works then you need to use Jazzy's [direct mode](#direct-mode) to pass in the correct directories. # Problem: Enum cases have the wrong doc comment If you write an enum case with a doc comment followed by an enum case without a doc comment, then both get the same doc comment. This seems to be a bug in `libclang`. The only workaround is to add the missing doc comment. # Problem: Swift API versions are all missing This usually means the `clang` flags are malformed in a way that is ignored by `libclang` but not by the Swift Objective-C importer. One easy way to accidentally do this is passing `-I` without a path, for example `--build-tool-flags ...,-I,-I,Headers`,.... This also sometimes happens if you are frequently switching back and forth between some Swift / Xcode versions -- it's a bug somewhere in the Apple tools. The bad state goes away with time / reboot. # Problem: Swift API versions have `Any` instead of type name Jazzy finds the Swift version of an Objective-C API using the SourceKit `source.request.editor.open.interface.header` request on the header file that contains the declaration, passing in the same `clang` flags used for the umbrella header. [See the code](https://github.com/jpsim/SourceKitten/blob/bed112c313ca8c3c149f8cb84069f1c080e86a7e/Source/SourceKittenFramework/Clang%2BSourceKitten.swift#L202). This means that each header file needs to compile standalone, without any implicit dependencies. For example: ``` MyModule.h // umbrella, includes MyClass.h then Utils.h MyClass.h // @interface MyClass ... @end Utils.h // void my_function( MyClass * myClass); ``` Here, `Utils.h` has an implicit dependency on `MyClass.h` that is normally satisfied by the include order of `MyModule.h`. One fix that allows `Utils.h` to compile standalone is to add `@class MyClass;`. # Problem: Structural `NS_SWIFT_NAME` not working The `NS_SWIFT_NAME` macro is mostly used to give an Objective-C API a different name in Swift. There are no known problems with this part. The macro can also be used to change the 'structure' of an API, for example make a C global function appear as a member function in Swift, or make a C class appear as a nested type in Swift. Jazzy doesn't understand or communicate these structural changes: you'll need to explain it in doc comments. ================================================ FILE: README.md ================================================ ![jazzy](images/logo.jpg) [![Build Status](https://github.com/realm/jazzy/actions/workflows/Tests.yml/badge.svg)](https://github.com/realm/jazzy/actions/workflows/Tests.yml) *jazzy is a command-line utility that generates documentation for Swift or Objective-C* ## About Both Swift and Objective-C projects are supported. Instead of parsing your source files, `jazzy` hooks into [Clang][clang] and [SourceKit][sourcekit] to use the [AST][ast] representation of your code and its comments for more accurate results. The output matches the look and feel of Apple’s official reference documentation, post WWDC 2014. Jazzy can also generate documentation from compiled Swift modules [using their symbol graph](#docs-from-swiftmodules-or-frameworks) instead of source code. ![Screenshot](images/screenshot.jpg) This project adheres to the [Contributor Covenant Code of Conduct](https://realm.io/conduct). By participating, you are expected to uphold this code. Please report unacceptable behavior to [info@realm.io](mailto:info@realm.io). ## Requirements You need development tools to build the project you wish to document. Jazzy supports both [Xcode][xcode] and [Swift Package Manager][spm] projects. Jazzy expects to be running on __macOS__. See [below](#linux) for tips to run Jazzy on Linux. ## Installation ```shell [sudo] gem install jazzy ``` See [Installation Problems](#installation-problems) for solutions to some common problems. ## Usage Run `jazzy` from your command line. Run `jazzy -h` for a list of additional options. If your Swift module is the first thing to build, and it builds fine when running `xcodebuild` or `swift build` without any arguments from the root of your project, then just running `jazzy` (without any arguments) from the root of your project should succeed too! If Jazzy generates docs for the wrong module then use `--module` to tell it which one you'd prefer. If this doesn't help, and you're using Xcode, then try passing extra arguments to `xcodebuild`, for example `jazzy --build-tool-arguments -scheme,MyScheme,-target,MyTarget`. If you want to generate docs for several modules at once then see [Documenting multiple modules](#documenting-multiple-modules). You can set options for your project’s documentation in a configuration file, `.jazzy.yaml` by default. For a detailed explanation and an exhaustive list of all available options, run `jazzy --help config`. ### Supported documentation keywords Swift documentation is written in markdown and supports a number of special keywords. Here are some resources with tutorials and examples, starting with the most modern: * Apple's [Writing Symbol Documentation in Your Source Files](https://developer.apple.com/documentation/xcode/writing-symbol-documentation-in-your-source-files) article. * Apple's [Formatting Your Documentation Content](https://developer.apple.com/documentation/xcode/formatting-your-documentation-content) article. * Apple's [Xcode Markup Formatting Reference](https://developer.apple.com/library/content/documentation/Xcode/Reference/xcode_markup_formatting_ref/). * Erica Sadun's [Swift header documentation in Xcode 7](https://ericasadun.com/2015/06/14/swift-header-documentation-in-xcode-7/) post and her [book on Swift Documentation Markup](https://itunes.apple.com/us/book/swift-documentation-markup/id1049010423). For Objective-C documentation the same keywords are supported, but note that the format is slightly different. In Swift you would write `- returns:`, but in Objective-C you write `@return`. See Apple's [*HeaderDoc User Guide*](https://developer.apple.com/legacy/library/documentation/DeveloperTools/Conceptual/HeaderDoc/tags/tags.html) for more details. **Note: `jazzy` currently does not support _all_ Objective-C keywords listed in this document, only @param, @return, @warning, @see, @note, @code, @endcode, and @c.** Jazzy can also generate cross-references within your documentation. A symbol name in backticks generates a link, for example: * \`MyClass\` - a link to documentation for `MyClass`. * \`MyClass.method(param1:)\` - a link to documentation for that method. * \`MyClass.method(...)\` - shortcut syntax for the same thing. * \`method(...)\` - shortcut syntax to link to `method` from the documentation of another method or property in the same class. * \`[MyClass method1]\` - a link to an Objective-C method. * \`-[MyClass method2:param1]\` - a link to another Objective-C method. Jazzy understands Apple's DocC-style links too, for example: * \`\`MyClass/method(param1:)\`\` - a link to the documentation for that method that appears as just `method(param1:)` in the rendered page. * \`\`\\`\` - a link to a specific overload of `method(_:)`. Jazzy can't tell which overload you intend and links to the first one. If your documentation is for multiple modules then symbol name resolution works approximately as though all the modules have been imported: you can use \`TypeName\` to refer to a top-level type in any of the modules, or \`ModuleName.TypeName\` to be specific. If there is an ambiguity then you can use a leading slash to indicate that the first part of the name should be read as a module name: \`/ModuleName.TypeName\`. ### Math Jazzy can render math equations written in LaTeX embedded in your markdown: * `` `$equation$` `` renders the equation in an inline style. * `` `$$equation$$` `` renders the equation in a display style, centered on a line of its own. For example, the markdown: ```markdown Inline: `$ax^2+bx+c=0$` Block: `$$x={\frac {-b\pm {\sqrt {b^{2}-4ac}}}{2a}}$$` ``` ..renders as: ![math](images/math.png) Math support is provided by [KaTeX](https://katex.org). ### Swift Swift documentation is generated by default. ##### Example This is how Realm Swift docs are generated: ```shell jazzy \ --clean \ --author Realm \ --author_url https://realm.io \ --source-host github \ --source-host-url https://github.com/realm/realm-cocoa \ --source-host-files-url https://github.com/realm/realm-cocoa/tree/v0.96.2 \ --module-version 0.96.2 \ --build-tool-arguments -scheme,RealmSwift \ --module RealmSwift \ --root-url https://realm.io/docs/swift/0.96.2/api/ \ --output docs/swift_output \ --theme docs/themes ``` This is how docs are generated for a project that uses the Swift Package Manager: ```shell jazzy \ --module DeckOfPlayingCards \ --swift-build-tool spm \ --build-tool-arguments -Xswiftc,-swift-version,-Xswiftc,5 ``` ### Objective-C To generate documentation for a simple Objective-C project, you must pass the following parameters: * `--objc` * `--umbrella-header ...` * `--framework-root ...` ...and optionally: * `--sdk [iphone|watch|appletv][os|simulator]|macosx` (default value of `macosx`) * `--hide-declarations [objc|swift]` (hides the selected language declarations) For example, this is how the `AFNetworking` docs are generated: ```shell jazzy \ --objc \ --author AFNetworking \ --author_url http://afnetworking.com \ --source-host github \ --source-host-url https://github.com/AFNetworking/AFNetworking \ --source-host-files-url https://github.com/AFNetworking/AFNetworking/tree/2.6.2 \ --module-version 2.6.2 \ --umbrella-header AFNetworking/AFNetworking.h \ --framework-root . \ --module AFNetworking ``` For a more complicated Objective-C project, instead use `--build-tool-arguments` to pass arbitrary compiler flags. For example, this is how Realm Objective-C docs are generated: ```shell jazzy \ --objc \ --clean \ --author Realm \ --author_url https://realm.io \ --source-host github \ --source-host-url https://github.com/realm/realm-cocoa \ --source-host-files-url https://github.com/realm/realm-cocoa/tree/v2.2.0 \ --module-version 2.2.0 \ --build-tool-arguments --objc,Realm/Realm.h,--,-x,objective-c,-isysroot,$(xcrun --show-sdk-path),-I,$(pwd) \ --module Realm \ --root-url https://realm.io/docs/objc/2.2.0/api/ \ --output docs/objc_output \ --head "$(cat docs/custom_head.html)" ``` See [the Objective-C docs](ObjectiveC.md) for more information and some tips on troubleshooting. ### Mixed Objective-C / Swift *This feature has some rough edges.* To generate documentation for a mixed Swift and Objective-C project you must first generate two [SourceKitten][sourcekitten] files: one for Swift and one for Objective-C. Then pass these files to Jazzy together using `--sourcekitten-sourcefile`. #### Example This is how docs are generated from an Xcode project for a module containing both Swift and Objective-C files: ```shell # Generate Swift SourceKitten output sourcekitten doc -- -workspace MyProject.xcworkspace -scheme MyScheme > swiftDoc.json # Generate Objective-C SourceKitten output sourcekitten doc --objc $(pwd)/MyProject/MyProject.h \ -- -x objective-c -isysroot $(xcrun --show-sdk-path --sdk iphonesimulator) \ -I $(pwd) -fmodules > objcDoc.json # Feed both outputs to Jazzy as a comma-separated list jazzy --module MyProject --sourcekitten-sourcefile swiftDoc.json,objcDoc.json ``` ### Docs from `.swiftmodule`s or frameworks Swift 5.3 added support for symbol graph generation from `.swiftmodule` files. Jazzy can use this to generate API documentation. This is faster than using the source code directly but does have limitations: for example documentation comments are available only for `public` declarations, and the presentation of Swift extensions may not match the way they are written in code. Some examples: 1. Generate docs for the Apple Combine framework for macOS: ```shell jazzy --module Combine --swift-build-tool symbolgraph ``` The SDK's library directories are included in the search path by default. 2. Same but for iOS: ```shell jazzy --module Combine --swift-build-tool symbolgraph --sdk iphoneos --build-tool-arguments -target,arm64-apple-ios14.1 ``` The `target` is the LLVM target triple and needs to match the SDK. The default here is the target of the host system that Jazzy is running on, something like `x86_64-apple-darwin19.6.0`. 3. Generate docs for a personal `.swiftmodule`: ```shell jazzy --module MyMod --swift-build-tool symbolgraph --build-tool-arguments -I,/Build/Products ``` This implies that `/Build/Products/MyMod.swiftmodule` exists. Jazzy's `--source-directory` (default current directory) is searched by default, so you only need the `-I` override if that's not enough. 4. For a personal framework: ```shell jazzy --module MyMod --swift-build-tool symbolgraph --build-tool-arguments -F,/Build/Products ``` This implies that `/Build/Products/MyMod.framework` exists and contains a `.swiftmodule`. Again the `--source-directory` is searched by default if `-F` is not passed in. 5. With pre-generated symbolgraph files: ```shell jazzy --module MyMod --swift-build-tool symbolgraph --symbolgraph-directory Build/symbolgraphs ``` If you've separately generated symbolgraph files by the use of `-emit-symbol-graph`, you can pass the location of these files using `--symbolgraph-directory` from where they can be parsed directly. See `swift symbolgraph-extract -help` for all the things you can pass via `--build-tool-arguments`: if your module has dependencies then you may need to add various search path options to let Swift load it. ### Documenting multiple modules *This feature is new, bugs and feedback welcome* Sometimes it's useful to document multiple modules together in the same site, for example an app and its extensions, or an SDK that happens to be implemented as several modules. Jazzy can build docs for all these together and create a single site with search, cross-module linking, and navigation. #### Build configuration If all the modules share the same build flags then the easiest way to do this is with `--modules`, for example `jazzy --modules ModuleA,ModuleB,ModuleC`. If your modules have different build flags then you have to use the config file. For example: ```yaml modules: - module: ModuleA - module: ModuleB build_tool_arguments: - -scheme - SpecialScheme - -target - ModuleB source_directory: ModuleBProject - module: ModuleC objc: true umbrella_header: ModuleC/ModuleC.h framework_root: ModuleC sdk: appletvsimulator - module: ModuleD sourcekitten_sourcefile: [ModuleD1.json, ModuleD2.json] ``` This describes a four-module project of which one is 'normal', one requires special Xcode treatment, one is Objective-C, and one has prebuilt SourceKitten JSON. Per-module options set at the top level are inherited by each module unless also set locally -- but you can't set both `--module` and `--modules`. Jazzy doesn't support `--podspec` mode in conjunction with the multiple modules feature. #### Presentation The `--merge-modules` flag controls how declarations from multiple modules are arranged into categories. The default of `all` has Jazzy combine declarations from the modules so there is one category of classes, one of structures, and so on. To the user this means they do not worry about which module exports a particular type, although that information remains available in the type's page. Setting `--merge-modules none` changes this so each module is a top-level category, with the module's symbols listed under it. Setting `--merge-modules extensions` is like `none` except cross-module extensions are shown as part of their extended type. For example if `ModuleA` extends `ModuleB.SomeType` then those extension members from `ModuleA` are shown on the `ModuleB.SomeType` page along with the rest of `SomeType`. You can use `--documentation` to include guides, `custom_categories` to customize the layout with types from whichever modules you want, and `--abstract` to add additional markdown content to the per-module category pages. Use the `--title`, `--readme-title`, and `--docset-title` flags to control the top-level names of your documentation. Without these, Jazzy uses the name of one of the modules being documented. ### Themes Three themes are provided with jazzy: `apple` (default), `fullwidth` and `jony`. * `apple` example: * `fullwidth` example: * `jony` example: You can specify which theme to use by passing in the `--theme` option. You can also provide your own custom theme by passing in the path to your theme directory. ### Guides | Description | Command | | --- | --- | | Command line option | `--documentation={file pattern}` | | Example | `--documentation=Docs/*.md` | | jazzy.yaml example | `documentation: Docs/*.md` | By default, jazzy looks for one of README.md, README.markdown, README.mdown or README (in that order) in the directory from where it runs to render the index page at the root of the docs output directory. Using the `--documentation` option, extra markdown files can be integrated into the generated docs and sidebar navigation. Any files found matching the file pattern will be parsed and included as a document with the type 'Guide' when generated. If the files are not included using the `custom_categories` config option, they will be grouped under 'Other Guides' in the sidebar navigation. There are a few limitations: - File names must be unique from source files. - Readme should be specified separately using the `readme` option. You can link to a guide from other guides or doc comments using the name of the page as it appears in the site. For example, to link to the guide generated from a file called `My Guide.md` you would write \`My Guide\`. ### Section description abstracts | Description | Command | | --- | --- | | Command line option | `--abstract={file pattern}` | | Example | `--abstract=Docs/Sections/*.md` | | jazzy.yaml example | `abstract: Docs/Sections/*.md` | Using the `--abstract` options, extra markdown can be included after the heading of section overview pages. Think of it as a template include. The list of files matching the pattern is compared against the list of sections generated and if a match is found, it's contents will be included in that section before listing source output. Unlike the `--documentation` option, these files are not included in navigation and if a file does not match a section title, it is not included at all. This is very helpful when using `custom_categories` for grouping types and including relevant documentation in those sections. For an example of a project using both `--documentation` and `--abstract` see: [https://reswift.github.io/ReSwift/](https://reswift.github.io/ReSwift/) ### Controlling what is documented In Swift mode, Jazzy by default documents only `public` and `open` declarations. To include declarations with a lower access level, set the `--min-acl` flag to `internal`, `fileprivate`, or `private`. By default, Jazzy does not document declarations marked `@_spi` when `--min-acl` is set to `public` or `open`. Set the `--include-spi-declarations` flag to include them. In Objective-C mode, Jazzy documents all declarations found in the `--umbrella-header` header file and any other header files included by it. You can control exactly which declarations should be documented using `--exclude`, `--include`, or `:nodoc:`. The `--include` and `--exclude` flags list source files that should be included/excluded respectively in the documentation. Entries in the list can be absolute pathnames beginning with `/` or relative pathnames. Relative pathnames are interpreted relative to the directory from where you run `jazzy` or, if the flags are set in the config file, relative to the directory containing the config file. Entries in the list can match multiple files using `*` to match any number of characters including `/`. For example: * `jazzy --include=/Users/fred/project/Sources/Secret.swift` -- include a specific file * `jazzy --exclude=/*/Internal*` -- exclude all files with names that begin with *Internal* and any files under any directory with a name beginning *Internal*. * `jazzy --exclude=Impl1/*,Impl2/*` -- exclude all files under the directories *Impl1* and *Impl2* found in the current directory. Note that the `--include` option is applied before the `--exclude` option. For example: * `jazzy --include=/*/Internal* --exclude=Impl1/*,Impl2/*` -- include all files with names that begin with *Internal* and any files under any directory with a name beginning *Internal*, **except** for those under the directories *Impl1* and *Impl2* found in the current directory Declarations with a documentation comment containing `:nodoc:` are excluded from the documentation. Declarations with the `@_documentation(visibility:)` attribute are treated as though they are written with the given visibility. You can use this as a replacement for `:nodoc:` as part of a transition to Apple's DocC but it is not compatible with Jazzy's symbolgraph mode. ### Documentation structure Jazzy arranges documentation into categories. The default categories are things like _Classes_ and _Structures_ corresponding to programming-language concepts, as well as _Guides_ if `--documentation` is set. You can customize the categories and their contents using `custom_categories` in the config file. `custom_categories` is an array of objects. Each category must contain two properties: - `name`: A string with the name you want to give to this category - `children`: An array used to specify the root level declarations that you want to add to this category. Each child, then, can be one of the following: - A string, containing either the exact name of one root level declaration you want to match, or the fully qualified name (`ModuleName.DeclarationName`) - An object, containing the property: - `regex`: a string which will be used to match multiple root level declarations from all of the modules. See the ReSwift [docs](https://reswift.github.io/ReSwift/) and [config file](https://github.com/ReSwift/ReSwift/blob/e94737282850fa038b625b4e351d1608a3d02cee/.jazzy.json) for an example. Within each category the items are ordered first alphabetically by source filename, and then by declaration order within the file. You can use `// MARK:` comments within the file to create subheadings on the page, for example to split up properties and methods. There’s no way to customize this order short of editing either the generated web page or the SourceKitten JSON. Swift extensions and Objective-C categories allow type members to be declared across multiple source files. In general, extensions follow the main type declaration: first extensions from the same source file, then extensions from other files ordered alphabetically by filename. Swift conditional extensions (`extension A where …`) always appear beneath unconditional extensions. Use this pattern to add or customize the subheading before extension members: ```swift extension MyType { // MARK: Subheading for this group of methods … } ``` When Jazzy is using `--swift-build-tool symgraph` the source file names and line numbers may not be available. In this case the ordering is approximately alphabetical by symbol name and USR; the order is stable for the same input. Jazzy does not normally create separate web pages for declarations that do not have any members -- instead they are entirely nested into their parent page. Use the `--separate-global-declarations` flag to change this and create pages for these empty types. ### Choosing the Swift language version Jazzy normally uses the Swift compiler from the Xcode currently configured by `xcode-select`. Use the `--swift-version` flag or the `DEVELOPER_DIR` environment variable to compile with a different Xcode. The value you pass to `--swift-version` must be the Swift language version given by `swift --version` in the Xcode you want to use. Jazzy uses [xcinvoke](https://github.com/segiddins/xcinvoke) to find a suitable Xcode installation on your system. This can be slow: if you know where Xcode is installed then it's faster to set `DEVELOPER_DIR` directly. For example to use Xcode 14: ```shell jazzy --swift-version 5.7 ``` ...or: ```shell DEVELOPER_DIR=/Applications/Xcode_14.app/Contents/Developer jazzy ``` ### Dash Docset Support As well as the browsable HTML documentation, Jazzy creates a _docset_ for use with the [Dash][dash] app. By default the docset is created at `docs/docsets/ModuleName.tgz`. Use `--docset-path` to create it somewhere else; use `--docset-title` to change the docset's title. Use `--docset-playground-url` and `--docset-icon` to further customize the docset. If you set both `--root-url` to be the (https://) URL where you plan to deploy your documentation and `--version` to give your documentation a version number then Jazzy also creates a docset feed XML file and includes an "Install in Dash" button on the site. This lets users who are browsing your documentation on the web install and start using the docs in Dash locally. ## Linux Jazzy uses [SourceKitten][sourcekitten] to communicate with the Swift build environment and compiler. The `sourcekitten` binary included in the Jazzy gem is built for macOS and so does not run on other operating systems. To use Jazzy on Linux you first need to install and build `sourcekitten` following instructions from [SourceKitten's GitHub repository][sourcekitten]. Then to generate documentation for a SwiftPM project, instead of running just `jazzy` do: ```shell sourcekitten doc --spm > doc.json jazzy --sourcekitten-sourcefile doc.json ``` We hope to improve this process in the future. ## Troubleshooting ### Swift **Only extensions are listed in the documentation?** Check the `--min-acl` setting -- see [above](#controlling-what-is-documented). **Unable to find an Xcode with swift version X** 1. The value passed with `--swift-version` must exactly match the version number from `swiftc --version`. For example Xcode 10.1 needs `--swift-version 4.2.1`. See [the flag documentation](#choosing-the-swift-language-version). 2. The Xcode you want to use must be in the Spotlight index. You can check this using `mdfind 'kMDItemCFBundleIdentifier == com.apple.dt.Xcode'`. Some users have reported this issue being fixed by a reboot; `mdutil -E` may also help. If none of these work then you can set the `DEVELOPER_DIR` environment variable to point to the Xcode you want before running Jazzy without the `--swift-version` flag. ### Objective-C See [this document](ObjectiveC.md). ### Miscellaneous **Missing docset** Jazzy only builds a docset when you set the `--module` or `--modules` flag. **Unable to pass --build-tool-arguments containing commas** If you want Jazzy to run something like `xcodebuild -scheme Scheme -destination 'a=x,b=y,c=z'` then you must use the config file instead of the CLI flag because the CLI parser that Jazzy uses cannot handle arguments that themselves contain commas. The example config file here would be: ```yaml build_tool_arguments: - "-scheme" - "Scheme" - "-destination" - "a=x,b=y,c=z" ``` **Errors running in an Xcode Run Script phase** Running Jazzy from an Xcode build phase can go wrong in cryptic ways when Jazzy has to run `xcodebuild`. Users [have reported](https://github.com/realm/jazzy/issues/1012) that error messages about symbols lacking USRs can be fixed by unsetting `LLVM_TARGET_TRIPLE_SUFFIX` as part of the run script. **Warnings about matches and leftovers when using globs and wildcards** Some flags such as `--include` and `--documentation` support '*' characters as wildcards. If you are using the CLI then you must make sure that your shell does not itself try to interpret them, for example by quoting the token: use `jazzy --documentation '*.md'` instead of `jazzy --documentation *.md`. ### Installation Problems **Can't find header files / clang** Some of the Ruby gems that Jazzy depends on have native C extensions. This means you need the Xcode command-line developer tools installed to build them: run `xcode-select --install` to install the tools. **/Applications/Xcode: No such file or directory** The path of your active Xcode installation must not contain spaces. So `/Applications/Xcode.app/` is fine, `/Applications/Xcode-10.2.app/` is fine, but `/Applications/Xcode 10.2.app/` is not. This restriction applies only when *installing* Jazzy, not running it. ### MacOS Before 10.14.4 Starting with Jazzy 0.10.0, if you see an error similar to `dyld: Symbol not found: _$s11SubSequenceSlTl` then you need to install the [Swift 5 Runtime Support for Command Line Tools](https://support.apple.com/kb/DL1998). Alternatively, you can: * Update to macOS 10.14.4 or later; or * Install Xcode 10.2 or later at `/Applications/Xcode.app`. ## Development Please review jazzy's [contributing guidelines](https://github.com/realm/jazzy/blob/master/CONTRIBUTING.md) when submitting pull requests. jazzy is composed of two parts: 1. The parser, [SourceKitten][SourceKitten] (written in Swift) 2. The site generator (written in ruby) To build and run jazzy from source: 1. Install [bundler][bundler]. 2. Run `bundle install` from the root of this repo. 3. Run jazzy from source by running `bin/jazzy`. Instructions to build SourceKitten from source can be found at [SourceKitten's GitHub repository][SourceKitten]. ## Design Goals - Generate source code docs matching Apple's official reference documentation - Support for standard Objective-C and Swift documentation comment syntax - Leverage modern HTML templating ([Mustache][mustache]) - Leverage the power and accuracy of the [Clang AST][ast] and [SourceKit][sourcekit] - Support for Dash docsets - Support Swift and Objective-C ## License This project is released under the [MIT license](https://github.com/realm/jazzy/blob/master/LICENSE). ## About Jazzy is maintained and funded by Realm Inc. The names and logos for Realm are trademarks of Realm Inc. We :heart: open source software! See [our other open source projects](https://github.com/realm), read [our blog](https://realm.io/news) or say hi on twitter ([@realm](https://twitter.com/realm)). [clang]: https://clang.llvm.org "Clang" [sourcekit]: https://www.jpsim.com/uncovering-sourcekit "Uncovering SourceKit" [ast]: https://clang.llvm.org/docs/IntroductionToTheClangAST.html "Introduction To The Clang AST" [xcode]: https://developer.apple.com/xcode "Xcode" [SourceKitten]: https://github.com/jpsim/SourceKitten "SourceKitten" [bundler]: https://rubygems.org/gems/bundler [mustache]: https://mustache.github.io "Mustache" [spm]: https://swift.org/package-manager/ "Swift Package Manager" [dash]: https://kapeli.com/dash/ "Dash" ================================================ FILE: Rakefile ================================================ # frozen_string_literal: true #-- Bootstrap --------------------------------------------------------------# desc 'Initializes your working copy to run the specs' task :bootstrap do if system('which bundle') title 'Installing gems' sh 'bundle install' title 'Updating submodules' sh 'git submodule update --init --recursive' else warn "\033[0;31m" \ "[!] Please install the bundler gem manually:\n" \ ' $ [sudo] gem install bundler' \ "\e[0m" exit 1 end end begin require 'bundler/gem_tasks' require 'fileutils' task default: :spec #-- Specs ------------------------------------------------------------------# desc 'Run specs' task :spec do title 'Running Tests' Rake::Task['unit_spec'].invoke Rake::Task['objc_spec'].invoke Rake::Task['swift_spec'].invoke Rake::Task['cocoapods_spec'].invoke Rake::Task['rubocop'].invoke end desc 'Run unit specs' task :unit_spec do files = FileList['spec/*_spec.rb'] .exclude('spec/integration_spec.rb').shuffle.join(' ') sh "bundle exec bacon #{files}" end desc 'Run objc integration specs' task :objc_spec do sh 'JAZZY_SPEC_SUBSET=objc bundle exec bacon spec/integration_spec.rb' end desc 'Run swift integration specs' task :swift_spec do sh 'JAZZY_SPEC_SUBSET=swift bundle exec bacon spec/integration_spec.rb' end desc 'Run cocoapods integration specs' task :cocoapods_spec do sh 'JAZZY_SPEC_SUBSET=cocoapods bundle exec bacon spec/integration_spec.rb' end desc 'Rebuilds integration fixtures' task :rebuild_integration_fixtures do title 'Running Integration tests' sh 'rm -rf spec/integration_specs/tmp' puts `bundle exec bacon spec/integration_spec.rb` title 'Storing fixtures' # Copy the files to the files produced by the specs to the after folders FileList['tmp/*'].each do |source| destination = "spec/integration_specs/#{source.gsub('tmp/', '')}/after" if File.exist?(destination) sh "rm -rf #{destination}" sh "mv #{source}/transformed #{destination}" end end # Remove files not used for the comparison # To keep the git diff clean specs_root = 'spec/integration_specs/*/after' files_glob = "#{specs_root}/{*,.*}" files_to_delete = FileList[files_glob] .exclude('**/.', '**/..') .exclude("#{specs_root}/*docs", "#{specs_root}/execution_output.txt") .include("#{specs_root}/**/*.dsidx") .include("#{specs_root}/**/*.tgz") files_to_delete.each do |file_to_delete| sh "rm -rf '#{file_to_delete}'" end puts puts 'Integration fixtures updated, see `spec/integration_specs`' end #-- RuboCop ----------------------------------------------------------------# desc 'Runs RuboCop linter on Ruby files' task :rubocop do sh 'bundle exec rubocop' end #-- SourceKitten -----------------------------------------------------------# desc 'Vendors SourceKitten' task :sourcekitten do sk_dir = 'SourceKitten' bin_path = Dir.chdir(sk_dir) do build_cmd = 'swift build -c release --arch arm64 --arch x86_64' `#{build_cmd}` `#{build_cmd} --show-bin-path`.chomp end FileUtils.cp_r "#{bin_path}/sourcekitten", 'bin' end #-- Theme Dependencies -----------------------------------------------------# THEME_FILES = { 'jquery/dist/jquery.min.js' => [ 'themes/apple/assets/js', 'themes/fullwidth/assets/js', 'themes/jony/assets/js', ], 'lunr/lunr.min.js' => [ 'themes/apple/assets/js', 'themes/fullwidth/assets/js', ], 'corejs-typeahead/dist/typeahead.jquery.js' => [ 'themes/apple/assets/js', 'themes/fullwidth/assets/js', ], 'katex/dist/katex.min.css' => ['extensions/katex/css'], 'katex/dist/fonts' => ['extensions/katex/css'], 'katex/dist/katex.min.js' => ['extensions/katex/js'], }.freeze desc 'Copies theme dependencies (`npm update/install` by hand first)' task :theme_deps do THEME_FILES.each_pair do |src, dsts| dsts.each do |dst| FileUtils.cp_r "js/node_modules/#{src}", "lib/jazzy/#{dst}" end end end rescue LoadError, NameError => e warn "\033[0;31m" \ '[!] Some Rake tasks haven been disabled because the environment' \ ' couldn’t be loaded. Be sure to run `rake bootstrap` first.' \ "\e[0m" warn e.message warn e.backtrace warn '' end #-- Helpers ------------------------------------------------------------------# def title(title) cyan_title = "\033[0;36m#{title}\033[0m" puts puts '-' * 80 puts cyan_title puts '-' * 80 puts end ================================================ FILE: bin/jazzy ================================================ #!/usr/bin/env ruby # frozen_string_literal: true if $PROGRAM_NAME == __FILE__ && !ENV['JAZZY_NO_BUNDLER'] ENV['BUNDLE_GEMFILE'] = File.expand_path('../Gemfile', __dir__) require 'rubygems' require 'bundler/setup' $LOAD_PATH.unshift File.expand_path('../lib', __dir__) elsif ENV['JAZZY_NO_BUNDLER'] require 'rubygems' gem 'jazzy' end require 'jazzy' Jazzy::DocBuilder.build(Jazzy::Config.instance = Jazzy::Config.parse!) ================================================ FILE: jazzy.gemspec ================================================ # coding: utf-8 # frozen_string_literal: true require File.expand_path('lib/jazzy/gem_version.rb', File.dirname(__FILE__)) Gem::Specification.new do |spec| spec.name = 'jazzy' spec.version = Jazzy::VERSION spec.authors = ['JP Simard', 'Tim Anglade', 'Samuel Giddins', 'John Fairhurst'] spec.email = ['jp@jpsim.com'] spec.summary = 'Soulful docs for Swift & Objective-C.' spec.description = 'Soulful docs for Swift & Objective-C. ' \ "Run in your SPM or Xcode project's root directory for " \ 'instant HTML docs.' spec.homepage = 'https://github.com/realm/jazzy' spec.license = 'MIT' spec.files = `git ls-files`.split($/) spec.executables << 'jazzy' spec.add_dependency 'activesupport', '>= 5.0', '< 8' spec.add_dependency 'cocoapods', '~> 1.5' spec.add_dependency 'logger' spec.add_dependency 'mustache', '~> 1.1' spec.add_dependency 'open4', '~> 1.3' spec.add_dependency 'redcarpet', '~> 3.4' spec.add_dependency 'rexml', ['>= 3.2.7', '< 4.0'] spec.add_dependency 'rouge', ['>= 2.0.6', '< 5.0'] spec.add_dependency 'sassc', '~> 2.1' spec.add_dependency 'sqlite3', '~> 1.3' spec.add_dependency 'xcinvoke', '~> 0.3.0' spec.add_development_dependency 'bundler', '~> 2.1' spec.add_development_dependency 'rake', '~> 13.0' spec.required_ruby_version = '>= 2.6.3' spec.metadata['rubygems_mfa_required'] = 'true' end ================================================ FILE: js/package.json ================================================ { "name": "jazzy-js", "version": "1.0.0", "description": "Jazzy theme dependencies", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "MIT", "dependencies": { "corejs-typeahead": "^1.3.1", "jquery": "^3.7.1", "katex": "^0.16.8", "lunr": "^2.3.9" } } ================================================ FILE: lib/jazzy/config.rb ================================================ # frozen_string_literal: true require 'optparse' require 'pathname' require 'uri' require 'jazzy/podspec_documenter' require 'jazzy/source_declaration/access_control_level' module Jazzy # rubocop:disable Metrics/ClassLength class Config # rubocop:disable Naming/AccessorMethodName class Attribute attr_reader :name, :description, :command_line, :config_file_key, :default, :parse, :per_module def initialize(name, description: nil, command_line: nil, default: nil, parse: ->(x) { x }, per_module: false) @name = name.to_s @description = Array(description) @command_line = Array(command_line) @default = default @parse = parse @config_file_key = full_command_line_name || @name @per_module = per_module end def get(config) config.method(name).call end def set_raw(config, val) config.method("#{name}=").call(val) end def set(config, val, mark_configured: true) set_raw(config, config.instance_exec(val, &parse)) config.method("#{name}_configured=").call(true) if mark_configured end def set_to_default(config) set(config, default, mark_configured: false) if default end def set_if_unconfigured(config, val) set(config, val) unless configured?(config) end def configured?(config) config.method("#{name}_configured").call end def attach_to_option_parser(config, opt) return if command_line.empty? opt.on(*command_line, *description) do |val| set(config, val) end end private def full_command_line_name long_option_names = command_line.map do |opt| Regexp.last_match(1) if opt.to_s =~ %r{ ^-- # starts with double dash (?:\[no-\])? # optional prefix for booleans ([^\s]+) # long option name }x end if long_option_name = long_option_names.compact.first long_option_name.tr('-', '_') end end end # rubocop:enable Naming/AccessorMethodName def self.config_attr(name, **opts) attr_accessor name attr_accessor "#{name}_configured" @all_config_attrs ||= [] @all_config_attrs << Attribute.new(name, **opts) end def self.alias_config_attr(name, forward, **opts) alias_method name.to_s, forward.to_s alias_method "#{name}=", "#{forward}=" alias_method "#{name}_configured", "#{forward}_configured" alias_method "#{name}_configured=", "#{forward}_configured=" @all_config_attrs << Attribute.new(name, **opts) end class << self attr_reader :all_config_attrs end attr_accessor :base_path def expand_glob_path(path) Pathname(path).expand_path(base_path) # nil means Pathname.pwd end def expand_path(path) abs_path = expand_glob_path(path) Pathname(Dir[abs_path][0] || abs_path) # Use existing filesystem spelling end def hide_swift? hide_declarations == 'swift' end def hide_objc? hide_declarations == 'objc' end # ──────── Build ──────── # rubocop:disable Layout/ArgumentAlignment config_attr :output, description: 'Folder to output the HTML docs to', command_line: ['-o', '--output FOLDER'], default: 'docs', parse: ->(o) { expand_path(o) } config_attr :clean, command_line: ['-c', '--[no-]clean'], description: ['Delete contents of output directory before running. ', 'WARNING: If --output is set to ~/Desktop, this will ' \ 'delete the ~/Desktop directory.'], default: false config_attr :objc_mode, command_line: '--[no-]objc', description: 'Generate docs for Objective-C.', default: false, per_module: true config_attr :umbrella_header, command_line: '--umbrella-header PATH', description: 'Umbrella header for your Objective-C framework.', parse: ->(uh) { expand_path(uh) }, per_module: true config_attr :framework_root, command_line: '--framework-root PATH', description: 'The root path to your Objective-C framework.', parse: ->(fr) { expand_path(fr) }, per_module: true config_attr :sdk, command_line: '--sdk [iphone|watch|appletv][os|simulator]|macosx', description: 'The SDK for which your code should be built.', default: 'macosx', per_module: true config_attr :hide_declarations, command_line: '--hide-declarations [objc|swift] ', description: 'Hide declarations in the specified language. Given that ' \ 'generating Swift docs only generates Swift declarations, ' \ 'this is useful for hiding a specific interface for ' \ 'either Objective-C or mixed Objective-C and Swift ' \ 'projects.', default: '' config_attr :keep_property_attributes, command_line: '--[no-]keep-property-attributes', description: 'Include the default Objective-C property attributes.', default: false config_attr :config_file, command_line: '--config PATH', description: ['Configuration file (.yaml or .json)', 'Default: .jazzy.yaml in source directory or ancestor'], parse: ->(cf) { expand_path(cf) } config_attr :build_tool_arguments, command_line: ['-b', '--build-tool-arguments arg1,arg2,…argN', Array], description: 'Arguments to forward to xcodebuild, swift build, or ' \ 'sourcekitten.', default: [], per_module: true config_attr :modules, command_line: ['--modules Mod1,Mod2,…ModN', Array], description: 'List of modules to document. Use the config file to set per-module ' \ "build flags, see 'Documenting multiple modules' in the README.", default: [] alias_config_attr :xcodebuild_arguments, :build_tool_arguments, command_line: ['-x', '--xcodebuild-arguments arg1,arg2,…argN', Array], description: 'Back-compatibility alias for build_tool_arguments.' config_attr :sourcekitten_sourcefile, command_line: ['-s', '--sourcekitten-sourcefile filepath1,…filepathN', Array], description: 'File(s) generated from sourcekitten output to parse', parse: ->(paths) { [paths].flatten.map { |path| expand_path(path) } }, default: [], per_module: true config_attr :source_directory, command_line: '--source-directory DIRPATH', description: 'The directory that contains the source to be documented', default: Pathname.pwd, parse: ->(sd) { expand_path(sd) }, per_module: true config_attr :symbolgraph_directory, command_line: '--symbolgraph-directory DIRPATH', description: 'A directory containing a set of Swift Symbolgraph files ' \ 'representing the module to be documented', parse: ->(sd) { expand_path(sd) }, per_module: true config_attr :excluded_files, command_line: ['-e', '--exclude filepath1,filepath2,…filepathN', Array], description: 'Source file pathnames to be excluded from documentation. ' \ 'Supports wildcards.', default: [], parse: ->(files) do Array(files).map { |f| expand_glob_path(f).to_s } end config_attr :included_files, command_line: ['-i', '--include filepath1,filepath2,…filepathN', Array], description: 'Source file pathnames to be included in documentation. ' \ 'Supports wildcards.', default: [], parse: ->(files) do Array(files).map { |f| expand_glob_path(f).to_s } end config_attr :swift_version, command_line: '--swift-version VERSION', default: nil, parse: ->(v) do if v.to_s.empty? nil elsif v.to_f < 2 raise 'jazzy only supports Swift 2.0 or later.' else v end end SWIFT_BUILD_TOOLS = %w[spm xcodebuild symbolgraph].freeze config_attr :swift_build_tool, command_line: "--swift-build-tool #{SWIFT_BUILD_TOOLS.join(' | ')}", description: 'Control whether Jazzy uses Swift Package Manager, ' \ 'xcodebuild, or swift-symbolgraph to build the module ' \ 'to be documented. By default it uses xcodebuild if ' \ 'there is a .xcodeproj file in the source directory.', parse: ->(tool) do return tool.to_sym if SWIFT_BUILD_TOOLS.include?(tool) raise "Unsupported swift_build_tool #{tool}, " \ "supported values: #{SWIFT_BUILD_TOOLS.join(', ')}" end # ──────── Metadata ──────── config_attr :author_name, command_line: ['-a', '--author AUTHOR_NAME'], description: 'Name of author to attribute in docs (e.g. Realm)', default: '' config_attr :author_url, command_line: ['-u', '--author_url URL'], description: 'Author URL of this project (e.g. https://realm.io)', default: '', parse: ->(u) { URI(u) } config_attr :module_name, command_line: ['-m', '--module MODULE_NAME'], description: 'Name of module being documented. (e.g. RealmSwift)', default: '', per_module: true config_attr :version, command_line: '--module-version VERSION', description: 'Version string to use as part of the default docs ' \ 'title and inside the docset.', default: '1.0' config_attr :title, command_line: '--title TITLE', description: 'Title to display at the top of each page, overriding the ' \ 'default generated from module name and version.', default: '' config_attr :copyright, command_line: '--copyright COPYRIGHT_MARKDOWN', description: 'copyright markdown rendered at the bottom of the docs pages' config_attr :readme_path, command_line: '--readme FILEPATH', description: 'The path to a markdown README file', parse: ->(rp) { expand_path(rp) } config_attr :readme_title, command_line: '--readme-title TITLE', description: 'The title for the README in the generated documentation' config_attr :documentation_glob, command_line: '--documentation GLOB', description: 'Glob that matches available documentation', parse: ->(dg) { Pathname.glob(dg) } config_attr :abstract_glob, command_line: '--abstract GLOB', description: 'Glob that matches available abstracts for categories', parse: ->(ag) { Pathname.glob(ag) } config_attr :podspec, command_line: '--podspec FILEPATH', description: 'A CocoaPods Podspec that describes the Swift library to ' \ 'document', parse: ->(ps) { PodspecDocumenter.create_podspec(Pathname(ps)) if ps }, default: Dir['*.podspec{,.json}'].first config_attr :pod_sources, command_line: ['--pod-sources url1,url2,…urlN', Array], description: 'A list of sources to find pod dependencies. Used only ' \ 'with --podspec when the podspec contains references to ' \ 'privately hosted pods. You must include the default pod ' \ 'source if public pods are also used.', default: [] config_attr :docset_icon, command_line: '--docset-icon FILEPATH', parse: ->(di) { expand_path(di) } config_attr :docset_path, command_line: '--docset-path DIRPATH', description: 'The relative path for the generated docset' config_attr :docset_title, command_line: '--docset-title TITLE', description: 'The title of the generated docset. A simplified version ' \ 'is used for the filenames associated with the docset. If the ' \ 'option is not set then the name of the module being documented is ' \ 'used as the docset title.' # ──────── URLs ──────── config_attr :root_url, command_line: ['-r', '--root-url URL'], description: 'Absolute URL root where these docs will be stored', # ensure trailing slash for correct URI.join() parse: ->(r) { URI(r.sub(%r{/?$}, '/')) } config_attr :dash_url, command_line: ['-d', '--dash_url URL'], description: 'Location of the dash XML feed ' \ 'e.g. https://realm.io/docsets/realm.xml)', parse: ->(d) { URI(d) } SOURCE_HOSTS = %w[github gitlab bitbucket].freeze config_attr :source_host, command_line: "--source-host #{SOURCE_HOSTS.join(' | ')}", description: ['The source-code hosting site to be linked from documentation.', 'This setting affects the logo image and link format.', "Default: 'github'"], default: 'github', parse: ->(host) do return host.to_sym if SOURCE_HOSTS.include?(host) raise "Unsupported source_host '#{host}', " \ "supported values: #{SOURCE_HOSTS.join(', ')}" end config_attr :source_host_url, command_line: ['--source-host-url URL'], description: ["URL to link from the source host's logo.", 'For example https://github.com/realm/realm-cocoa'], parse: ->(g) { URI(g) } alias_config_attr :github_url, :source_host_url, command_line: ['-g', '--github_url URL'], description: 'Back-compatibility alias for source_host_url.' config_attr :source_host_files_url, command_line: '--source-host-files-url PREFIX', description: [ "The base URL on the source host of the project's files, to link " \ 'from individual declarations.', 'For example https://github.com/realm/realm-cocoa/tree/v0.87.1', ] alias_config_attr :github_file_prefix, :source_host_files_url, command_line: '--github-file-prefix PREFIX', description: 'Back-compatibility alias for source_host_files_url' config_attr :docset_playground_url, command_line: '--docset-playground-url URL', description: 'URL of an interactive playground to demonstrate the ' \ 'framework, linked to from the docset.' # ──────── Doc generation options ──────── config_attr :disable_search, command_line: '--disable-search', description: 'Avoid generating a search index. ' \ 'Search is available in some themes.', default: false config_attr :skip_documentation, command_line: '--skip-documentation', description: 'Will skip the documentation generation phase.', default: false config_attr :min_acl, command_line: '--min-acl [private | fileprivate | internal | package | public | open]', description: 'minimum access control level to document', default: 'public', parse: ->(acl) do SourceDeclaration::AccessControlLevel.from_human_string(acl) end config_attr :skip_undocumented, command_line: '--[no-]skip-undocumented', description: "Don't document declarations that have no documentation " \ 'comments.', default: false config_attr :hide_documentation_coverage, command_line: '--[no-]hide-documentation-coverage', description: 'Hide "(X% documented)" from the generated documents', default: false config_attr :custom_categories, description: 'Custom navigation categories to replace the standard ' \ "'Classes', 'Protocols', etc. Types not explicitly named " \ 'in a custom category appear in generic groups at the ' \ 'end. Example: https://git.io/v4Bcp', default: [] config_attr :custom_categories_unlisted_prefix, description: "Prefix for navigation section names that aren't " \ 'explicitly listed in `custom_categories`.', default: 'Other ' config_attr :hide_unlisted_documentation, command_line: '--[no-]hide-unlisted-documentation', description: "Don't include documentation in the sidebar from the " \ "`documentation` config value that aren't explicitly " \ 'listed in `custom_categories`.', default: false config_attr :custom_head, command_line: '--head HTML', description: 'Custom HTML to inject into .', default: '' BUILTIN_THEME_DIR = Pathname(__dir__) + 'themes' BUILTIN_THEMES = BUILTIN_THEME_DIR.children(false).map(&:to_s) config_attr :theme_directory, command_line: "--theme [#{BUILTIN_THEMES.join(' | ')} | DIRPATH]", description: "Which theme to use. Specify either 'apple' (default), " \ 'one of the other built-in theme names, or the path to ' \ 'your mustache templates and other assets for a custom ' \ 'theme.', default: 'apple', parse: ->(t) do if BUILTIN_THEMES.include?(t) BUILTIN_THEME_DIR + t else expand_path(t) end end config_attr :use_safe_filenames, command_line: '--use-safe-filenames', description: 'Replace unsafe characters in filenames with an encoded ' \ 'representation. This will reduce human readability of ' \ 'some URLs, but may be necessary for projects that ' \ 'expose filename-unfriendly functions such as /(_:_:)', default: false config_attr :template_directory, command_line: ['-t', '--template-directory DIRPATH'], description: 'DEPRECATED: Use --theme instead.', parse: ->(_) do raise '--template-directory (-t) is deprecated: use --theme instead.' end config_attr :assets_directory, command_line: '--assets-directory DIRPATH', description: 'DEPRECATED: Use --theme instead.', parse: ->(_) do raise '--assets-directory is deprecated: use --theme instead.' end config_attr :undocumented_text, command_line: '--undocumented-text UNDOCUMENTED_TEXT', description: 'Default text for undocumented symbols. The default ' \ 'is "Undocumented", put "" if no text is required', default: 'Undocumented' config_attr :separate_global_declarations, command_line: '--[no-]separate-global-declarations', description: 'Create separate pages for all global declarations ' \ "(classes, structures, enums etc.) even if they don't " \ 'have children.', default: false config_attr :include_spi_declarations, command_line: '--[no-]include-spi-declarations', description: 'Include Swift declarations marked `@_spi` even if ' \ '--min-acl is set to `public` or `open`.', default: false MERGE_MODULES = %w[all extensions none].freeze config_attr :merge_modules, command_line: "--merge-modules #{MERGE_MODULES.join(' | ')}", description: 'Control how to display declarations from multiple ' \ 'modules. `all`, the default, places all declarations of the ' \ "same kind together. `none` keeps each module's declarations " \ 'separate. `extensions` is like `none` but merges ' \ 'cross-module extensions into their extended type.', default: 'all', parse: ->(merge) do return merge.to_sym if MERGE_MODULES.include?(merge) raise "Unsupported merge_modules #{merge}, " \ "supported values: #{MERGE_MODULES.join(', ')}" end # rubocop:enable Layout/ArgumentAlignment def initialize self.class.all_config_attrs.each do |attr| attr.set_to_default(self) end end def theme_directory=(theme_directory) @theme_directory = theme_directory Doc.template_path = theme_directory + 'templates' end def self.parse! config = new config.parse_command_line config.parse_config_file PodspecDocumenter.apply_config_defaults(config.podspec, config) config.set_module_configs config.validate config end def warning(message) warn "WARNING: #{message}" end # rubocop:disable Metrics/MethodLength def parse_command_line OptionParser.new do |opt| opt.banner = 'Usage: jazzy' opt.separator '' opt.separator 'Options' self.class.all_config_attrs.each do |attr| attr.attach_to_option_parser(self, opt) end opt.on('-v', '--version', 'Print version number') do puts "jazzy version: #{Jazzy::VERSION}" exit end opt.on('-h', '--help [TOPIC]', 'Available topics:', ' usage Command line options (this help message)', ' config Configuration file options', '...or an option keyword, e.g. "dash"') do |topic| case topic when 'usage', nil puts opt when 'config' print_config_file_help else print_option_help(topic) end exit end end.parse! unless ARGV.empty? warning "Leftover unused command-line text: #{ARGV}" end end def parse_config_file config_path = locate_config_file return unless config_path self.base_path = config_path.parent puts "Using config file #{config_path}" config_file = read_config_file(config_path) attrs_by_conf_key, attrs_by_name = grouped_attributes parse_config_hash(config_file, attrs_by_conf_key, attrs_by_name) end def parse_config_hash(hash, attrs_by_conf_key, attrs_by_name, override: false) hash.each do |key, value| unless attr = attrs_by_conf_key[key] message = "Unknown config file attribute #{key.inspect}" if matching_name = attrs_by_name[key] message += " (Did you mean #{matching_name.first.config_file_key.inspect}?)" end warning message next end setter = override ? :set : :set_if_unconfigured attr.first.method(setter).call(self, value) end end # Find keyed versions of the attributes, by config file key and then name-in-code # Optional block allows filtering/overriding of attribute list. def grouped_attributes attrs = self.class.all_config_attrs attrs = yield attrs if block_given? %i[config_file_key name].map do |property| attrs.group_by(&property) end end def validate if source_host_configured && source_host_url.nil? && source_host_files_url.nil? warning 'Option `source_host` is set but has no effect without either ' \ '`source_host_url` or `source_host_files_url`.' end if modules_configured && module_name_configured raise 'Options `modules` and `module` are both set which is not supported. ' \ 'To document multiple modules, use just `modules`.' end if modules_configured && podspec_configured raise 'Options `modules` and `podspec` are both set which is not supported.' end module_configs.each(&:validate_module) end def validate_module if objc_mode && build_tool_arguments_configured && (framework_root_configured || umbrella_header_configured) warning 'Option `build_tool_arguments` is set: values passed to ' \ '`framework_root` or `umbrella_header` may be ignored.' end end # rubocop:enable Metrics/MethodLength # Module Configs # # The user can enter module information in three different ways. This # consolidates them into one view for the rest of the code. # # 1) Single module, back-compatible # --module Foo etc etc (or not given at all) # # 2) Multiple modules, simple, sharing build params # --modules Foo,Bar,Baz --source-directory Xyz # # 3) Multiple modules, custom, different build params but # inheriting others from the top level. # This is config-file only. # - modules # - module: Foo # source_directory: Xyz # build_tool_arguments: [a, b, c] # # After this we're left with `config.module_configs` that is an # array of `Config` objects. attr_reader :module_configs attr_reader :module_names def set_module_configs @module_configs = parse_module_configs @module_names = module_configs.map(&:module_name) @module_names_set = Set.new(module_names) end def module_name?(name) @module_names_set.include?(name) end def multiple_modules? @module_names.count > 1 end def parse_module_configs return [self] unless modules_configured raise 'Config file key `modules` must be an array' unless modules.is_a?(Array) if modules.first.is_a?(String) # Massage format (2) into (3) self.modules = modules.map { |mod| { 'module' => mod } } end # Allow per-module overrides of only some config options attrs_by_conf_key, attrs_by_name = grouped_attributes { |attr| attr.select(&:per_module) } modules.map do |module_hash| mod_name = module_hash['module'] || '' raise 'Missing `modules.module` config key' if mod_name.empty? dup.tap do |module_config| module_config.parse_config_hash( module_hash, attrs_by_conf_key, attrs_by_name, override: true ) end end end # For podspec query def module_name_known? module_name_configured || modules_configured end def locate_config_file return config_file if config_file source_directory.ascend do |dir| candidate = dir.join('.jazzy.yaml') return candidate if candidate.exist? end nil end def read_config_file(file) case File.extname(file) when '.json' JSON.parse(File.read(file)) when '.yaml', '.yml' YAML.safe_load(File.read(file)) else raise "Config file must be .yaml or .json, but got #{file.inspect}" end end def print_config_file_help puts <<-_EOS_ By default, jazzy looks for a file named ".jazzy.yaml" in the source directory and its ancestors. You can override the config file location with --config. (The source directory is the current working directory by default. You can override that with --source-directory.) The config file can be in YAML or JSON format. Available options are: _EOS_ .gsub(/^ +/, '') print_option_help end def print_option_help(topic = '') found = false self.class.all_config_attrs.each do |attr| match = ([attr.name] + attr.command_line).any? do |opt| opt.to_s.include?(topic) end if match found = true puts puts attr.name.to_s.tr('_', ' ').upcase puts puts " Config file: #{attr.config_file_key}" cmd_line_forms = attr.command_line.select { |opt| opt.is_a?(String) } if cmd_line_forms.any? puts " Command line: #{cmd_line_forms.join(', ')}" end puts print_attr_description(attr) end end warn "Unknown help topic #{topic.inspect}" unless found end def print_attr_description(attr) attr.description.each { |line| puts " #{line}" } if attr.default && attr.default != '' puts " Default: #{attr.default}" end end #-------------------------------------------------------------------------# # @!group Singleton # @return [Config] the current config instance creating one if needed. # def self.instance @instance ||= new end # Sets the current config instance. If set to nil the config will be # recreated when needed. # # @param [Config, Nil] the instance. # # @return [void] # class << self attr_writer :instance end # Provides support for accessing the configuration instance in other # scopes. # module Mixin def config Config.instance end end end # rubocop:enable Metrics/ClassLength end ================================================ FILE: lib/jazzy/doc.rb ================================================ # frozen_string_literal: true require 'date' require 'pathname' require 'mustache' require 'jazzy/config' require 'jazzy/gem_version' require 'jazzy/jazzy_markdown' module Jazzy class Doc < Mustache include Config::Mixin self.template_name = 'doc' def copyright copyright = config.copyright || ( # Fake date is used to keep integration tests consistent date = ENV['JAZZY_FAKE_DATE'] || DateTime.now.strftime('%Y-%m-%d') year = date[0..3] "© #{year} [#{config.author_name}](#{config.author_url}). " \ "All rights reserved. (Last updated: #{date})" ) Markdown.render_copyright(copyright).chomp end def jazzy_version # Fake version is used to keep integration tests consistent ENV['JAZZY_FAKE_VERSION'] || Jazzy::VERSION end def objc_first? config.objc_mode && config.hide_declarations != 'objc' end def language_stub objc_first? ? 'objc' : 'swift' end def module_version config.version_configured ? config.version : nil end def docs_title if config.title_configured config.title elsif config.version_configured # Fake version for integration tests version = ENV['JAZZY_FAKE_MODULE_VERSION'] || config.version "#{config.module_configs.first.module_name} #{version} Docs" else "#{config.module_configs.first.module_name} Docs" end end def enable_katex Markdown.has_math end end end ================================================ FILE: lib/jazzy/doc_builder.rb ================================================ # frozen_string_literal: true require 'fileutils' require 'mustache' require 'pathname' require 'sassc' require 'jazzy/config' require 'jazzy/doc' require 'jazzy/docset_builder' require 'jazzy/documentation_generator' require 'jazzy/search_builder' require 'jazzy/jazzy_markdown' require 'jazzy/podspec_documenter' require 'jazzy/source_declaration' require 'jazzy/source_document' require 'jazzy/source_module' require 'jazzy/sourcekitten' require 'jazzy/symbol_graph' module Jazzy # This module handles HTML generation, file writing, asset copying, # and generally building docs given sourcekitten output module DocBuilder # mkdir -p output directory and clean if option is set def self.prepare_output_dir(output_dir, clean) FileUtils.rm_r output_dir if clean && output_dir.directory? FileUtils.mkdir_p output_dir end # Generate doc structure to be used in sidebar navigation # @return [Array] doc structure comprised of # section names & child names & URLs def self.doc_structure_for_docs(docs) docs .map do |doc| children = children_for_doc(doc) { section: doc.name, url: doc.url, children: children, } end .select do |structure| if Config.instance.hide_unlisted_documentation unlisted_prefix = Config.instance.custom_categories_unlisted_prefix structure[:section] != "#{unlisted_prefix}Guides" else true end end end def self.children_for_doc(doc) doc.children .sort_by { |c| [c.nav_order, c.name, c.usr || ''] } .flat_map do |child| # FIXME: include arbitrarily nested extensible types [{ name: child.name, url: child.url }] + Array(child.children.select do |sub_child| sub_child.type.swift_extensible? || sub_child.type.extension? end).map do |sub_child| { name: "– #{sub_child.name}", url: sub_child.url } end end end # Build documentation from the given options # @param [Config] options def self.build(options) module_jsons = options.module_configs.map do |module_config| if module_config.podspec_configured # Config#validate guarantees not multi-module here pod_documenter = PodspecDocumenter.new(options.podspec) pod_documenter.sourcekitten_output(options) elsif !module_config.sourcekitten_sourcefile.empty? "[#{module_config.sourcekitten_sourcefile.map(&:read).join(',')}]" elsif module_config.swift_build_tool == :symbolgraph SymbolGraph.build(module_config) else Dir.chdir(module_config.source_directory) do arguments = SourceKitten.arguments_from_options(module_config) SourceKitten.run_sourcekitten(arguments) end end end build_docs_for_sourcekitten_output(module_jsons, options) end # Build & write HTML docs to disk from structured docs array # @param [String] output_dir Root directory to write docs # @param [SourceModule] source_module All info to generate docs def self.build_docs(output_dir, source_module) each_doc(output_dir, source_module.docs) do |doc, path| prepare_output_dir(path.parent, false) depth = path.relative_path_from(output_dir).each_filename.count - 1 path_to_root = '../' * depth path.open('w') do |file| file.write(document(source_module, doc, path_to_root)) end end end def self.each_doc(output_dir, docs, &block) docs.each do |doc| next unless doc.render_as_page? # Filepath is relative to documentation root: path = output_dir + doc.filepath block.call(doc, path) each_doc( output_dir, doc.children, &block ) end end def self.build_site(docs, coverage, options) warn 'building site' structure = doc_structure_for_docs(docs) docs << SourceDocument.make_index(options.readme_path) output_dir = options.output docset_builder = DocsetBuilder.new(output_dir) source_module = SourceModule.new(docs, structure, coverage, docset_builder) build_docs(output_dir, source_module) unless options.disable_search warn 'building search index' SearchBuilder.build(source_module, output_dir) end copy_extensions(source_module, output_dir) copy_theme_assets(output_dir) docset_builder.build!(source_module.all_declarations) generate_badge(source_module.doc_coverage, options) friendly_path = relative_path_if_inside(output_dir, Pathname.pwd) puts "jam out ♪♫ to your fresh new docs in `#{friendly_path}`" source_module end # Build docs given sourcekitten output # @param [Array] sourcekitten_output Output of sourcekitten command for each module # @param [Config] options Build options def self.build_docs_for_sourcekitten_output(sourcekitten_output, options) (docs, stats) = SourceKitten.parse( sourcekitten_output, options, DocumentationGenerator.source_docs, ) prepare_output_dir(options.output, options.clean) stats.report unless options.skip_documentation build_site(docs, stats.doc_coverage, options) end write_lint_report(stats.undocumented_decls, options) end def self.relative_path_if_inside(path, base_path) relative = path.relative_path_from(base_path) if relative.to_path =~ %r{/^..(/|$)/} path else relative end end def self.undocumented_warnings(decls) decls.map do |decl| { file: decl.file, line: decl.start_line || decl.line, symbol: decl.fully_qualified_name, symbol_kind: decl.type.kind, warning: 'undocumented', } end end def self.write_lint_report(undocumented, options) (options.output + 'undocumented.json').open('w') do |f| warnings = undocumented_warnings(undocumented) lint_report = { warnings: warnings.sort_by do |w| [w[:file] || Pathname(''), w[:line] || 0, w[:symbol], w[:symbol_kind]] end, source_directory: options.source_directory, } f.write(JSON.pretty_generate(lint_report)) end end def self.copy_theme_assets(destination) assets_directory = Config.instance.theme_directory + 'assets' FileUtils.cp_r(assets_directory.children, destination) Pathname.glob(destination + 'css/**/*.scss').each do |scss| css = SassC::Engine.new(scss.read).render css_filename = scss.sub(/\.scss$/, '') css_filename.open('w') { |f| f.write(css) } FileUtils.rm scss end end def self.copy_extensions(source_module, destination) if source_host = source_module.host&.extension copy_extension(source_host, destination) end copy_extension('katex', destination) if Markdown.has_math end def self.copy_extension(name, destination) ext_directory = Pathname(__dir__) / 'extensions' / name FileUtils.cp_r(ext_directory.children, destination) end def self.render(doc_model, markdown) html = Markdown.render(markdown) SourceKitten.autolink_document(html, doc_model) end def self.render_inline(doc_model, markdown) html = Markdown.render_inline(markdown) SourceKitten.autolink_document(html, doc_model) end # Build Mustache document - common fields between page types def self.new_document(source_module, doc_model) Doc.new.tap do |doc| doc[:custom_head] = Config.instance.custom_head doc[:disable_search] = Config.instance.disable_search doc[:doc_coverage] = source_module.doc_coverage unless Config.instance.hide_documentation_coverage doc[:structure] = source_module.doc_structure doc[:readme_title] = source_module.readme_title doc[:module_name] = doc[:readme_title] doc[:author_name] = source_module.author_name if source_host = source_module.host doc[:source_host_name] = source_host.name doc[:source_host_url] = source_host.url doc[:source_host_image] = source_host.image doc[:source_host_item_url] = source_host.item_url(doc_model) doc[:github_url] = doc[:source_host_url] doc[:github_token_url] = doc[:source_host_item_url] end doc[:dash_url] = source_module.dash_feed_url doc[:breadcrumbs] = make_breadcrumbs(doc_model) end end # Build Mustache document from a markdown source file # @param [SourceModule] module-wide settings # @param [Hash] doc_model Parsed doc. @see SourceKitten.parse # @param [String] path_to_root def self.document_markdown(source_module, doc_model, path_to_root) doc = new_document(source_module, doc_model) name = doc_model.readme? ? source_module.readme_title : doc_model.name doc[:name] = name doc[:overview] = render(doc_model, doc_model.content(source_module)) doc[:path_to_root] = path_to_root doc[:hide_name] = true doc.render.gsub(ELIDED_AUTOLINK_TOKEN, path_to_root) end # Returns the appropriate color for the provided percentage, # used for generating a badge on shields.io # @param [Number] coverage The documentation coverage percentage def self.color_for_coverage(coverage) if coverage < 10 'e05d44' # red elsif coverage < 30 'fe7d37' # orange elsif coverage < 60 'dfb317' # yellow elsif coverage < 85 'a4a61d' # yellowgreen elsif coverage < 90 '97CA00' # green else '4c1' # brightgreen end end # rubocop:disable Metrics/MethodLength # Generates an SVG similar to those from shields.io displaying the # documentation percentage # @param [Number] coverage The documentation coverage percentage # @param [Config] options Build options def self.generate_badge(coverage, options) return if options.hide_documentation_coverage coverage_length = coverage.to_s.size.succ percent_string_length = coverage_length * 80 + 10 percent_string_offset = coverage_length * 40 + 975 width = coverage_length * 8 + 104 svg = <<-SVG.gsub(/^ {8}/, '') documentation documentation #{coverage}% #{coverage}% SVG badge_output = options.output + 'badge.svg' File.open(badge_output, 'w') { |f| f << svg } end # rubocop:enable Metrics/MethodLength # Build mustache item for a top-level doc # @param [Hash] item Parsed doc child item # @param [Config] options Build options # rubocop:disable Metrics/MethodLength def self.render_item(item, source_module) # Combine abstract and discussion into abstract abstract = (item.abstract || '') + (item.discussion || '') source_host_item_url = source_module.host&.item_url(item) { name: item.name, name_html: item.name.gsub(':', ':'), abstract: abstract, declaration: item.display_declaration, language: item.display_language, other_language_declaration: item.display_other_language_declaration, usr: item.usr, dash_type: item.type.dash_type, source_host_item_url: source_host_item_url, github_token_url: source_host_item_url, default_impl_abstract: item.default_impl_abstract, from_protocol_extension: item.from_protocol_extension, return: item.return, parameters: (item.parameters if item.parameters.any?), url: (item.url if item.render_as_page?), start_line: item.start_line, end_line: item.end_line, direct_link: item.omit_content_from_parent?, deprecation_message: item.deprecation_message, unavailable_message: item.unavailable_message, usage_discouraged: item.usage_discouraged?, async: item.async, declaration_note: item.declaration_note, } end # rubocop:enable Metrics/MethodLength def self.make_task(mark, uid, items, doc_model) { name: mark.name, name_html: (render_inline(doc_model, mark.name) if mark.name), uid: ERB::Util.url_encode(uid), items: items, pre_separator: mark.has_start_dash, post_separator: mark.has_end_dash, } end # Render tasks for Mustache document # @param [Config] options Build options # @param [Hash] doc_model Parsed doc. @see SourceKitten.parse def self.render_tasks(source_module, children) marks = children.map(&:mark).uniq.compact mark_names_counts = {} marks.map do |mark| mark_children = children.select { |child| child.mark == mark } items = mark_children.map { |child| render_item(child, source_module) } uid = (mark.name || 'Unnamed').to_s if mark_names_counts.key?(uid) mark_names_counts[uid] += 1 uid += mark_names_counts[uid].to_s else mark_names_counts[uid] = 1 end make_task(mark, uid, items, mark_children.first) end end # rubocop:disable Metrics/MethodLength # Build Mustache document from single parsed decl # @param [SourceModule] module-wide settings # @param [Hash] doc_model Parsed doc. @see SourceKitten.parse # @param [String] path_to_root def self.document(source_module, doc_model, path_to_root) if doc_model.type.markdown? return document_markdown(source_module, doc_model, path_to_root) end overview = (doc_model.abstract || '') + (doc_model.discussion || '') alternative_abstract = doc_model.alternative_abstract if alternative_abstract overview = render(doc_model, alternative_abstract) + overview end doc = new_document(source_module, doc_model) doc[:name] = doc_model.name doc[:kind] = doc_model.type.name doc[:dash_type] = doc_model.type.dash_type doc[:declaration] = doc_model.display_declaration doc[:language] = doc_model.display_language doc[:other_language_declaration] = doc_model.display_other_language_declaration doc[:overview] = overview doc[:parameters] = doc_model.parameters doc[:return] = doc_model.return doc[:tasks] = render_tasks(source_module, doc_model.children) doc[:path_to_root] = path_to_root doc[:deprecation_message] = doc_model.deprecation_message doc[:unavailable_message] = doc_model.unavailable_message doc[:usage_discouraged] = doc_model.usage_discouraged? doc.render.gsub(ELIDED_AUTOLINK_TOKEN, path_to_root) end # rubocop:enable Metrics/MethodLength # Breadcrumbs for a page - doesn't include the top 'readme' crumb def self.make_breadcrumbs(doc_model) return [] if doc_model.readme? docs_path = doc_model.docs_path breadcrumbs = docs_path.map do |doc| { name: doc.name, url: doc.url, last: doc == doc_model, } end return breadcrumbs if breadcrumbs.one? # Add the module name to the outer type if not clear from context if docs_path[1].ambiguous_module_name?(docs_path[0].name) breadcrumbs[1][:name] = docs_path[1].fully_qualified_module_name end breadcrumbs end end end ================================================ FILE: lib/jazzy/doc_index.rb ================================================ # frozen_string_literal: true module Jazzy # This class stores an index of symbol names for doing name lookup # when resolving custom categories and autolinks. class DocIndex # A node in the index tree. The root has no decl; its children are # per-module indexed by module names. The second level, where each # scope is a module, also has no decl; its children are scopes, one # for each top-level decl in the module. From the third level onwards # the decl is valid. class Scope attr_reader :decl # SourceDeclaration attr_reader :children # String:Scope def initialize(decl, children) @decl = decl @children = children end def self.new_root(module_decls) new(nil, module_decls.transform_values do |decls| Scope.new_decl(nil, decls) end) end # Decl names in a scope are usually unique. The exceptions # are (1) methods and (2) typealias+extension, which historically # jazzy does not merge. The logic here and in `merge()` below # preserves the historical ambiguity-resolution of (1) and tries # to do the best for (2). def self.new_decl(decl, child_decls) child_scopes = {} child_decls.flat_map do |child_decl| child_scope = Scope.new_decl(child_decl, child_decl.children) child_decl.index_names.map do |name| if curr = child_scopes[name] curr.merge(child_scope) else child_scopes[name] = child_scope end end end new(decl, child_scopes) end def merge(new_scope) return unless type = decl&.type return unless new_type = new_scope.decl&.type if type.swift_typealias? && new_type.swift_extension? @children = new_scope.children elsif type.swift_extension? && new_type.swift_typealias? @decl = new_scope.decl end end # Lookup of a name like `Mod.Type.method(arg:)` requires passing # an array of name 'parts' eg. ['Mod', 'Type', 'method(arg:)']. def lookup(parts) return decl if parts.empty? children[parts.first]&.lookup(parts[1...]) end # Look up of a regex matching all children for current level only. def lookup_regex(regex) children.select { |name, _| name.match(regex) } .map { |_, scope| scope.decl }.compact end # Get an array of scopes matching the name parts. def lookup_path(parts) [self] + (children[parts.first]&.lookup_path(parts[1...]) || []) end end attr_reader :root_scope def initialize(all_decls) @root_scope = Scope.new_root(all_decls.group_by(&:module_name)) end # Look up a name and return the matching SourceDeclaration or nil. # # `context` is an optional SourceDeclaration indicating where the text # was found, affects name resolution - see `lookup_context()` below. def lookup(name, context = nil) lookup_name = LookupName.new(name) return lookup_fully_qualified(lookup_name) if lookup_name.fully_qualified? return lookup_guess(lookup_name) if context.nil? lookup_context(lookup_name, context) end # Look up a regex and return all matching top level SourceDeclaration. def lookup_regex(regex) root_scope.children.map { |_, scope| scope.lookup_regex(regex) }.flatten end private # Look up a fully-qualified name, ie. it starts with the module name. def lookup_fully_qualified(lookup_name) root_scope.lookup(lookup_name.parts) end # Look up a top-level name best-effort, searching for a module that # has it before trying the first name-part as a module name. def lookup_guess(lookup_name) root_scope.children.each_value do |module_scope| if result = module_scope.lookup(lookup_name.parts) return result end end lookup_fully_qualified(lookup_name) end # Look up a name from a declaration context, approximately how # Swift resolves names. # # 1 - try and resolve with a common prefix, eg. 'B' from 'T.A' # can match 'T.B', or 'R' from 'S.T.A' can match 'S.R'. # 2 - try and resolve as a top-level symbol from a different module # 3 - (affordance for docs writers) resolve as a child of the context, # eg. 'B' from 'T.A' can match 'T.A.B' *only if* (1,2) fail. # Currently disabled for Swift for back-compatibility. def lookup_context(lookup_name, context) context_scope_path = root_scope.lookup_path(context.fully_qualified_module_name_parts) context_scope = context_scope_path.pop context_scope_path.reverse.each do |scope| if decl = scope.lookup(lookup_name.parts) return decl end end lookup_guess(lookup_name) || (lookup_name.objc? && context_scope.lookup(lookup_name.parts)) end # Helper for name lookup, really a cache for information as we # try various strategies. class LookupName attr_reader :name def initialize(name) @name = name end def fully_qualified? name.start_with?('/') end def objc? name.start_with?('-', '+') end def parts @parts ||= find_parts end private # Turn a name as written into a list of components to # be matched. # Swift: Strip out odd characters and split # ObjC: Compound names look like '+[Class(Category) method:]' # and need to become ['Class(Category)', '+method:'] def find_parts if name =~ /([+-])\[(\w+(?: ?\(\w+\))?) ([\w:]+)\]/ [Regexp.last_match[2], Regexp.last_match[1] + Regexp.last_match[3]] else name .sub(%r{^[@/]}, '') # ignore custom attribute reference, fully-qualified .gsub(/<.*?>/, '') # remove generic parameters .split(%r{(? CFBundleIdentifier com.jazzy.{{lowercase_safe_name}} CFBundleName {{name}} DocSetPlatformFamily {{lowercase_name}} isDashDocset dashIndexFilePath index.html isJavaScriptEnabled DashDocSetFamily dashtoc {{#root_url}} DashDocSetFallbackURL {{{.}}} {{/root_url}} {{#playground_url}} DashDocSetPlayURL {{{.}}} {{/playground_url}} ================================================ FILE: lib/jazzy/docset_builder.rb ================================================ # frozen_string_literal: true require 'mustache' require 'sqlite3' module Jazzy module DocBuilder # Follows the instructions found at https://kapeli.com/docsets#dashDocset. class DocsetBuilder include Config::Mixin attr_reader :output_dir attr_reader :generated_docs_dir attr_reader :source_module attr_reader :docset_dir attr_reader :documents_dir attr_reader :name def initialize(generated_docs_dir) @name = config.docset_title || config.module_names.first docset_path = config.docset_path || "docsets/#{safe_name}.docset" @docset_dir = generated_docs_dir + docset_path @generated_docs_dir = generated_docs_dir @output_dir = docset_dir.parent @documents_dir = docset_dir + 'Contents/Resources/Documents/' end def build!(all_declarations) docset_dir.rmtree if docset_dir.exist? copy_docs copy_icon if config.docset_icon write_plist create_index(all_declarations) create_archive create_xml if config.version && config.root_url end private def safe_name name.gsub(/[^a-z0-9_-]+/i, '_') end def write_plist info_plist_path = docset_dir + 'Contents/Info.plist' info_plist_path.open('w') do |plist| template = Pathname(__dir__) + 'docset_builder/info_plist.mustache' plist << Mustache.render( template.read, lowercase_name: name.downcase, lowercase_safe_name: safe_name.downcase, name: name, root_url: config.root_url, playground_url: config.docset_playground_url, ) end end def create_archive target = "#{safe_name}.tgz" source = docset_dir.basename.to_s options = { chdir: output_dir.to_s, [1, 2] => '/dev/null', # silence all output from `tar` } system('tar', "--exclude='.DS_Store'", '-cvzf', target, source, options) end def copy_docs files_to_copy = Pathname.glob(generated_docs_dir + '*') - [docset_dir, output_dir] FileUtils.mkdir_p documents_dir FileUtils.cp_r files_to_copy, documents_dir end def copy_icon FileUtils.cp config.docset_icon, docset_dir + 'icon.png' end def create_index(all_declarations) search_index_path = docset_dir + 'Contents/Resources/docSet.dsidx' SQLite3::Database.new(search_index_path.to_s) do |db| db.execute('CREATE TABLE searchIndex(' \ 'id INTEGER PRIMARY KEY, name TEXT, type TEXT, path TEXT);') db.execute('CREATE UNIQUE INDEX anchor ON ' \ 'searchIndex (name, type, path);') all_declarations.select(&:type).each do |doc| db.execute('INSERT OR IGNORE INTO searchIndex(name, type, path) ' \ 'VALUES (?, ?, ?);', [doc.name, doc.type.dash_type, doc.filepath]) end end end def create_xml (output_dir + "#{safe_name}.xml").open('w') do |xml| url = URI.join(config.root_url, "docsets/#{safe_name}.tgz") xml << "#{config.version}#{url}" \ "\n" end end # The web URL where the user intends to place the docset XML file. def dash_url return nil unless config.dash_url || config.root_url config.dash_url || URI.join( config.root_url, "docsets/#{safe_name}.xml", ) end public # The dash-feed:// URL that links from the Dash icon in generated # docs. This is passed to the Dash app and encodes the actual web # `dash_url` where the user has placed the XML file. # # Unfortunately for historical reasons this is *also* called the # 'dash_url' where it appears in mustache templates and so on. def dash_feed_url dash_url&.then do |url| "dash-feed://#{ERB::Util.url_encode(url.to_s)}" end end end end end ================================================ FILE: lib/jazzy/documentation_generator.rb ================================================ # frozen_string_literal: true require 'pathname' require 'jazzy/jazzy_markdown' require 'jazzy/source_document' module Jazzy module DocumentationGenerator extend Config::Mixin def self.source_docs documentation_entries.map do |file_path| SourceDocument.new.tap do |sd| sd.name = File.basename(file_path, '.md') sd.overview = overview Pathname(file_path) sd.usr = "documentation.#{sd.name}" end end end def self.overview(file_path) return '' unless file_path&.exist? file_path.read end def self.documentation_entries return [] unless config.documentation_glob_configured && config.documentation_glob config.documentation_glob.select { |e| File.file? e } end end end ================================================ FILE: lib/jazzy/executable.rb ================================================ # frozen_string_literal: true module Jazzy module Executable class IO < Array def initialize(io = nil) super() @io = io end def <<(value) super ensure @io << value.to_s if @io end def to_s join("\n") end end class << self def execute_command(executable, args, raise_on_failure, env: {}) require 'shellwords' bin = `which #{executable.to_s.shellescape}`.strip raise "Unable to locate the executable `#{executable}`" if bin.empty? require 'open4' stdout = IO.new stderr = IO.new($stderr) options = { stdout: stdout, stderr: stderr, status: true } status = Open4.spawn(env, bin, *args, options) unless status.success? full_command = "#{bin.shellescape} #{args.map(&:shellescape)}" output = stdout.to_s << stderr.to_s if raise_on_failure raise "#{full_command}\n\n#{output}" else warn("[!] Failed: #{full_command}") end end [stdout.to_s, status] end end end end ================================================ FILE: lib/jazzy/gem_version.rb ================================================ # frozen_string_literal: true module Jazzy VERSION = '0.15.4' unless defined? Jazzy::VERSION end ================================================ FILE: lib/jazzy/grouper.rb ================================================ # frozen_string_literal: true module Jazzy # This module deals with arranging top-level declarations and guides into # groups automatically and/or using a custom list. module Grouper extend Config::Mixin # Group root-level docs by custom categories (if any) and type or module def self.group_docs(docs, doc_index) custom_categories, docs = group_custom_categories(docs, doc_index) unlisted_prefix = config.custom_categories_unlisted_prefix type_category_prefix = custom_categories.any? ? unlisted_prefix : '' all_categories = custom_categories + if config.merge_modules == :all group_docs_by_type(docs, type_category_prefix) else group_docs_by_module(docs, type_category_prefix) end merge_consecutive_marks(all_categories) end # Group root-level docs by type def self.group_docs_by_type(docs, type_category_prefix) type_groups = SourceDeclaration::Type.all.map do |type| children, docs = docs.partition { |doc| doc.type == type } make_type_group(children, type, type_category_prefix) end merge_categories(type_groups.compact) + docs end # Group root-level docs by module name def self.group_docs_by_module(docs, type_category_prefix) guide_categories, docs = group_guides(docs, type_category_prefix) module_categories = docs .group_by(&:doc_module_name) .map do |name, module_docs| make_group( module_docs, name, "The following declarations are provided by module #{name}.", ) end guide_categories + module_categories end def self.group_custom_categories(docs, doc_index) group = config.custom_categories.map do |category| children = category['children'].map do |selector| selected = select_docs(doc_index, selector) selected.map do |doc| unless doc.parent_in_code.nil? warn "WARNING: Declaration \"#{doc.fully_qualified_module_name}\" " \ 'specified in categories file exists but is not top-level and ' \ 'cannot be included here' next nil end docs.delete(doc) end end.flatten.compact # Category config overrides alphabetization children.each.with_index { |child, i| child.nav_order = i } make_group(children, category['name'], '') end [group.compact, docs] end def self.select_docs(doc_index, selector) if selector.is_a?(String) unless single_doc = doc_index.lookup(selector) warn 'WARNING: No documented top-level declarations match ' \ "name \"#{selector}\" specified in categories file" return [] end [single_doc] else doc_index.lookup_regex(selector['regex']) .sort_by(&:name) end end def self.group_guides(docs, prefix) guides, others = docs.partition { |doc| doc.type.markdown? } return [[], others] unless guides.any? [[make_type_group(guides, guides.first.type, prefix)], others] end def self.make_type_group(docs, type, type_category_prefix) make_group( docs, type_category_prefix + type.plural_name, "The following #{type.plural_name.downcase} are available globally.", type_category_prefix + type.plural_url_name, ) end # Join categories with the same name (eg. ObjC and Swift classes) def self.merge_categories(categories) merged = [] categories.each do |new_category| if existing = merged.find { |cat| cat.name == new_category.name } existing.children += new_category.children else merged.append(new_category) end end merged end def self.make_group(group, name, abstract, url_name = nil) group.reject! { |decl| decl.name.empty? } unless group.empty? SourceDeclaration.new.tap do |sd| sd.type = SourceDeclaration::Type.overview sd.name = name sd.url_name = url_name sd.abstract = Markdown.render(abstract) sd.children = group end end end # Merge consecutive sections with the same mark into one section # Needed because of pulling various decls into groups def self.merge_consecutive_marks(docs) prev_mark = nil docs.each do |doc| if prev_mark&.can_merge?(doc.mark) doc.mark = prev_mark end prev_mark = doc.mark merge_consecutive_marks(doc.children) end end end end ================================================ FILE: lib/jazzy/highlighter.rb ================================================ # frozen_string_literal: true require 'rouge' module Jazzy # This module helps highlight code module Highlighter SWIFT = 'swift' OBJC = 'objective_c' class Formatter < Rouge::Formatters::HTML def initialize(language) @language = language super() end def stream(tokens, &block) yield "
"
        super
        yield "
\n" end end def self.highlight_swift(source) highlight(source, SWIFT) end def self.highlight_objc(source) highlight(source, OBJC) end def self.highlight(source, language) source && Rouge.highlight(source, language, Formatter.new(language)) end end end ================================================ FILE: lib/jazzy/jazzy_markdown.rb ================================================ # frozen_string_literal: true require 'redcarpet' require 'rouge' require 'rouge/plugins/redcarpet' module Jazzy module Markdown # Publish if generated HTML needs math support class << self; attr_accessor :has_math; end module Footnotes # Global unique footnote ID def self.next_footnote @next_footnote ||= 0 @next_footnote += 1 end # Per-render map from user to global ID attr_accessor :footnotes_hash def reset @footnotes_hash = {} end def map_footnote(user_num) footnotes_hash.fetch(user_num) do footnotes_hash[user_num] = Footnotes.next_footnote end end def footnote_ref(num) mapped = map_footnote(num) "" \ "#{num}" end # follow native redcarpet: backlink goes before the first

tag def footnote_def(text, num) mapped = map_footnote(num) "\n
  • " + text.sub(%r{(?=

    )}, " ") + '
  • ' end end # rubocop:disable Metrics/ClassLength class JazzyHTML < Redcarpet::Render::HTML include Redcarpet::Render::SmartyPants include Rouge::Plugins::Redcarpet include Footnotes attr_accessor :default_language def header(text, header_level) text_slug = text.gsub(/[^[[:word:]]]+/, '-') .downcase .sub(/^-/, '') .sub(/-$/, '') "" \ "#{text}" \ "\n" end def codespan(text) case text when /^\$\$(.*)\$\$$/m o = ["

    ", Regexp.last_match[1], '

    '] Markdown.has_math = true when /^\$(.*)\$$/m o = ["", Regexp.last_match[1], ''] Markdown.has_math = true else o = ['', text.to_s, ''] end o[0] + CGI.escapeHTML(o[1]) + o[2] end # List from # https://github.com/apple/swift/blob/master/include/swift/Markup/SimpleFields.def UNIQUELY_HANDLED_CALLOUTS = %w[parameters parameter returns].freeze GENERAL_CALLOUTS = %w[attention author authors bug complexity copyright date experiment important invariant keyword mutatingvariant nonmutatingvariant note postcondition precondition recommended recommendedover remark remarks requires see seealso since todo throws version warning].freeze SPECIAL_LIST_TYPES = (UNIQUELY_HANDLED_CALLOUTS + GENERAL_CALLOUTS).freeze SPECIAL_LIST_TYPE_REGEX = %r{ \A\s* # optional leading spaces (

    \s*)? # optional opening p tag # any one of our special list types (#{SPECIAL_LIST_TYPES.map(&Regexp.method(:escape)).join('|')}) [\s:] # followed by either a space or a colon }ix.freeze ELIDED_LI_TOKEN = '7wNVzLB0OYPL2eGlPKu8q4vITltqh0Y6DPZf659TPMAeYh49o' def list_item(text, _list_type) if text =~ SPECIAL_LIST_TYPE_REGEX type = Regexp.last_match(2) if UNIQUELY_HANDLED_CALLOUTS.include? type.downcase return ELIDED_LI_TOKEN end return render_list_aside(type, text.sub(/#{Regexp.escape(type)}:\s+/, '')) end "

  • #{text.strip}
  • \n" end def render_list_aside(type, text) "#{render_aside(type, text).chomp}
      \n" end def render_aside(type, text) <<-HTML

      #{type.underscore.humanize}

      #{text}
      HTML end def list(text, list_type) elided = text.gsub!(ELIDED_LI_TOKEN, '') return if text =~ /\A\s*\Z/ && elided tag = list_type == :ordered ? 'ol' : 'ul' "\n<#{tag}>\n#{text}\n" .gsub(%r{\n?
        \n?
      }, '') end # List from # https://developer.apple.com/documentation/xcode/formatting-your-documentation-content#Add-Notes-and-Other-Asides DOCC_CALLOUTS = %w[note important warning tip experiment].freeze DOCC_CALLOUT_REGEX = %r{ \A\s* # optional leading spaces (?:

      \s*)? # optional opening p tag # any one of the callout names (#{DOCC_CALLOUTS.map(&Regexp.method(:escape)).join('|')}) : # followed directly by a colon }ix.freeze def block_quote(html) if html =~ DOCC_CALLOUT_REGEX type = Regexp.last_match[1] render_aside(type, html.sub(/#{Regexp.escape(type)}:\s*/, '')) else "\n

      \n#{html}
      \n" end end def block_code(code, language) super(code, language || default_language) end def rouge_formatter(lexer) Highlighter::Formatter.new(lexer.tag) end end # rubocop:enable Metrics/ClassLength REDCARPET_OPTIONS = { autolink: true, fenced_code_blocks: true, no_intra_emphasis: true, strikethrough: true, space_after_headers: false, tables: true, lax_spacing: true, footnotes: true, }.freeze # Spot and capture returns & param HTML for separate display. class JazzyDeclarationHTML < JazzyHTML attr_reader :returns, :parameters def reset @returns = nil @parameters = {} super end INTRO_PAT = '\A(?\s*(

      \s*)?)' OUTRO_PAT = '(?.*)\z' RETURNS_REGEX = /#{INTRO_PAT}returns:#{OUTRO_PAT}/im.freeze IDENT_PAT = '(?\S+)' # Param formats: normal swift, objc via sourcekitten, and # possibly inside 'Parameters:' PARAM_PAT1 = "(parameter +#{IDENT_PAT}\\s*:)" PARAM_PAT2 = "(parameter:\\s*#{IDENT_PAT}\\s+)" PARAM_PAT3 = "(#{IDENT_PAT}\\s*:)" PARAM_PAT = "(?:#{PARAM_PAT1}|#{PARAM_PAT2}|#{PARAM_PAT3})" PARAM_REGEX = /#{INTRO_PAT}#{PARAM_PAT}#{OUTRO_PAT}/im.freeze def list_item(text, _list_type) if text =~ RETURNS_REGEX @returns = render_param_returns(Regexp.last_match) elsif text =~ PARAM_REGEX @parameters[Regexp.last_match(:param)] = render_param_returns(Regexp.last_match) end super end def render_param_returns(matches) body = matches[:intro].strip + matches[:outro].strip body = "

      #{body}

      " unless body.start_with?('

      ') # call smartypants for pretty quotes etc. postprocess(body) end end def self.renderer @renderer ||= JazzyDeclarationHTML.new end def self.markdown @markdown ||= Redcarpet::Markdown.new(renderer, REDCARPET_OPTIONS) end # Produces

      -delimited block content def self.render(markdown_text, default_language = nil) renderer.reset renderer.default_language = default_language markdown.render(markdown_text) end # Produces -delimited inline content def self.render_inline(markdown_text, default_language = nil) render(markdown_text, default_language) .sub(%r{^

      (.*)

      $}, '\1') end def self.rendered_returns renderer.returns end def self.rendered_parameters renderer.parameters end class JazzyCopyright < Redcarpet::Render::HTML def link(link, _title, content) %(#{content}) end end def self.copyright_markdown @copyright_markdown ||= Redcarpet::Markdown.new( JazzyCopyright, REDCARPET_OPTIONS, ) end def self.render_copyright(markdown_text) copyright_markdown.render(markdown_text) end end end ================================================ FILE: lib/jazzy/podspec_documenter.rb ================================================ # frozen_string_literal: true require 'tmpdir' require 'json' module Jazzy # rubocop:disable Metrics/ClassLength class PodspecDocumenter attr_reader :podspec def initialize(podspec) @podspec = podspec end # Build documentation from the given options # @param [Config] options def sourcekitten_output(config) installation_root = Pathname(Dir.mktmpdir(['jazzy', podspec.name])) installation_root.rmtree if installation_root.exist? Pod::Config.instance.with_changes(installation_root: installation_root, verbose: false) do sandbox = Pod::Sandbox.new(Pod::Config.instance.sandbox_root) installer = Pod::Installer.new(sandbox, podfile(config)) installer.install! stdout = Dir.chdir(sandbox.root) do targets = installer.pod_targets .select { |pt| pt.pod_name == podspec.root.name } .map(&:label) targets.map do |t| args = %W[doc --module-name #{podspec.module_name} -- -target #{t}] SourceKitten.run_sourcekitten(args) end end stdout.reduce([]) { |a, s| a + JSON.parse(s) }.to_json end end def self.create_podspec(podspec_path) case podspec_path when Pathname, String require 'cocoapods' Pod::Specification.from_file(podspec_path) end end # rubocop:disable Metrics/MethodLength def self.apply_config_defaults(podspec, config) return unless podspec unless config.author_name_configured config.author_name = author_name(podspec) config.author_name_configured = true end unless config.module_name_known? config.module_name = podspec.module_name config.module_name_configured = true end unless config.author_url_configured config.author_url = podspec.homepage || github_file_prefix(podspec) config.author_url_configured = true end unless config.version_configured config.version = podspec.version.to_s config.version_configured = true end unless config.source_host_files_url_configured config.source_host_files_url = github_file_prefix(podspec) config.source_host_files_url_configured = true end unless config.swift_version_configured trunk_swift_build = podspec.attributes_hash['pushed_with_swift_version'] config.swift_version = trunk_swift_build if trunk_swift_build config.swift_version_configured = true end end # rubocop:enable Metrics/MethodLength private # @!group Config helper methods def self.author_name(podspec) if podspec.authors.respond_to? :to_hash podspec.authors.keys.to_sentence || '' elsif podspec.authors.respond_to? :to_ary podspec.authors.to_sentence end || podspec.authors || '' end private_class_method :author_name def self.github_file_prefix(podspec) return unless podspec.source[:url] =~ %r{github.com[:/]+(.+)/(.+)} org, repo = Regexp.last_match return unless rev = podspec.source[:tag] || podspec.source[:commit] "https://github.com/#{org}/#{repo.sub(/\.git$/, '')}/blob/#{rev}" end private_class_method :github_file_prefix # Latest valid value for SWIFT_VERSION. LATEST_SWIFT_VERSION = '6' private_constant :LATEST_SWIFT_VERSION # All valid values for SWIFT_VERSION that are longer # than a major version number. Ordered ascending. LONG_SWIFT_VERSIONS = ['4.2'].freeze private_constant :LONG_SWIFT_VERSIONS # Go from a full Swift version like 4.2.1 to # something valid for SWIFT_VERSION. def compiler_swift_version(user_version) unless user_version return podspec_swift_version || LATEST_SWIFT_VERSION end LONG_SWIFT_VERSIONS.select do |version| user_version.start_with?(version) end.last || "#{user_version[0]}.0" end def podspec_swift_version # `swift_versions` exists from CocoaPods 1.7 if podspec.respond_to?('swift_versions') podspec.swift_versions.max else podspec.swift_version end end # @!group SourceKitten output helper methods def pod_path if podspec.defined_in_file podspec.defined_in_file.parent else config.source_directory end end # rubocop:disable Metrics/MethodLength def podfile(config) swift_version = compiler_swift_version(config.swift_version) podspec = @podspec path = pod_path @podfile ||= Pod::Podfile.new do config.pod_sources.each do |src| source src end install! 'cocoapods', integrate_targets: false, deterministic_uuids: false [podspec, *podspec.recursive_subspecs].each do |ss| next if ss.test_specification ss.available_platforms.each do |p| # Travis builds take too long when building docs for all available # platforms for the Moya integration spec, so we just document OSX. # TODO: remove once jazzy is fast enough. next if ENV['JAZZY_INTEGRATION_SPECS'] && p.name != :osx target("Jazzy-#{ss.name.gsub('/', '__')}-#{p.name}") do use_frameworks! platform p.name, p.deployment_target pod ss.name, path: path.realpath.to_s current_target_definition.swift_version = swift_version end end end end end # rubocop:enable Metrics/MethodLength end # rubocop:enable Metrics/ClassLength end ================================================ FILE: lib/jazzy/search_builder.rb ================================================ # frozen_string_literal: true module Jazzy module SearchBuilder def self.build(source_module, output_dir) decls = source_module.all_declarations.select do |d| d.type && d.name && !d.name.empty? end index = decls.to_h do |d| [d.url, { name: d.name, abstract: d.abstract && d.abstract.split("\n").map(&:strip).first, parent_name: d.parent_in_code&.name, }.reject { |_, v| v.nil? || v.empty? }] end File.write(File.join(output_dir, 'search.json'), index.to_json) end end end ================================================ FILE: lib/jazzy/source_declaration/access_control_level.rb ================================================ # frozen_string_literal: true module Jazzy class SourceDeclaration class AccessControlLevel include Comparable # Order matters LEVELS = %i[private fileprivate internal package public open].freeze LEVELS_INDEX = LEVELS.to_h { |i| [i, LEVELS.index(i)] }.freeze attr_reader :level def initialize(level) @level = level end # From a SourceKit accessibility string def self.from_accessibility(accessibility) return nil if accessibility.nil? if accessibility =~ /^source\.lang\.swift\.accessibility\.(.*)$/ && (matched = Regexp.last_match(1).to_sym) && !LEVELS_INDEX[matched].nil? return new(matched) end raise "cannot initialize AccessControlLevel with '#{accessibility}'" end # From a SourceKit declaration hash def self.from_doc(doc) return AccessControlLevel.internal if implicit_deinit?(doc) from_documentation_attribute(doc) || from_accessibility(doc['key.accessibility']) || from_doc_explicit_declaration(doc) || AccessControlLevel.internal # fallback on internal ACL end # Workaround `deinit` being always technically public def self.implicit_deinit?(doc) doc['key.name'] == 'deinit' && from_doc_explicit_declaration(doc).nil? end # From a Swift declaration def self.from_doc_explicit_declaration(doc) declaration = doc['key.parsed_declaration'] LEVELS.each do |level| if declaration =~ /\b#{level}\b/ return send(level) end end nil end # From a config instruction def self.from_human_string(string) normalized = string.to_s.downcase.to_sym if LEVELS_INDEX[normalized].nil? raise "cannot initialize AccessControlLevel with '#{string}'" end send(normalized) end # From a @_documentation(visibility:) attribute def self.from_documentation_attribute(doc) if doc['key.annotated_decl'] =~ /@_documentation\(\s*visibility\s*:\s*(\w+)/ from_human_string(Regexp.last_match[1]) end end # Define `AccessControlLevel.public` etc. LEVELS.each do |level| define_singleton_method(level) do new(level) end end # Comparing access levels def <=>(other) LEVELS_INDEX[level] <=> LEVELS_INDEX[other.level] end def included_levels LEVELS_INDEX.select { |_, v| v >= LEVELS_INDEX[level] }.keys end def excluded_levels LEVELS_INDEX.select { |_, v| v < LEVELS_INDEX[level] }.keys end end end end ================================================ FILE: lib/jazzy/source_declaration/type.rb ================================================ # frozen_string_literal: true require 'logger' require 'active_support' require 'active_support/inflector' module Jazzy class SourceDeclaration # rubocop:disable Metrics/ClassLength class Type def self.all TYPES.keys.map { |k| new(k) }.reject { |t| t.name.nil? } end attr_reader :kind def initialize(kind, declaration = nil) kind = fixup_kind(kind, declaration) if declaration @kind = kind @type = TYPES[kind] end # Improve kind from full declaration def fixup_kind(kind, declaration) if kind == 'source.lang.swift.decl.class' && declaration.include?( 'actor', ) 'source.lang.swift.decl.actor' else kind end end def dash_type @type && @type[:dash] end def name @type && @type[:jazzy] end # kinds that are 'global' and should get their own pages # with --separate-global-declarations def global? @type && @type[:global] end # name to use for type subdirectory in URLs for back-compatibility def url_name @type && (@type[:url] || @type[:jazzy]) end def name_controlled_manually? !kind.start_with?('source') # "'source'.lang..." for Swift # or "'source'kitten.source..." for Objective-C # but not "Overview" for navigation groups. end def plural_name name.pluralize end def plural_url_name url_name.pluralize end def objc_mark? kind == 'sourcekitten.source.lang.objc.mark' end # covers MARK: TODO: FIXME: comments def swift_mark? kind == 'source.lang.swift.syntaxtype.comment.mark' end def mark? objc_mark? || swift_mark? end # mark that should start a new task section def task_mark?(name) objc_mark? || (swift_mark? && name.start_with?('MARK: ')) end def objc_enum? kind == 'sourcekitten.source.lang.objc.decl.enum' end def objc_typedef? kind == 'sourcekitten.source.lang.objc.decl.typedef' end def objc_category? kind == 'sourcekitten.source.lang.objc.decl.category' end def objc_class? kind == 'sourcekitten.source.lang.objc.decl.class' end def swift_type? kind.include? 'swift' end def swift_enum_case? kind == 'source.lang.swift.decl.enumcase' end def swift_enum_element? kind == 'source.lang.swift.decl.enumelement' end def should_document? declaration? && !param? && !generic_type_param? end def declaration? kind.start_with?('source.lang.swift.decl', 'sourcekitten.source.lang.objc.decl') end def extension? swift_extension? || objc_category? end def swift_extension? kind =~ /^source\.lang\.swift\.decl\.extension.*/ end def swift_extensible? kind =~ /^source\.lang\.swift\.decl\.(class|struct|protocol|enum|actor)$/ end def swift_protocol? kind == 'source.lang.swift.decl.protocol' end def swift_typealias? kind == 'source.lang.swift.decl.typealias' end def swift_global_function? kind == 'source.lang.swift.decl.function.free' end def param? # SourceKit strangely categorizes initializer parameters as local # variables, so both kinds represent a parameter in jazzy. ['source.lang.swift.decl.var.parameter', 'source.lang.swift.decl.var.local'].include?(kind) end def generic_type_param? kind == 'source.lang.swift.decl.generic_type_param' end def swift_variable? kind.start_with?('source.lang.swift.decl.var') end def objc_unexposed? kind == 'sourcekitten.source.lang.objc.decl.unexposed' end OVERVIEW_KIND = 'Overview' def self.overview Type.new(OVERVIEW_KIND) end def overview? kind == OVERVIEW_KIND end MARKDOWN_KIND = 'document.markdown' def self.markdown Type.new(MARKDOWN_KIND) end def markdown? kind == MARKDOWN_KIND end def hash kind.hash end alias equals == def ==(other) other && kind == other.kind end TYPES = { # Markdown MARKDOWN_KIND => { jazzy: 'Guide', dash: 'Guide', }.freeze, # Group/Overview OVERVIEW_KIND => { jazzy: nil, dash: 'Section', }.freeze, # Objective-C 'sourcekitten.source.lang.objc.decl.unexposed' => { jazzy: 'Unexposed', dash: 'Unexposed', }.freeze, 'sourcekitten.source.lang.objc.decl.category' => { jazzy: 'Category', dash: 'Extension', global: true, }.freeze, 'sourcekitten.source.lang.objc.decl.class' => { jazzy: 'Class', dash: 'Class', global: true, }.freeze, 'sourcekitten.source.lang.objc.decl.constant' => { jazzy: 'Constant', dash: 'Constant', global: true, }.freeze, 'sourcekitten.source.lang.objc.decl.enum' => { jazzy: 'Enumeration', url: 'Enum', dash: 'Enum', global: true, }.freeze, 'sourcekitten.source.lang.objc.decl.enumcase' => { jazzy: 'Enumeration Case', dash: 'Case', }.freeze, 'sourcekitten.source.lang.objc.decl.initializer' => { jazzy: 'Initializer', dash: 'Initializer', }.freeze, 'sourcekitten.source.lang.objc.decl.method.class' => { jazzy: 'Class Method', dash: 'Method', }.freeze, 'sourcekitten.source.lang.objc.decl.method.instance' => { jazzy: 'Instance Method', dash: 'Method', }.freeze, 'sourcekitten.source.lang.objc.decl.property' => { jazzy: 'Property', dash: 'Property', }.freeze, 'sourcekitten.source.lang.objc.decl.protocol' => { jazzy: 'Protocol', dash: 'Protocol', global: true, }.freeze, 'sourcekitten.source.lang.objc.decl.typedef' => { jazzy: 'Type Definition', dash: 'Type', global: true, }.freeze, 'sourcekitten.source.lang.objc.mark' => { jazzy: 'Mark', dash: 'Mark', }.freeze, 'sourcekitten.source.lang.objc.decl.function' => { jazzy: 'Function', dash: 'Function', global: true, }.freeze, 'sourcekitten.source.lang.objc.decl.struct' => { jazzy: 'Structure', url: 'Struct', dash: 'Struct', global: true, }.freeze, 'sourcekitten.source.lang.objc.decl.union' => { jazzy: 'Union', dash: 'Union', global: true, }.freeze, 'sourcekitten.source.lang.objc.decl.field' => { jazzy: 'Field', dash: 'Field', }.freeze, 'sourcekitten.source.lang.objc.decl.ivar' => { jazzy: 'Instance Variable', dash: 'Ivar', }.freeze, 'sourcekitten.source.lang.objc.module.import' => { jazzy: 'Module', dash: 'Module', }.freeze, # Swift 'source.lang.swift.decl.actor' => { jazzy: 'Actor', dash: 'Actor', global: true, }.freeze, 'source.lang.swift.decl.function.accessor.address' => { jazzy: 'Addressor', dash: 'Function', }.freeze, 'source.lang.swift.decl.function.accessor.didset' => { jazzy: 'didSet Observer', dash: 'Function', }.freeze, 'source.lang.swift.decl.function.accessor.getter' => { jazzy: 'Getter', dash: 'Function', }.freeze, 'source.lang.swift.decl.function.accessor.mutableaddress' => { jazzy: 'Mutable Addressor', dash: 'Function', }.freeze, 'source.lang.swift.decl.function.accessor.setter' => { jazzy: 'Setter', dash: 'Function', }.freeze, 'source.lang.swift.decl.function.accessor.willset' => { jazzy: 'willSet Observer', dash: 'Function', }.freeze, 'source.lang.swift.decl.function.operator' => { jazzy: 'Operator', dash: 'Function', }.freeze, 'source.lang.swift.decl.function.operator.infix' => { jazzy: 'Infix Operator', dash: 'Function', }.freeze, 'source.lang.swift.decl.function.operator.postfix' => { jazzy: 'Postfix Operator', dash: 'Function', }.freeze, 'source.lang.swift.decl.function.operator.prefix' => { jazzy: 'Prefix Operator', dash: 'Function', }.freeze, 'source.lang.swift.decl.function.method.class' => { jazzy: 'Class Method', dash: 'Method', }.freeze, 'source.lang.swift.decl.var.class' => { jazzy: 'Class Variable', dash: 'Variable', }.freeze, 'source.lang.swift.decl.class' => { jazzy: 'Class', dash: 'Class', global: true, }.freeze, 'source.lang.swift.decl.function.constructor' => { jazzy: 'Initializer', dash: 'Constructor', }.freeze, 'source.lang.swift.decl.function.destructor' => { jazzy: 'Deinitializer', dash: 'Method', }.freeze, 'source.lang.swift.decl.var.global' => { jazzy: 'Global Variable', dash: 'Global', global: true, }.freeze, 'source.lang.swift.decl.enumcase' => { jazzy: 'Enumeration Case', dash: 'Case', }.freeze, 'source.lang.swift.decl.enumelement' => { jazzy: 'Enumeration Element', dash: 'Element', }.freeze, 'source.lang.swift.decl.enum' => { jazzy: 'Enumeration', url: 'Enum', dash: 'Enum', global: true, }.freeze, 'source.lang.swift.decl.extension' => { jazzy: 'Extension', dash: 'Extension', global: true, }.freeze, 'source.lang.swift.decl.extension.class' => { jazzy: 'Class Extension', dash: 'Extension', global: true, }.freeze, 'source.lang.swift.decl.extension.enum' => { jazzy: 'Enumeration Extension', dash: 'Extension', global: true, }.freeze, 'source.lang.swift.decl.extension.protocol' => { jazzy: 'Protocol Extension', dash: 'Extension', global: true, }.freeze, 'source.lang.swift.decl.extension.struct' => { jazzy: 'Structure Extension', dash: 'Extension', global: true, }.freeze, 'source.lang.swift.decl.function.free' => { jazzy: 'Function', dash: 'Function', global: true, }.freeze, 'source.lang.swift.decl.function.method.instance' => { jazzy: 'Instance Method', dash: 'Method', }.freeze, 'source.lang.swift.decl.var.instance' => { jazzy: 'Instance Variable', dash: 'Property', }.freeze, 'source.lang.swift.decl.var.local' => { jazzy: 'Local Variable', dash: 'Variable', }.freeze, 'source.lang.swift.decl.var.parameter' => { jazzy: 'Parameter', dash: 'Parameter', }.freeze, 'source.lang.swift.decl.protocol' => { jazzy: 'Protocol', dash: 'Protocol', global: true, }.freeze, 'source.lang.swift.decl.function.method.static' => { jazzy: 'Static Method', dash: 'Method', }.freeze, 'source.lang.swift.decl.var.static' => { jazzy: 'Static Variable', dash: 'Variable', }.freeze, 'source.lang.swift.decl.struct' => { jazzy: 'Structure', url: 'Struct', dash: 'Struct', global: true, }.freeze, 'source.lang.swift.decl.function.subscript' => { jazzy: 'Subscript', dash: 'Method', }.freeze, 'source.lang.swift.decl.typealias' => { jazzy: 'Type Alias', url: 'Typealias', dash: 'Alias', global: true, }.freeze, 'source.lang.swift.decl.generic_type_param' => { jazzy: 'Generic Type Parameter', dash: 'Parameter', }.freeze, 'source.lang.swift.decl.associatedtype' => { jazzy: 'Associated Type', dash: 'Alias', }.freeze, 'source.lang.swift.decl.macro' => { jazzy: 'Macro', dash: 'Macro', }.freeze, }.freeze end # rubocop:enable Metrics/ClassLength end end ================================================ FILE: lib/jazzy/source_declaration.rb ================================================ # frozen_string_literal: true require 'jazzy/source_declaration/access_control_level' require 'jazzy/source_declaration/type' module Jazzy # rubocop:disable Metrics/ClassLength class SourceDeclaration # kind of declaration (e.g. class, variable, function) attr_accessor :type # static type of declared element (e.g. String.Type -> ()) attr_accessor :typename # Give the item its own page or just inline into parent? def render_as_page? children.any? || (Config.instance.separate_global_declarations && type.global?) end def swift? type.swift_type? end def highlight_language swift? ? Highlighter::SWIFT : Highlighter::OBJC end # When referencing this item from its parent category, # include the content or just link to it directly? def omit_content_from_parent? Config.instance.separate_global_declarations && render_as_page? end # Element containing this declaration in the code attr_accessor :parent_in_code # Logical parent in the documentation. May differ from parent_in_code # because of top-level categories and merged extensions. attr_accessor :parent_in_docs # counterpart of parent_in_docs attr_reader :children def children=(new_children) # Freeze to ensure that parent_in_docs stays in sync @children = new_children.freeze @children.each { |c| c.parent_in_docs = self } end # Chain of parent_in_code from top level to self. (Includes self.) def namespace_path namespace_ancestors + [self] end def namespace_ancestors if parent_in_code parent_in_code.namespace_path else [] end end # 'OuterType.NestedType.method(arg:)' def fully_qualified_name namespace_path.map(&:name).join('.') end # :name doesn't include any generic type params. # This regexp matches any generic type params in parent names. def fully_qualified_name_regexp Regexp.new(namespace_path.map(&:name) .map { |n| Regexp.escape(n) } .join('(?:<.*?>)?\.')) end def fully_qualified_module_name_parts path = namespace_path path.map(&:name).prepend(path.first.module_name).compact end # 'MyModule.OuterType.NestedType.method(arg:)' def fully_qualified_module_name fully_qualified_module_name_parts.join('.') end # List of doc_parent decls, .last is self def docs_path (parent_in_docs&.docs_path || []) + [self] end # If this declaration is an objc category, returns an array with the name # of the extended objc class and the category name itself, i.e. # ["NSString", "MyMethods"], nil otherwise. def objc_category_name name.split(/[()]/) if type.objc_category? end def swift_objc_extension? type.swift_extension? && usr&.start_with?('c:objc') end def swift_extension_objc_name return unless type.swift_extension? && usr usr.split('(cs)').last end # The language in the templates for display def display_language return 'Swift' if swift? Config.instance.hide_objc? ? 'Swift' : 'Objective-C' end def display_declaration return declaration if swift? Config.instance.hide_objc? ? other_language_declaration : declaration end def display_other_language_declaration other_language_declaration unless Config.instance.hide_objc? || Config.instance.hide_swift? end attr_accessor :file attr_accessor :line attr_accessor :column attr_accessor :usr attr_accessor :type_usr attr_accessor :module_name attr_accessor :name attr_accessor :objc_name attr_accessor :declaration attr_accessor :other_language_declaration attr_accessor :abstract attr_accessor :default_impl_abstract attr_accessor :from_protocol_extension attr_accessor :discussion attr_accessor :return attr_accessor :parameters attr_accessor :url attr_accessor :mark attr_accessor :access_control_level attr_accessor :start_line attr_accessor :end_line attr_accessor :nav_order attr_accessor :url_name attr_accessor :deprecated attr_accessor :deprecation_message attr_accessor :unavailable attr_accessor :unavailable_message attr_accessor :generic_requirements attr_accessor :inherited_types attr_accessor :async # The name of the module being documented that contains this # declaration. Only different from module_name when this is # an extension of a type from another module. Nil for guides. attr_accessor :doc_module_name def usage_discouraged? unavailable || deprecated end def filepath CGI.unescape(url) end # Base filename (no extension) for the item def docs_filename result = url_name || name # Workaround functions sharing names with # different argument types (f(a:Int) vs. f(a:String)) return result unless type.swift_global_function? result + "_#{type_usr}" end def constrained_extension? type.swift_extension? && generic_requirements end def mark_for_children if constrained_extension? SourceMark.new_generic_requirements(generic_requirements) else SourceMark.new end end def inherited_types? inherited_types && !inherited_types.empty? end # Is there at least one inherited type that is not in the given list? def other_inherited_types?(unwanted) return false unless inherited_types? inherited_types.any? { |t| !unwanted.include?(t) } end # Pre-Swift 5.6: SourceKit only sets module_name for imported modules # Swift 5.6+: module_name is always set def type_from_doc_module? !type.extension? || (swift? && usr && (module_name.nil? || module_name == doc_module_name)) end # Don't ask the user to write documentation for types being extended # from other modules. Compile errors leave no docs and a `nil` USR. def mark_undocumented? !swift? || (usr && !extension_of_external_type?) end def extension_of_external_type? !module_name.nil? && !Config.instance.module_name?(module_name) end # Is it unclear from context what module the (top-level) decl is from? def ambiguous_module_name?(group_name) extension_of_external_type? || (Config.instance.multiple_modules? && !module_name.nil? && group_name != module_name) end # Does the user need help understanding how to get this declaration? def need_doc_module_note? return false unless Config.instance.multiple_modules? return false if docs_path.first.name == doc_module_name if parent_in_code.nil? # Top-level decls with no page of their own !render_as_page? else # Members added by extension parent_in_code.module_name != doc_module_name end end # Info text for contents page by collapsed item name def declaration_note notes = [ default_impl_abstract ? 'default implementation' : nil, from_protocol_extension ? 'extension method' : nil, async ? 'asynchronous' : nil, need_doc_module_note? ? "from #{doc_module_name}" : nil, ].compact notes.join(', ').upcase_first unless notes.empty? end # For matching where `Self` is equivalent without considering # constraints def simplified_typename typename&.gsub(//, '') end # Is the candidate `SourceDeclaration` probably a default # implementation of this declaration? def default_implementation?(candidate) name == candidate.name && type == candidate.type && simplified_typename == candidate.simplified_typename && async == candidate.async end def readme? false end def alternative_abstract if file = alternative_abstract_file Pathname(file).read end end def alternative_abstract_file abstract_glob.select do |f| # allow Structs.md or Structures.md [name, url_name].include?(File.basename(f).split('.').first) end.first end def abstract_glob return [] unless Config.instance.abstract_glob_configured && Config.instance.abstract_glob Config.instance.abstract_glob.select { |e| File.file? e } end end # rubocop:enable Metrics/ClassLength end ================================================ FILE: lib/jazzy/source_document.rb ================================================ # frozen_string_literal: true require 'pathname' require 'jazzy/jazzy_markdown' module Jazzy # Standalone markdown docs including index.html class SourceDocument < SourceDeclaration attr_accessor :overview attr_accessor :readme_path def initialize super self.children = [] self.parameters = [] self.abstract = '' self.type = SourceDeclaration::Type.markdown self.mark = SourceMark.new end def self.make_index(readme_path) SourceDocument.new.tap do |sd| sd.name = 'index' sd.url = sd.name + '.html' sd.readme_path = readme_path end end def readme? url == 'index.html' end def render_as_page? true end def omit_content_from_parent? true end def config Config.instance end def url_name name.downcase.strip.tr(' ', '-').gsub(/[^[[:word:]]-]/, '') end def content(source_module) return readme_content(source_module) if name == 'index' overview end def readme_content(source_module) config_readme || fallback_readme || generated_readme(source_module) end def config_readme readme_path.read if readme_path&.exist? end def fallback_readme %w[README.md README.markdown README.mdown README].each do |potential_name| file = config.source_directory + potential_name return file.read if file.exist? end false end def generated_readme(source_module) if podspec = config.podspec ### License # #{license[:license]} <<-README # #{podspec.name} ### #{podspec.summary} #{podspec.description} ### Installation ```ruby pod '#{podspec.name}' ``` ### Authors #{source_module.author_name} README else <<-README # #{source_module.readme_title} ### Authors #{source_module.author_name} README end end end end ================================================ FILE: lib/jazzy/source_host.rb ================================================ # frozen_string_literal: true module Jazzy # Deal with different source code repositories module SourceHost # Factory to create the right source host def self.create(options) return unless options.source_host_url || options.source_host_files_url case options.source_host when :github then GitHub.new when :gitlab then GitLab.new when :bitbucket then BitBucket.new end end # Use GitHub as the default behaviour. class GitHub include Config::Mixin # Human readable name, appears in UI def name 'GitHub' end # Jazzy extension with logo def extension name.downcase end # Logo image filename within extension def image 'gh.png' end # URL to link to from logo def url config.source_host_url end # URL to link to from a SourceDeclaration. # Compare using `realpath` because `item.file` comes out of # SourceKit/etc. def item_url(item) return unless files_url && item.file realpath = item.file.realpath return unless realpath.to_path.start_with?(local_root_realpath) path = realpath.relative_path_from(local_root_realpath) fragment = if item.start_line && (item.start_line != item.end_line) item_url_multiline_fragment(item.start_line, item.end_line) else item_url_line_fragment(item.line) end "#{files_url}/#{path}##{fragment}" end private def files_url config.source_host_files_url end def local_root_realpath @local_root_realpath ||= config.source_directory.realpath.to_path end # Source host's line numbering link scheme def item_url_line_fragment(line) "L#{line}" end def item_url_multiline_fragment(start_line, end_line) "L#{start_line}-L#{end_line}" end end # GitLab very similar to GitHub class GitLab < GitHub def name 'GitLab' end def image 'gitlab.svg' end end # BitBucket has its own line number system class BitBucket < GitHub def name 'Bitbucket' end def image 'bitbucket.svg' end def item_url_line_fragment(line) "lines-#{line}" end def item_url_multiline_fragment(start_line, end_line) "lines-#{start_line}:#{end_line}" end end end end ================================================ FILE: lib/jazzy/source_mark.rb ================================================ # frozen_string_literal: true module Jazzy class SourceMark attr_accessor :name attr_accessor :has_start_dash attr_accessor :has_end_dash def initialize(mark_string = nil) return unless mark_string # Format: 'MARK: - NAME -' with dashes optional mark_content = mark_string.sub(/^MARK: /, '') if mark_content.empty? # Empty return elsif mark_content == '-' # Separator self.has_start_dash = true return end self.has_start_dash = mark_content.start_with?('- ') self.has_end_dash = mark_content.end_with?(' -') start_index = has_start_dash ? 2 : 0 end_index = has_end_dash ? -3 : -1 self.name = mark_content[start_index..end_index] end def self.new_generic_requirements(requirements) marked_up = requirements.gsub(/\b([^=:]\S*)\b/, '`\1`') text = "Available where #{marked_up}" new(text) end def empty? !name && !has_start_dash && !has_end_dash end def copy(other) self.name = other.name self.has_start_dash = other.has_start_dash self.has_end_dash = other.has_end_dash end # Can we merge the contents of another mark into our own? def can_merge?(other) other.empty? || other.name == name end end end ================================================ FILE: lib/jazzy/source_module.rb ================================================ # frozen_string_literal: true require 'uri' require 'jazzy/config' require 'jazzy/source_declaration' require 'jazzy/source_host' module Jazzy # A cache of info that is common across all page templating, gathered # from other parts of the program. class SourceModule include Config::Mixin attr_accessor :readme_title attr_accessor :docs attr_accessor :doc_coverage attr_accessor :doc_structure attr_accessor :author_name attr_accessor :author_url attr_accessor :dash_feed_url attr_accessor :host def initialize(docs, doc_structure, doc_coverage, docset_builder) self.docs = docs self.doc_structure = doc_structure self.doc_coverage = doc_coverage title = config.readme_title || config.module_names.first self.readme_title = title.empty? ? 'Index' : title self.author_name = config.author_name self.author_url = config.author_url self.host = SourceHost.create(config) self.dash_feed_url = docset_builder.dash_feed_url end def all_declarations all_declarations = [] visitor = lambda do |d| all_declarations.unshift(*d) d.map(&:children).each { |c| visitor[c] } end visitor[docs] all_declarations.reject { |doc| doc.name == 'index' } end end end ================================================ FILE: lib/jazzy/sourcekitten.rb ================================================ # frozen_string_literal: true require 'json' require 'pathname' require 'shellwords' require 'xcinvoke' require 'cgi' require 'rexml/document' require 'jazzy/config' require 'jazzy/executable' require 'jazzy/highlighter' require 'jazzy/source_declaration' require 'jazzy/source_mark' require 'jazzy/stats' require 'jazzy/grouper' require 'jazzy/doc_index' ELIDED_AUTOLINK_TOKEN = '36f8f5912051ae747ef441d6511ca4cb' def autolink_regex(middle_regex, after_highlight) start_tag_re, end_tag_re = if after_highlight [//, ''] else ['', ''] end /(#{start_tag_re})[ \t]*(#{middle_regex})[ \t]*(#{end_tag_re})/ end class String def autolink_block(doc_url, middle_regex, after_highlight) gsub(autolink_regex(middle_regex, after_highlight)) do original = Regexp.last_match(0) start_tag, raw_name, end_tag = Regexp.last_match.captures link_target, display_name = yield(CGI.unescape_html(raw_name)) if link_target && !link_target.type.extension? && link_target.url && link_target.url != doc_url.split('#').first && # Don't link to parent link_target.url != doc_url # Don't link to self "#{start_tag}" \ "#{CGI.escape_html(display_name)}#{end_tag}" else original end end end def unindent(count) gsub(/^#{' ' * count}/, '') end end module Jazzy # This module interacts with the sourcekitten command-line executable module SourceKitten def self.undocumented_abstract @undocumented_abstract ||= Markdown.render( Config.instance.undocumented_text, ).freeze end # # URL assignment # def self.sanitize_filename(doc) unsafe_filename = doc.docs_filename sanitzation_enabled = Config.instance.use_safe_filenames if sanitzation_enabled && !doc.type.name_controlled_manually? CGI.escape(unsafe_filename).gsub('_', '%5F').tr('%', '_') else unsafe_filename end end # rubocop:disable Metrics/MethodLength # Generate doc URL by prepending its parents' URLs # @return [Hash] input docs with URLs def self.make_doc_urls(docs) docs.each do |doc| if doc.render_as_page? doc.url = ( subdir_for_doc(doc) + [sanitize_filename(doc) + '.html'] ).map { |path| ERB::Util.url_encode(path) }.join('/') doc.children = make_doc_urls(doc.children) else # Don't create HTML page for this doc if it doesn't have children # Instead, make its link a hash-link on its parent's page if doc.typename == '<>' warn "A compile error prevented #{doc.fully_qualified_name} " \ 'from receiving a unique USR. Documentation may be ' \ 'incomplete. Please check for compile errors by running ' \ '`xcodebuild` or `swift build` with arguments ' \ "`#{Config.instance.build_tool_arguments.shelljoin}`." end id = doc.usr unless id id = doc.name || 'unknown' warn "`#{id}` has no USR. First make sure all modules used in " \ 'your project have been imported. If all used modules are ' \ 'imported, please report this problem by filing an issue at ' \ 'https://github.com/realm/jazzy/issues along with your ' \ 'Xcode project. If this token is declared in an `#if` block, ' \ 'please ignore this message.' end doc.url = "#{doc.parent_in_docs.url}#/#{id}" end end end # rubocop:enable Metrics/MethodLength # Determine the subdirectory in which a doc should be placed. # Guides in the root for back-compatibility. # Declarations under outer namespace type (Structures, Classes, etc.) def self.subdir_for_doc(doc) if Config.instance.multiple_modules? subdir_for_doc_multi_module(doc) else # Back-compatibility layout version subdir_for_doc_single_module(doc) end end # Pre-multi-module site layout, does not allow for # types with the same name. def self.subdir_for_doc_single_module(doc) # Guides + Groups in the root return [] if doc.type.markdown? || doc.type.overview? [doc.namespace_path.first.type.plural_url_name] + doc.namespace_ancestors.map(&:name) end # Multi-module site layout, separate each module that # is being documented. def self.subdir_for_doc_multi_module(doc) # Guides + Groups in the root return [] if doc.type.markdown? || doc.type.overview? root_decl = doc.namespace_path.first # Extensions need an extra dir to allow for extending # ExternalModule1.TypeName and ExternalModule2.TypeName namespace_subdir = if root_decl.type.swift_extension? ['Extensions', root_decl.module_name] else [doc.namespace_path.first.type.plural_url_name] end [root_decl.doc_module_name] + namespace_subdir + doc.namespace_ancestors.map(&:name) end # # CLI argument calculation # # returns all subdirectories of specified path def self.rec_path(path) path.children.collect do |child| if child.directory? rec_path(child) + [child] end end.select { |x| x }.flatten(1) end def self.use_spm?(module_config) module_config.swift_build_tool == :spm || (!module_config.swift_build_tool_configured && Dir['*.xcodeproj', '*.xcworkspace'].empty? && !module_config.build_tool_arguments.include?('-project') && !module_config.build_tool_arguments.include?('-workspace')) end # Builds SourceKitten arguments based on Jazzy options def self.arguments_from_options(module_config) arguments = ['doc'] if module_config.objc_mode arguments += objc_arguments_from_options(module_config) else arguments += ['--spm'] if use_spm?(module_config) unless module_config.module_name.empty? arguments += ['--module-name', module_config.module_name] end arguments += ['--'] end arguments + module_config.build_tool_arguments end def self.objc_arguments_from_options(module_config) arguments = [] if module_config.build_tool_arguments.empty? arguments += ['--objc', module_config.umbrella_header.to_s, '--', '-x', 'objective-c', '-isysroot', `xcrun --show-sdk-path --sdk #{module_config.sdk}`.chomp, '-I', module_config.framework_root.to_s, '-fmodules'] end # add additional -I arguments for each subdirectory of framework_root unless module_config.framework_root.nil? rec_path(Pathname.new(module_config.framework_root.to_s)).collect do |child| if child.directory? arguments += ['-I', child.to_s] end end end arguments end # Run sourcekitten with given arguments and return STDOUT def self.run_sourcekitten(arguments) if swift_version = Config.instance.swift_version unless xcode = XCInvoke::Xcode.find_swift_version(swift_version) raise "Unable to find an Xcode with swift version #{swift_version}." end env = xcode.as_env else env = ENV end bin_path = Pathname(__FILE__) + '../../../bin/sourcekitten' output, = Executable.execute_command(bin_path, arguments, true, env: env) output end # # SourceDeclaration generation # def self.make_default_doc_info(declaration) # @todo: Fix these declaration.abstract = '' declaration.parameters = [] declaration.children = [] end def self.attribute?(doc, attr_name) doc['key.attributes']&.find do |attribute| attribute['key.attribute'] == "source.decl.attribute.#{attr_name}" end end def self.availability_attribute?(doc) attribute?(doc, 'available') end def self.spi_attribute?(doc) attribute?(doc, '_spi') end def self.should_document?(doc) return false if doc['key.doc.comment'].to_s.include?(':nodoc:') type = SourceDeclaration::Type.new(doc['key.kind']) # Always document Objective-C declarations. return true unless type.swift_type? # Don't document Swift types if we are hiding Swift return false if Config.instance.hide_swift? # Don't document @available declarations with no USR, since it means # they're unavailable. if availability_attribute?(doc) && !doc['key.usr'] return false end # Only document @_spi declarations in some scenarios return false unless should_document_spi?(doc) # Don't document declarations excluded by the min_acl setting if type.swift_extension? should_document_swift_extension?(doc) else should_document_acl?(type, doc) end end # Check visibility: SPI def self.should_document_spi?(doc) spi_ok = @min_acl < SourceDeclaration::AccessControlLevel.public || Config.instance.include_spi_declarations || (!spi_attribute?(doc) && !doc['key.symgraph_spi']) @stats.add_spi_skipped unless spi_ok spi_ok end # Check visibility: access control def self.should_document_acl?(type, doc) # Include all enum elements for now, can't tell their ACL. return true if type.swift_enum_element? acl_ok = SourceDeclaration::AccessControlLevel.from_doc(doc) >= @min_acl unless acl_ok @stats.add_acl_skipped @inaccessible_protocols.append(doc['key.name']) if type.swift_protocol? end acl_ok end # Document extensions if they add protocol conformances, or have any # member that needs to be documented. def self.should_document_swift_extension?(doc) doc['key.inheritedtypes'] || Array(doc['key.substructure']).any? do |subdoc| subtype = SourceDeclaration::Type.new(subdoc['key.kind']) !subtype.mark? && should_document?(subdoc) end end def self.process_undocumented_token(doc, declaration) make_default_doc_info(declaration) if declaration.mark_undocumented? @stats.add_undocumented(declaration) return nil if @skip_undocumented declaration.abstract = undocumented_abstract else declaration.abstract = Markdown.render(doc['key.doc.comment'] || '', declaration.highlight_language) end declaration end def self.parameters(doc, discovered) (doc['key.doc.parameters'] || []).map do |p| name = p['name'] { name: name, discussion: discovered[name], } end.reject { |param| param[:discussion].nil? } end def self.make_doc_info(doc, declaration) return unless should_document?(doc) highlight_declaration(doc, declaration) make_deprecation_info(doc, declaration) unless doc['key.doc.full_as_xml'] return process_undocumented_token(doc, declaration) end declaration.abstract = Markdown.render(doc['key.doc.comment'] || '', declaration.highlight_language) declaration.discussion = '' declaration.return = Markdown.rendered_returns declaration.parameters = parameters(doc, Markdown.rendered_parameters) @stats.add_documented end def self.highlight_declaration(doc, declaration) if declaration.swift? declaration.declaration = Highlighter.highlight_swift(make_swift_declaration(doc, declaration)) else declaration.declaration = Highlighter.highlight_objc( make_objc_declaration(doc['key.parsed_declaration']), ) declaration.other_language_declaration = Highlighter.highlight_swift(doc['key.swift_declaration']) end end def self.make_deprecation_info(doc, declaration) if declaration.deprecated declaration.deprecation_message = Markdown.render(doc['key.deprecation_message'] || '') end if declaration.unavailable declaration.unavailable_message = Markdown.render(doc['key.unavailable_message'] || '') end end # Strip tags and convert entities def self.xml_to_text(xml) document = REXML::Document.new(xml) REXML::XPath.match(document.root, '//text()').map(&:value).join rescue StandardError '' end # Regexp to match an @attribute. Complex to handle @available(). def self.attribute_regexp(name) qstring = /"(?:[^"\\]*|\\.)*"/ %r{@#{name} # @attr name (?:\s*\( # optionally followed by spaces + parens, (?: # containing any number of either.. [^")]*| # normal characters or... #{qstring} # quoted strings. )* # (end parens content) \))? # (end optional parens) }x end # Get all attributes of some name def self.extract_attributes(declaration, name = '\w+') attrs = declaration.scan(attribute_regexp(name)) # Rouge #806 workaround, use unicode lookalike for ')' inside attributes. attrs.map { |str| str.gsub(/\)(?!\s*$)/, "\ufe5a") } end # Keep everything except instructions to us def self.extract_documented_attributes(declaration) extract_attributes(declaration).reject do |attr| attr.start_with?('@_documentation') end end def self.extract_availability(declaration) extract_attributes(declaration, 'available') end # Split leading attributes from a decl, returning both parts. def self.split_decl_attributes(declaration) declaration =~ /^((?:#{attribute_regexp('\w+')}\s*)*)(.*)$/m Regexp.last_match.captures end def self.prefer_parsed_decl?(parsed, annotated, type) return true if annotated.empty? return false unless parsed return false if type.swift_variable? # prefer { get }-style annotated.include?(' = default') || # SR-2608 (parsed.scan(/@autoclosure|@escaping/).count > annotated.scan(/@autoclosure|@escaping/).count) || # SR-6321 parsed.include?("\n") # user formatting end # Apply fixes to improve the compiler's declaration def self.fix_up_compiler_decl(annotated_decl, declaration) annotated_decl. # Replace the fully qualified name of a type with its base name gsub(declaration.fully_qualified_name_regexp, declaration.name). # Workaround for SR-9816 gsub(" {\n get\n }", ''). # Workaround for SR-12139 gsub(/mutating\s+mutating/, 'mutating') end # Find the best Swift declaration def self.make_swift_declaration(doc, declaration) # From compiler 'quick help' style annotated_decl_xml = doc['key.annotated_decl'] return nil unless annotated_decl_xml annotated_decl_attrs, annotated_decl_body = split_decl_attributes(xml_to_text(annotated_decl_xml)) # From source code parsed_decl = doc['key.parsed_declaration'] # Don't present type attributes on extensions return parsed_decl if declaration.type.extension? decl = if prefer_parsed_decl?(parsed_decl, annotated_decl_body, declaration.type) # Strip any attrs captured by parsed version inline_attrs, parsed_decl_body = split_decl_attributes(parsed_decl) parsed_decl_body.unindent(inline_attrs.length) else # Improve the compiler declaration fix_up_compiler_decl(annotated_decl_body, declaration) end # @available attrs only in compiler 'interface' style extract_availability(doc['key.doc.declaration'] || '') .concat(extract_documented_attributes(annotated_decl_attrs)) .concat(fabricate_spi_attributes(doc, annotated_decl_attrs)) .push(decl) .join("\n") end # Swift 6 workaround: @_spi attribute & SPI group missing def self.fabricate_spi_attributes(doc, attrs) return [] unless spi_attribute?(doc) return [] if attrs =~ /@_spi/ ['@_spi(<>)'] end # Exclude non-async routines that accept async closures def self.swift_async?(fully_annotated_decl) document = REXML::Document.new(fully_annotated_decl) !document.elements['/*/syntaxtype.keyword[text()="async"]'].nil? rescue StandardError nil end # Strip default property attributes because libclang # adds them all, even if absent in the original source code. DEFAULT_ATTRIBUTES = %w[atomic readwrite assign unsafe_unretained].freeze def self.make_objc_declaration(declaration) return declaration if Config.instance.keep_property_attributes declaration =~ /\A@property\s+\((.*?)\)/ return declaration unless Regexp.last_match attrs = Regexp.last_match[1].split(',').map(&:strip) - DEFAULT_ATTRIBUTES attrs_text = attrs.empty? ? '' : " (#{attrs.join(', ')})" declaration .sub(/(?<=@property)\s+\(.*?\)/, attrs_text) .gsub(/\s+/, ' ') end def self.make_substructure(doc, declaration) return [] unless subdocs = doc['key.substructure'] make_source_declarations(subdocs, declaration, declaration.mark_for_children) end # rubocop:disable Metrics/MethodLength # rubocop:disable Metrics/CyclomaticComplexity # rubocop:disable Metrics/PerceivedComplexity def self.make_source_declarations(docs, parent = nil, mark = SourceMark.new) declarations = [] current_mark = mark Array(docs).each do |doc| if doc.key?('key.diagnostic_stage') declarations += make_source_declarations( doc['key.substructure'], parent ) next end declaration = SourceDeclaration.new declaration.parent_in_code = parent declaration.type = SourceDeclaration::Type.new(doc['key.kind'], doc['key.fully_annotated_decl']) declaration.typename = doc['key.typename'] declaration.objc_name = doc['key.name'] documented_name = if Config.instance.hide_objc? && doc['key.swift_name'] doc['key.swift_name'] else declaration.objc_name end if declaration.type.task_mark?(documented_name) current_mark = SourceMark.new(documented_name) end if declaration.type.swift_enum_case? # Enum "cases" are thin wrappers around enum "elements". declarations += make_source_declarations( doc['key.substructure'], parent, current_mark ) next end next unless declaration.type.should_document? unless declaration.type.name raise 'Please file an issue at ' \ 'https://github.com/realm/jazzy/issues about adding support ' \ "for `#{declaration.type.kind}`." end unless documented_name warn 'Found a declaration without `key.name` that will be ' \ 'ignored. Documentation may be incomplete. This is probably ' \ 'caused by unresolved compiler errors: check the sourcekitten ' \ 'output for error messages.' next end declaration.file = Pathname(doc['key.filepath']) if doc['key.filepath'] declaration.usr = doc['key.usr'] declaration.type_usr = doc['key.typeusr'] declaration.module_name = if declaration.swift? # Filter out Apple sub-framework implementation names doc['key.modulename']&.sub(/\..*$/, '') else # ObjC best effort, category original module is unavailable @current_module_name end declaration.doc_module_name = @current_module_name declaration.name = documented_name declaration.mark = current_mark declaration.access_control_level = SourceDeclaration::AccessControlLevel.from_doc(doc) declaration.line = doc['key.doc.line'] || doc['key.line'] declaration.column = doc['key.doc.column'] || doc['key.column'] declaration.start_line = doc['key.parsed_scope.start'] declaration.end_line = doc['key.parsed_scope.end'] declaration.deprecated = doc['key.always_deprecated'] declaration.unavailable = doc['key.always_unavailable'] declaration.generic_requirements = find_generic_requirements(doc['key.parsed_declaration']) inherited_types = doc['key.inheritedtypes'] || [] declaration.inherited_types = inherited_types.map { |type| type['key.name'] }.compact declaration.async = doc['key.symgraph_async'] || if xml_declaration = doc['key.fully_annotated_decl'] swift_async?(xml_declaration) end next unless make_doc_info(doc, declaration) declaration.children = make_substructure(doc, declaration) next if declaration.type.extension? && declaration.children.empty? && !declaration.inherited_types? declarations << declaration end declarations end # rubocop:enable Metrics/PerceivedComplexity # rubocop:enable Metrics/CyclomaticComplexity # rubocop:enable Metrics/MethodLength def self.find_generic_requirements(parsed_declaration) parsed_declaration =~ /\bwhere\s+(.*)$/m return nil unless Regexp.last_match Regexp.last_match[1].gsub(/\s+/, ' ') end # # SourceDeclaration generation - extension management # # Expands extensions of nested types declared at the top level into # a tree so they can be deduplicated properly def self.expand_extensions(decls) decls.map do |decl| next decl unless decl.type.extension? && decl.name.include?('.') # Don't expand the Swift namespace if we're in ObjC mode. # ex: NS_SWIFT_NAME(Foo.Bar) should not create top-level Foo next decl if decl.swift_objc_extension? && !Config.instance.hide_objc? name_parts = decl.name.split('.') decl.name = name_parts.pop expand_extension(decl, name_parts, decls) end end def self.expand_extension(extension, name_parts, decls) return extension if name_parts.empty? name = name_parts.shift candidates = decls.select { |decl| decl.name == name } SourceDeclaration.new.tap do |decl| make_default_doc_info(decl) decl.name = name decl.module_name = extension.module_name decl.doc_module_name = extension.doc_module_name decl.type = extension.type decl.mark = extension.mark decl.usr = candidates.first.usr unless candidates.empty? child = expand_extension(extension, name_parts, candidates.flat_map(&:children).uniq) child.parent_in_code = decl decl.children = [child] end end # Merges multiple extensions of the same entity into a single document. # # Merges extensions into the protocol/class/struct/enum they extend, if it # occurs in the same project. # # Merges redundant declarations when documenting podspecs. def self.deduplicate_declarations(declarations) duplicate_groups = declarations .group_by { |d| deduplication_key(d, declarations) } .values duplicate_groups.flat_map do |group| # Put extended type (if present) before extensions merge_declarations(group) end.compact end # Returns true if an Objective-C declaration is mergeable. def self.mergeable_objc?(decl, root_decls) decl.type.objc_class? || (decl.type.objc_category? && (category_classname = decl.objc_category_name[0]) && root_decls.any? { |d| d.name == category_classname }) end # Returns if a Swift declaration is mergeable. # Start off merging in typealiases to help understand extensions. def self.mergeable_swift?(decl) decl.type.swift_extensible? || decl.type.swift_extension? || decl.type.swift_typealias? end # Normally merge all extensions into their types and each other. # # :none means only merge within a module -- so two extensions to # some type get merged, but an extension to a type from # another documented module does not get merged into that type # :extensions means extensions of documented modules get merged, # but if we're documenting ModA and ModB, and they both provide # extensions to Swift.String, then those two extensions still # appear separately. # # (The USR part of the dedup key means ModA.Foo and ModB.Foo do not # get merged.) def self.module_deduplication_key(decl) if (Config.instance.merge_modules == :none) || (Config.instance.merge_modules == :extensions && decl.extension_of_external_type?) decl.doc_module_name else '' end end # Two declarations get merged if they have the same deduplication key. def self.deduplication_key(decl, root_decls) mod_key = module_deduplication_key(decl) # Swift extension of objc class if decl.swift_objc_extension? [decl.swift_extension_objc_name, nil, :objc_class_and_categories, mod_key] # Swift type or Swift extension of Swift type elsif mergeable_swift?(decl) [decl.usr, nil, decl.name, mod_key] # Objc categories and classes elsif mergeable_objc?(decl, root_decls) # Using the ObjC name to match swift_objc_extension. name, _ = decl.objc_category_name || decl.objc_name [name, nil, :objc_class_and_categories, mod_key] # Non-mergable declarations (funcs, typedefs etc...) else # The typename part works around a Swift bug, jazzy#1396 [decl.usr, decl.typename, decl.name, decl.type.kind, ''] end end # rubocop:disable Metrics/MethodLength # rubocop:disable Metrics/PerceivedComplexity # Merges all of the given types and extensions into a single document. def self.merge_declarations(decls) extensions, typedecls = decls.partition { |d| d.type.extension? } if typedecls.size > 1 info = typedecls .map { |t| "#{t.type.name.downcase} #{t.name}" } .join(', ') warn 'Found conflicting type declarations with the same name, which ' \ "may indicate a build issue or a bug in Jazzy: #{info}" end typedecl = typedecls.first extensions = reject_inaccessible_extensions(typedecl, extensions) if typedecl if typedecl.type.swift_protocol? mark_and_merge_protocol_extensions(typedecl, extensions) extensions.reject! { |ext| ext.children.empty? } end merge_objc_declaration_marks(typedecl, extensions) end # Keep type-aliases separate from any extensions if typedecl&.type&.swift_typealias? [merge_type_and_extensions(typedecls, []), merge_type_and_extensions([], extensions)] else merge_type_and_extensions(typedecls, extensions) end end # rubocop:enable Metrics/PerceivedComplexity # rubocop:enable Metrics/MethodLength def self.merge_type_and_extensions(typedecls, extensions) # Constrained extensions at the end constrained, regular_exts = extensions.partition(&:constrained_extension?) decls = typedecls + regular_exts + constrained return nil if decls.empty? move_merged_extension_marks(decls) merge_code_declaration(decls) decls.first.tap do |merged| merged.children = deduplicate_declarations( decls.flat_map(&:children).uniq, ) merged.children.each do |child| child.parent_in_code = merged end end end # Now we know all the public types and all the private protocols, # reject extensions that add public protocols to private types # or add private protocols to public types. def self.reject_inaccessible_extensions(typedecl, extensions) swift_exts, objc_exts = extensions.partition(&:swift?) # Reject extensions that are just conformances to private protocols unwanted_exts, wanted_exts = swift_exts.partition do |ext| ext.children.empty? && !ext.other_inherited_types?(@inaccessible_protocols) end # Given extensions of a type from this module, without the # type itself, the type must be private and the extensions # should be rejected. if !typedecl && wanted_exts.first&.type_from_doc_module? unwanted_exts += wanted_exts wanted_exts = [] end # Don't tell the user to document them or their contents remover = lambda do |decls| decls.each { |d| @stats.remove_undocumented(d) } decls.map(&:children).each { |c| remover[c] } end remover[unwanted_exts] objc_exts + wanted_exts end # Protocol extensions. # # If any of the extensions provide default implementations for methods in # the given protocol, merge those members into the protocol doc instead of # keeping them on the extension. These get a “Default implementation” # annotation in the generated docs. Default implementations added by # conditional extensions are annotated but listed separately. # # Protocol methods provided only in an extension and not in the protocol # itself are a special beast: they do not use dynamic dispatch. These get an # “Extension method” annotation in the generated docs. def self.mark_and_merge_protocol_extensions(protocol, extensions) extensions.each do |ext| ext.children = ext.children.select do |ext_member| proto_member = protocol.children.find do |p| p.default_implementation?(ext_member) end # Extension-only method, keep. unless proto_member ext_member.from_protocol_extension = true next true end # Default impl but constrained, mark and keep. if ext.constrained_extension? ext_member.default_impl_abstract = ext_member.abstract ext_member.abstract = nil next true end # Default impl for all users, merge. proto_member.default_impl_abstract = ext_member.abstract next false end end end # Mark children merged from categories with the name of category # (unless they already have a mark) def self.merge_objc_declaration_marks(typedecl, extensions) return unless typedecl.type.objc_class? extensions.each do |ext| _, category_name = ext.objc_category_name ext.children.each { |c| c.mark.name ||= category_name } end end # For each extension to be merged, move any MARK from the extension # declaration down to the extension contents so it still shows up. def self.move_merged_extension_marks(decls) return unless to_be_merged = decls[1..] to_be_merged.each do |ext| child = ext.children.first if child && child.mark.empty? child.mark.copy(ext.mark) end end end # Merge useful information added by extensions into the main # declaration: public protocol conformances and, for top-level extensions, # further conditional extensions of the same type. def self.merge_code_declaration(decls) declarations = decls[1..].select do |decl| decl.type.swift_extension? && (decl.other_inherited_types?(@inaccessible_protocols) || (decls.first.type.swift_extension? && decl.constrained_extension?)) end.prepend(decls.first) html_declaration = '' until declarations.empty? module_decls, declarations = next_doc_module_group(declarations) first = module_decls.first if need_doc_module_note?(first, html_declaration) html_declaration += "From #{first.doc_module_name}:" end html_declaration += module_decls.map(&:declaration).uniq.join end # Must preserve `nil` for edge cases decls.first.declaration = html_declaration unless html_declaration.empty? end # Grab all the extensions from the same doc module def self.next_doc_module_group(decls) decls.partition do |decl| decl.doc_module_name == decls.first.doc_module_name end end # Does this extension/type need a note explaining which doc module it is from? # Only for extensions, if there actually are multiple modules. # Last condition avoids it for simple 'extension Array'. def self.need_doc_module_note?(decl, html_declaration) Config.instance.multiple_modules? && decl.type.swift_extension? && !(html_declaration.empty? && !decl.constrained_extension? && !decl.inherited_types?) end # # Autolinking # # Links recognized top-level declarations within # - inlined code within docs # - method signatures after they've been processed by the highlighter # # The `after_highlight` flag is used to differentiate between the two modes. # # DocC link format - follow Xcode and don't display slash-separated parts. def self.autolink_text(text, doc, after_highlight: false) text.autolink_block(doc.url, '[^\s]+', after_highlight) do |raw_name| sym_name = (raw_name[/^$/, 1] || raw_name).sub(/(? 0 swift_acls = comma_list(config.min_acl.included_levels) puts "included #{acl_included} " + (config.objc_mode ? '' : "#{swift_acls} ") + symbol_or_symbols(acl_included) end if !config.objc_mode && acl_skipped > 0 puts "skipped #{acl_skipped} " \ "#{comma_list(config.min_acl.excluded_levels)} " \ "#{symbol_or_symbols(acl_skipped)} " \ '(use `--min-acl` to specify a different minimum ACL)' end if spi_skipped > 0 puts "skipped #{spi_skipped} SPI #{symbol_or_symbols(spi_skipped)} " \ '(use `--include-spi-declarations` to include these)' end end def doc_coverage return 0 if acl_included == 0 (100 * documented) / acl_included end private def comma_list(items) case items.count when 0 then '' when 1 then items[0] when 2 then "#{items[0]} or #{items[1]}" else "#{items[0..-2].join(', ')}, or #{items[-1]}" end end def symbol_or_symbols(count) count == 1 ? 'symbol' : 'symbols' end end end ================================================ FILE: lib/jazzy/symbol_graph/constraint.rb ================================================ # frozen_string_literal: true module Jazzy module SymbolGraph # Constraint is a tidied-up JSON object, used by both Symbol and # Relationship, and key to reconstructing extensions. class Constraint attr_accessor :kind attr_accessor :lhs attr_accessor :rhs private def initialize(kind, lhs, rhs) self.kind = kind # "==" or ":" self.lhs = lhs self.rhs = rhs end KIND_MAP = { 'conformance' => ':', 'superclass' => ':', 'sameType' => '==', }.freeze private_constant :KIND_MAP public # Init from a JSON hash def self.new_hash(hash) kind = KIND_MAP[hash[:kind]] raise "Unknown constraint kind '#{kind}'" unless kind lhs = hash[:lhs].sub(/^Self\./, '') rhs = hash[:rhs].sub(/^Self\./, '') new(kind, lhs, rhs) end # Init from a Swift declaration fragment eg. 'A : B' def self.new_declaration(decl) decl =~ /^(.*?)\s*([:<=]+)\s*(.*)$/ new(Regexp.last_match[2], Regexp.last_match[1], Regexp.last_match[3]) end def to_swift "#{lhs} #{kind} #{rhs}" end # The first component of types in the constraint def type_names Set.new([lhs, rhs].map { |n| n.sub(/\..*$/, '') }) end def self.new_list(hash_list) hash_list.map { |h| Constraint.new_hash(h) }.sort.uniq end # Swift protocols and reqs have an implementation/hidden conformance # to their own protocol: we don't want to think about this in docs. def self.new_list_for_symbol(hash_list, path_components) hash_list.map do |hash| if hash[:lhs] == 'Self' && hash[:kind] == 'conformance' && path_components.include?(hash[:rhs]) next nil end Constraint.new_hash(hash) end.compact end # Workaround Swift 5.3 bug with missing constraint rels, eg. # extension P { # func f(a: C) where C: P {} # } def self.new_list_from_declaration(decl) return [] if decl.include?('(') decl.split(/\s*,\s*/).map { |cons| Constraint.new_declaration(cons) } end # Sort order - by Swift text include Comparable def <=>(other) to_swift <=> other.to_swift end alias eql? == def hash to_swift.hash end end end end class Array def to_where_clause empty? ? '' : " where #{map(&:to_swift).join(', ')}" end end ================================================ FILE: lib/jazzy/symbol_graph/ext_key.rb ================================================ # frozen_string_literal: true module Jazzy module SymbolGraph # An ExtKey identifies an extension of a type, made up of the USR of # the type and the constraints of the extension. With Swift 5.9 extension # symbols, the USR is the 'fake' USR invented by symbolgraph to solve the # same problem as this type, which means less merging takes place. class ExtKey attr_accessor :usr attr_accessor :constraints_text def initialize(usr, constraints) self.usr = usr self.constraints_text = constraints.map(&:to_swift).join end def hash_key usr + constraints_text end def eql?(other) hash_key == other.hash_key end def hash hash_key.hash end end class ExtSymNode def ext_key ExtKey.new(usr, all_constraints.ext) end end end end ================================================ FILE: lib/jazzy/symbol_graph/ext_node.rb ================================================ # frozen_string_literal: true require 'set' module Jazzy module SymbolGraph # For extensions we need to track constraints of the extended type # and the constraints introduced by the extension. class ExtConstraints attr_accessor :type # array attr_accessor :ext # array # all constraints inherited by members of the extension def merged (type + ext).sort end def initialize(type_constraints, ext_constraints) self.type = type_constraints || [] self.ext = ext_constraints || [] end end # An ExtNode is a node of the reconstructed syntax tree representing # an extension that we fabricate to resolve certain relationships. class ExtNode < BaseNode attr_accessor :usr attr_accessor :real_usr attr_accessor :name attr_accessor :all_constraints # ExtConstraints attr_accessor :conformances # set, can be empty # Deduce an extension from a member of an unknown type or # of known type with additional constraints def self.new_for_member(type_usr, member, constraints) new(type_usr, member.parent_qualified_name, constraints).tap { |o| o.add_child(member) } end # Deduce an extension from a protocol conformance for some type def self.new_for_conformance(type_usr, type_name, protocol, constraints) new(type_usr, type_name, constraints).tap do |o| o.add_conformance(protocol) end end private def initialize(usr, name, constraints) self.usr = usr self.name = name self.all_constraints = constraints self.conformances = Set.new super() end public def constraints all_constraints.merged end def add_conformance(protocol) conformances.add(protocol) end def full_declaration decl = "extension #{name}" unless conformances.empty? decl += " : #{conformances.sort.join(', ')}" end decl + all_constraints.ext.to_where_clause end def to_sourcekit(module_name, ext_module_name) declaration = full_declaration xml_declaration = "#{CGI.escapeHTML(declaration)}" hash = { 'key.kind' => 'source.lang.swift.decl.extension', 'key.usr' => real_usr || usr, 'key.name' => name, 'key.modulename' => ext_module_name, 'key.parsed_declaration' => declaration, 'key.annotated_decl' => xml_declaration, } unless conformances.empty? hash['key.inheritedtypes'] = conformances.sort.map do |conformance| { 'key.name' => conformance } end end add_children_to_sourcekit(hash, module_name) hash end # Sort order - by type name then constraint then conformances # Conformance check needed for stable order with Swift 5.9 # extension symbols that can't merge as well as previously. include Comparable def sort_key name + constraints.map(&:to_swift).join + conformances.sort.join end def <=>(other) sort_key <=> other.sort_key end end # An ExtSymNode is an extension generated from a Swift 5.9 extension # symbol, for extensions of types from other modules only. class ExtSymNode < ExtNode attr_accessor :symbol def initialize(symbol) self.symbol = symbol super(symbol.usr, symbol.full_name, # sadly can't tell what constraints are inherited vs added ExtConstraints.new([], symbol.constraints)) end def to_sourcekit(module_name, ext_module_name) hash = super symbol.add_to_sourcekit(hash) end end end end ================================================ FILE: lib/jazzy/symbol_graph/graph.rb ================================================ # frozen_string_literal: true # rubocop:disable Metrics/ClassLength module Jazzy module SymbolGraph # A Graph is the coordinator to import a symbolgraph json file. # Deserialize it to Symbols and Relationships, then rebuild # the AST shape using SymNodes and ExtNodes and extract SourceKit json. class Graph attr_accessor :module_name # Our module attr_accessor :ext_module_name # Module being extended attr_accessor :symbol_nodes # usr -> SymNode attr_accessor :relationships # [Relationship] attr_accessor :ext_nodes # (usr, constraints) -> ExtNode # Parse the JSON into flat tables of data def initialize(json, module_name, ext_module_name) self.module_name = module_name self.ext_module_name = ext_module_name graph = JSON.parse(json, symbolize_names: true) self.symbol_nodes = {} self.ext_nodes = {} graph[:symbols].each do |hash| symbol = Symbol.new(hash) if symbol.extension? node = ExtSymNode.new(symbol) ext_nodes[node.ext_key] = node else symbol_nodes[symbol.usr] = SymNode.new(symbol) end end self.relationships = graph[:relationships].map { |hash| Relationship.new(hash) } end # ExtNode index. ExtKey (type USR, extension constraints) -> ExtNode. # This minimizes the number of extensions def add_ext_member(type_usr, member_node, constraints) key = ExtKey.new(type_usr, constraints.ext) if ext_node = ext_nodes[key] ext_node.add_child(member_node) else ext_nodes[key] = ExtNode.new_for_member(type_usr, member_node, constraints) end end def add_ext_conformance(type_usr, type_name, protocol, constraints) key = ExtKey.new(type_usr, constraints.ext) if ext_node = ext_nodes[key] ext_node.add_conformance(protocol) else ext_nodes[key] = ExtNode.new_for_conformance(type_usr, type_name, protocol, constraints) end end # Increasingly desparate ways to find the name of the symbol # at the target end of a relationship def rel_target_name(rel, target_node) target_node&.symbol&.name || rel.target_fallback || Jazzy::SymbolGraph.demangle(rel.target_usr) end # Same for the source end. Less help from the tool here def rel_source_name(rel, source_node) source_node&.qualified_name || Jazzy::SymbolGraph.demangle(rel.source_usr) end # Protocol conformance is redundant if it's unconditional # and already expressed in the type's declaration. # # Skip implementation-detail conformances. def redundant_conformance?(rel, type, protocol) return false unless type (rel.constraints.empty? && type.conformance?(protocol)) || (type.actor? && rel.actor_protocol?) end # source is a member/protocol requirement of target def rebuild_member(rel, source, target) return unless source source.protocol_requirement = rel.protocol_requirement? constraints = ExtConstraints.new(target&.constraints, source.unique_context_constraints(target)) # Add to its parent or invent an extension unless target&.add_child?(source, constraints.ext) add_ext_member(rel.target_usr, source, constraints) end end # "source : target" either from type decl or ext decl def rebuild_conformance(rel, source, target) protocol_name = rel_target_name(rel, target) return if redundant_conformance?(rel, source, protocol_name) type_constraints = source&.constraints || [] constraints = ExtConstraints.new(type_constraints, rel.constraints - type_constraints) # Create an extension or enhance an existing one add_ext_conformance(rel.source_usr, rel_source_name(rel, source), protocol_name, constraints) end # "source is a default implementation of protocol requirement target" def rebuild_default_implementation(_rel, source, target) return unless source unless target && (target_parent = target.parent) && target_parent.is_a?(SymNode) # Could probably figure this out with demangle, but... warn "Can't resolve membership of default implementation " \ "#{source.symbol.usr}." source.unlisted = true return end constraints = ExtConstraints.new(target_parent.constraints, source.unique_context_constraints(target_parent)) add_ext_member(target_parent.symbol.usr, source, constraints) end # "source is a class that inherits from target" def rebuild_inherits(_rel, source, target) if source && target source.superclass_name = target.symbol.name end end # "References to fake_usr should be real_usr" def unalias_extensions(fake_usr, real_usr) ext_nodes.each_pair do |key, ext| if key.usr == fake_usr ext.real_usr = real_usr end end end # Process a structural relationship to link nodes def rebuild_rel(rel) source = symbol_nodes[rel.source_usr] target = symbol_nodes[rel.target_usr] case rel.kind when :memberOf, :optionalRequirementOf, :requirementOf rebuild_member(rel, source, target) when :conformsTo rebuild_conformance(rel, source, target) when :defaultImplementationOf rebuild_default_implementation(rel, source, target) when :inheritsFrom rebuild_inherits(rel, source, target) when :extensionTo unalias_extensions(rel.source_usr, rel.target_usr) end # don't seem to care about: # - overrides: not bothered, also unimplemented for protocols end # Rebuild the AST structure and convert to SourceKit def to_sourcekit relationships.sort.each { |r| rebuild_rel(r) } root_symbol_nodes = symbol_nodes.values .select(&:top_level_decl?) .sort .map { |n| n.to_sourcekit(module_name) } root_ext_nodes = ext_nodes.values .sort .map { |n| n.to_sourcekit(module_name, ext_module_name) } { 'key.diagnostic_stage' => 'parse', 'key.substructure' => root_symbol_nodes + root_ext_nodes, } end end end end # rubocop:enable Metrics/ClassLength ================================================ FILE: lib/jazzy/symbol_graph/relationship.rb ================================================ # frozen_string_literal: true module Jazzy module SymbolGraph # A Relationship is a tidied-up SymbolGraph JSON object class Relationship attr_accessor :kind attr_accessor :source_usr attr_accessor :target_usr attr_accessor :target_fallback # can be nil attr_accessor :constraints # array, can be empty # Order matters: defaultImplementationOf after the protocols # have been defined; extensionTo after all the extensions have # been discovered. KINDS = %w[memberOf conformsTo overrides inheritsFrom requirementOf optionalRequirementOf defaultImplementationOf extensionTo].freeze KINDS_INDEX = KINDS.to_h { |i| [i.to_sym, KINDS.index(i)] }.freeze def protocol_requirement? %i[requirementOf optionalRequirementOf].include? kind end def default_implementation? kind == :defaultImplementationOf end def extension_to? kind == :extensionTo end # Protocol conformances added by compiler to actor decls that # users aren't interested in. def actor_protocol? %w[Actor AnyActor Sendable].include?(target_fallback) end def initialize(hash) kind = hash[:kind] unless KINDS.include?(kind) raise "Unknown relationship kind '#{kind}'" end self.kind = kind.to_sym self.source_usr = hash[:source] self.target_usr = hash[:target] if fallback = hash[:targetFallback] # Strip the leading module name self.target_fallback = fallback.sub(/^.*?\./, '') end self.constraints = Constraint.new_list(hash[:swiftConstraints] || []) end # Sort order include Comparable def <=>(other) return 0 if kind == other.kind KINDS_INDEX[kind] <=> KINDS_INDEX[other.kind] end end end end ================================================ FILE: lib/jazzy/symbol_graph/sym_node.rb ================================================ # frozen_string_literal: true module Jazzy module SymbolGraph # The rebuilt syntax tree is made of nodes that either match # symbols or that we fabricate for extensions. This is the common # treeishness. class BaseNode attr_accessor :children # array, can be empty attr_accessor :parent # can be nil def initialize self.children = [] end def add_child(child) child.parent = self children.append(child) end def add_children_to_sourcekit(hash, module_name) unless children.empty? hash['key.substructure'] = children.sort.map { |c| c.to_sourcekit(module_name) } end end end # A SymNode is a node of the reconstructed syntax tree holding a symbol. # It can turn itself into SourceKit and helps decode extensions. class SymNode < BaseNode attr_accessor :symbol attr_writer :override attr_writer :protocol_requirement attr_writer :unlisted attr_accessor :superclass_name def override? @override end def protocol_requirement? @protocol_requirement end def top_level_decl? !@unlisted && parent.nil? end def initialize(symbol) self.symbol = symbol super() end def qualified_name symbol.path_components.join('.') end def parent_qualified_name symbol.path_components[0...-1].join('.') end def protocol? symbol.kind.end_with?('protocol') end def actor? symbol.kind.end_with?('actor') end def constraints symbol.constraints end # Add another SymNode as a member if possible. # It must go in an extension if either: # - it has different generic constraints to us; or # - we're a protocol and it's a default impl / ext method def add_child?(node, unique_context_constraints) unless unique_context_constraints.empty? && (!protocol? || node.protocol_requirement?) return false end add_child(node) true end # The `Constraint`s on this decl that are both: # 1. Unique, ie. not just inherited from its context; and # 2. Constraining the *context's* gen params rather than our own. def unique_context_constraints(context) return symbol.constraints unless context new_generic_type_params = symbol.generic_type_params - context.symbol.generic_type_params (symbol.constraints - context.symbol.constraints) .select { |con| con.type_names.disjoint?(new_generic_type_params) } end # Messy check whether we need to fabricate an extension for a protocol # conformance: don't bother if it's already in the type declaration. def conformance?(protocol) return false unless symbol.declaration =~ /(?<=:).*?(?=(where|$))/ Regexp.last_match[0] =~ /\b#{protocol}\b/ end # Generate the 'where' clause for the declaration def where_clause parent_constraints = parent&.constraints || [] (constraints - parent_constraints).to_where_clause end def inherits_clause return '' unless superclass_name " : #{superclass_name}" end # approximately... def async? symbol.declaration =~ /\basync\b[^)]*$/ end def full_declaration symbol.attributes .append(symbol.declaration + inherits_clause + where_clause) .join("\n") end def to_sourcekit(module_name) declaration = full_declaration xml_declaration = "#{CGI.escapeHTML(declaration)}" hash = { 'key.kind' => symbol.kind, 'key.usr' => symbol.usr, 'key.name' => symbol.name, 'key.modulename' => module_name, 'key.parsed_declaration' => declaration, 'key.annotated_decl' => xml_declaration, 'key.symgraph_async' => async?, } if params = symbol.parameter_names hash['key.doc.parameters'] = params.map { |name| { 'name' => name } } end hash['key.symgraph_spi'] = true if symbol.spi add_children_to_sourcekit(hash, module_name) symbol.add_to_sourcekit(hash) end # Sort order - by symbol include Comparable def <=>(other) symbol <=> other.symbol end end end end ================================================ FILE: lib/jazzy/symbol_graph/symbol.rb ================================================ # frozen_string_literal: true # rubocop:disable Metrics/ClassLength module Jazzy module SymbolGraph # A Symbol is a tidied-up SymbolGraph JSON object class Symbol attr_accessor :usr attr_accessor :path_components attr_accessor :declaration attr_accessor :kind attr_accessor :acl attr_accessor :spi attr_accessor :location # can be nil, keys :filename :line :character attr_accessor :constraints # array, can be empty attr_accessor :doc_comments # can be nil attr_accessor :attributes # array, can be empty attr_accessor :generic_type_params # set, can be empty attr_accessor :parameter_names # array, can be nil def name path_components[-1] || '??' end def full_name path_components.join('.') end def initialize(hash) self.usr = hash[:identifier][:precise] self.path_components = hash[:pathComponents] raw_decl, keywords = parse_decl_fragments(hash[:declarationFragments]) init_kind(hash[:kind][:identifier], keywords) init_declaration(raw_decl) if func_signature = hash[:functionSignature] init_func_signature(func_signature) end init_acl(hash[:accessLevel]) self.spi = hash[:spi] if location = hash[:location] init_location(location) end init_constraints(hash, raw_decl) if comments_hash = hash[:docComment] init_doc_comments(comments_hash) end init_attributes(hash[:availability] || []) init_generic_type_params(hash) end def parse_decl_fragments(fragments) decl = '' keywords = Set.new fragments.each do |frag| decl += frag[:spelling] keywords.add(frag[:spelling]) if frag[:kind] == 'keyword' end [decl, keywords] end # Repair problems with SymbolGraph's declprinter def init_declaration(raw_decl) # Too much 'Self.TypeName'; omitted arg labels look odd; # duplicated constraints; swift 5.3 vs. master workaround self.declaration = raw_decl .gsub(/\bSelf\./, '') .gsub(/(?<=\(|, )_: /, '_ arg: ') .gsub(/ where.*$/, '') if kind == 'source.lang.swift.decl.class' declaration.sub!(/\s*:.*$/, '') end end # Remember pieces of methods for later markdown parsing def init_func_signature(func_signature) self.parameter_names = (func_signature[:parameters] || []).map { |h| h[:name] } end # Mapping SymbolGraph's declkinds to SourceKit KIND_MAP = { 'class' => 'class', 'struct' => 'struct', 'enum' => 'enum', 'enum.case' => 'enumelement', # intentional 'protocol' => 'protocol', 'init' => 'function.constructor', 'deinit' => 'function.destructor', 'func.op' => 'function.operator', 'type.method' => 'function.method.class', 'static.method' => 'function.method.static', 'method' => 'function.method.instance', 'func' => 'function.free', 'type.property' => 'var.class', 'static.property' => 'var.static', 'property' => 'var.instance', 'var' => 'var.global', 'subscript' => 'function.subscript', 'type.subscript' => 'function.subscript', 'static.subscript' => 'function.subscript', 'typealias' => 'typealias', 'associatedtype' => 'associatedtype', 'actor' => 'actor', 'macro' => 'macro', 'extension' => 'extension', }.freeze # We treat 'static var' differently to 'class var' # We treat actors as first-class entities def adjust_kind_for_declaration(kind, keywords) if kind == 'swift.class' && keywords.member?('actor') return 'swift.actor' end return kind unless keywords.member?('static') kind.gsub('type', 'static') end def init_kind(kind, keywords) adjusted = adjust_kind_for_declaration(kind, keywords) sourcekit_kind = KIND_MAP[adjusted.sub('swift.', '')] raise "Unknown symbol kind '#{kind}'" unless sourcekit_kind self.kind = "source.lang.swift.decl.#{sourcekit_kind}" end def extension? kind.end_with?('extension') end # Mapping SymbolGraph's ACL to SourceKit def init_acl(acl) self.acl = "source.lang.swift.accessibility.#{acl}" end # Symbol location - only available for public+ decls def init_location(loc_hash) self.location = {} location[:filename] = loc_hash[:uri].sub(%r{^file://}, '') location[:line] = loc_hash[:position][:line] location[:character] = loc_hash[:position][:character] end # Generic constraints: in one or both of two places. # There can be duplicates; these are removed by `Constraint`. def init_constraints(hash, raw_decl) raw_constraints = %i[swiftGenerics swiftExtension].flat_map do |key| next [] unless container = hash[key] container[:constraints] || [] end constraints = Constraint.new_list_for_symbol(raw_constraints, path_components) if raw_decl =~ / where (.*)$/ constraints += Constraint.new_list_from_declaration(Regexp.last_match[1]) end self.constraints = constraints.sort.uniq end # Generic type params def init_generic_type_params(hash) self.generic_type_params = Set.new( if (generics = hash[:swiftGenerics]) && (parameters = generics[:parameters]) parameters.map { |p| p[:name] } else [] end, ) end def init_doc_comments(comments_hash) self.doc_comments = comments_hash[:lines] .map { |l| l[:text] } .join("\n") end # Availability # Re-encode this as Swift. Should really teach Jazzy about these, # could maybe then do something smarter here. def availability_attributes(avail_hash_list) avail_hash_list.map do |avail| str = '@available(' if avail[:isUnconditionallyDeprecated] str += '*, deprecated' elsif domain = avail[:domain] str += domain %i[introduced deprecated obsoleted].each do |event| if version = avail[event] str += ", #{event}: #{decode_version(version)}" end end else warn "Found confusing availability: #{avail}" next nil end str += ", message: \"#{avail[:message]}\"" if avail[:message] str += ", renamed: \"#{avail[:renamed]}\"" if avail[:renamed] str + ')' end.compact end def decode_version(hash) str = hash[:major].to_s str += ".#{hash[:minor]}" if hash[:minor] str += ".#{hash[:patch]}" if hash[:patch] str end def spi_attributes spi ? ['@_spi(Unknown)'] : [] end def init_attributes(avail_hash_list) self.attributes = availability_attributes(avail_hash_list) + spi_attributes end # SourceKit common fields, shared by extension and regular symbols. # Things we do not know for fabricated extensions. def add_to_sourcekit(hash) unless doc_comments.nil? hash['key.doc.comment'] = doc_comments hash['key.doc.full_as_xml'] = '' end hash['key.accessibility'] = acl unless location.nil? hash['key.filepath'] = location[:filename] hash['key.doc.line'] = location[:line] + 1 hash['key.doc.column'] = location[:character] + 1 end hash end # Sort order include Comparable def <=>(other) # Things with location: order by file/line/column # (pls tell what wheel i am reinventing :/) if location && other_loc = other.location if location[:filename] == other_loc[:filename] if location[:line] == other_loc[:line] return location[:character] <=> other_loc[:character] end return location[:line] <=> other_loc[:line] end return location[:filename] <=> other_loc[:filename] end # Things with a location before things without a location return +1 if location.nil? && other.location return -1 if location && other.location.nil? # Things without a location: by name and then USR return usr <=> other.usr if name == other.name name <=> other.name end end end end # rubocop:enable Metrics/ClassLength ================================================ FILE: lib/jazzy/symbol_graph.rb ================================================ # frozen_string_literal: true require 'set' require 'jazzy/symbol_graph/graph' require 'jazzy/symbol_graph/constraint' require 'jazzy/symbol_graph/symbol' require 'jazzy/symbol_graph/relationship' require 'jazzy/symbol_graph/sym_node' require 'jazzy/symbol_graph/ext_node' require 'jazzy/symbol_graph/ext_key' # This is the top-level symbolgraph driver that deals with # figuring out arguments, running the tool, and loading the # results. module Jazzy module SymbolGraph # Find swift symbol graph files, either having been passed # in directly, or generated by running`swift symbolgraph-extract` # with configured args. # Then parse the results, and return as JSON in SourceKit[ten] # format. def self.build(module_config) if module_config.symbolgraph_directory.nil? Dir.mktmpdir do |tmp_dir| args = arguments(module_config, tmp_dir) Executable.execute_command('swift', args.unshift('symbolgraph-extract'), true) # raise on error parse_symbols(tmp_dir) end else parse_symbols(module_config.symbolgraph_directory.to_s) end end # Figure out the args to pass to symbolgraph-extract def self.arguments(module_config, output_path) if module_config.module_name.empty? raise 'error: `--swift-build-tool symbolgraph` requires `--module`.' end user_args = module_config.build_tool_arguments.join if user_args =~ /-(?:module-name|minimum-access-level|output-dir)/ raise 'error: `--build-tool-arguments` for ' \ "`--swift-build-tool symbolgraph` can't use `-module`, " \ '`-minimum-access-level`, or `-output-dir`.' end # Default set args = [ '-module-name', module_config.module_name, '-minimum-access-level', 'private', '-output-dir', output_path, '-skip-synthesized-members' ] # Things user can override args += ['-sdk', sdk(module_config)] unless user_args =~ /-sdk/ args += ['-target', target] unless user_args =~ /-target/ args += ['-F', module_config.source_directory.to_s] unless user_args =~ /-F(?!s)/ args += ['-I', module_config.source_directory.to_s] unless user_args =~ /-I/ args + module_config.build_tool_arguments end # Parse the symbol files in the given directory def self.parse_symbols(directory) Dir[directory + '/*.symbols.json'].sort.map do |filename| # The @ part is for extensions in our module (before the @) # of types in another module (after the @). File.basename(filename) =~ /(.*?)(@(.*?))?\.symbols/ module_name = Regexp.last_match[1] ext_module_name = Regexp.last_match[3] || module_name json = File.read(filename) { filename => Graph.new(json, module_name, ext_module_name).to_sourcekit, } end.to_json end # Get the SDK path. On !darwin this just isn't needed. def self.sdk(module_config) `xcrun --show-sdk-path --sdk #{module_config.sdk}`.chomp end # Guess a default LLVM target. Feels like the tool should figure this # out from sdk + the binary somehow? def self.target `swift -version` =~ /Target: (.*?)$/ Regexp.last_match[1] || 'x86_64-apple-macosx10.15' end # This is a last-ditch fallback for when symbolgraph doesn't # provide a name - at least conforming external types to local # protocols. def self.demangle(usr) args = %w[demangle -simplified -compact].append(usr.sub(/^s:/, 's')) output, = Executable.execute_command('swift', args, true) output.chomp rescue StandardError usr end end end ================================================ FILE: lib/jazzy/themes/apple/assets/css/highlight.css.scss ================================================ /*! Jazzy - https://github.com/realm/jazzy * Copyright Realm Inc. * SPDX-License-Identifier: MIT */ /* Credit to https://gist.github.com/wataru420/2048287 */ .highlight { .c { color: #999988; font-style: italic } // Comment .err { color: #a61717; background-color: #e3d2d2 } // Error .k { color: #000000; font-weight: bold } // Keyword .o { color: #000000; font-weight: bold } // Operator .cm { color: #999988; font-style: italic } // Comment.Multiline .cp { color: #999999; font-weight: bold } // Comment.Preproc .c1 { color: #999988; font-style: italic } // Comment.Single .cs { color: #999999; font-weight: bold; font-style: italic } // Comment.Special .gd { color: #000000; background-color: #ffdddd } // Generic.Deleted .gd .x { color: #000000; background-color: #ffaaaa } // Generic.Deleted.Specific .ge { color: #000000; font-style: italic } // Generic.Emph .gr { color: #aa0000 } // Generic.Error .gh { color: #999999 } // Generic.Heading .gi { color: #000000; background-color: #ddffdd } // Generic.Inserted .gi .x { color: #000000; background-color: #aaffaa } // Generic.Inserted.Specific .go { color: #888888 } // Generic.Output .gp { color: #555555 } // Generic.Prompt .gs { font-weight: bold } // Generic.Strong .gu { color: #aaaaaa } // Generic.Subheading .gt { color: #aa0000 } // Generic.Traceback .kc { color: #000000; font-weight: bold } // Keyword.Constant .kd { color: #000000; font-weight: bold } // Keyword.Declaration .kp { color: #000000; font-weight: bold } // Keyword.Pseudo .kr { color: #000000; font-weight: bold } // Keyword.Reserved .kt { color: #445588; } // Keyword.Type .m { color: #009999 } // Literal.Number .s { color: #d14 } // Literal.String .na { color: #008080 } // Name.Attribute .nb { color: #0086B3 } // Name.Builtin .nc { color: #445588; font-weight: bold } // Name.Class .no { color: #008080 } // Name.Constant .ni { color: #800080 } // Name.Entity .ne { color: #990000; font-weight: bold } // Name.Exception .nf { color: #990000; } // Name.Function .nn { color: #555555 } // Name.Namespace .nt { color: #000080 } // Name.Tag .nv { color: #008080 } // Name.Variable .ow { color: #000000; font-weight: bold } // Operator.Word .w { color: #bbbbbb } // Text.Whitespace .mf { color: #009999 } // Literal.Number.Float .mh { color: #009999 } // Literal.Number.Hex .mi { color: #009999 } // Literal.Number.Integer .mo { color: #009999 } // Literal.Number.Oct .sb { color: #d14 } // Literal.String.Backtick .sc { color: #d14 } // Literal.String.Char .sd { color: #d14 } // Literal.String.Doc .s2 { color: #d14 } // Literal.String.Double .se { color: #d14 } // Literal.String.Escape .sh { color: #d14 } // Literal.String.Heredoc .si { color: #d14 } // Literal.String.Interpol .sx { color: #d14 } // Literal.String.Other .sr { color: #009926 } // Literal.String.Regex .s1 { color: #d14 } // Literal.String.Single .ss { color: #990073 } // Literal.String.Symbol .bp { color: #999999 } // Name.Builtin.Pseudo .vc { color: #008080 } // Name.Variable.Class .vg { color: #008080 } // Name.Variable.Global .vi { color: #008080 } // Name.Variable.Instance .il { color: #009999 } // Literal.Number.Integer.Long } ================================================ FILE: lib/jazzy/themes/apple/assets/css/jazzy.css.scss ================================================ /*! Jazzy - https://github.com/realm/jazzy * Copyright Realm Inc. * SPDX-License-Identifier: MIT */ //////////////////////////////// // Constants //////////////////////////////// $bg_color: #414141; $doc_coverage_color: #999; $code_color: #777; $code_bg_color: #eee; $link_color: #0088cc; $white_color: #fff; $light_gray_bg_color: #f2f2f2; $declaration_bg_color: #f9f9f9; $sidebar_bg_color: #f9f9f9; $declaration_title_language_color: #4b8afb; $sidebar_width: 230px; $content_wrapper_width: 980px; $content_top_offset: 70px; $content_body_margin: 16px; $content_body_left_offset: $sidebar_width + $content_body_margin; $header_height: 32px; $breadcrumb_padding_top: 12px; $code_font: 0.95em Menlo, monospace; $gray_border_color: #e2e2e2; $gray_border: 1px solid $gray_border_color; $declaration_language_border: 5px solid #cde9f4; $aside_color: #aaa; $aside_border: 5px solid lighten($aside_color, 20%); $aside_warning_color: #ff0000; $aside_warning_border: 5px solid lighten($aside_warning_color, 20%); //////////////////////////////// // Reset //////////////////////////////// html, body, div, span, h1, h3, h4, p, a, code, em, img, ul, li, table, tbody, tr, td { background: transparent; border: 0; margin: 0; outline: 0; padding: 0; vertical-align: baseline; } //////////////////////////////// // Global //////////////////////////////// body { background-color: $light_gray_bg_color; font-family: Helvetica, freesans, Arial, sans-serif; font-size: 14px; -webkit-font-smoothing: subpixel-antialiased; word-wrap: break-word; } // Headers h1, h2, h3 { margin-top: 0.8em; margin-bottom: 0.3em; font-weight: 100; color: black; } h1 { font-size: 2.5em; } h2 { font-size: 2em; border-bottom: $gray_border; } h4 { font-size: 13px; line-height: 1.5; margin-top: 21px; } h5 { font-size: 1.1em; } h6 { font-size: 1.1em; color: $code_color; } .section-name { color: rgba(128,128,128,1); display: block; font-family: Helvetica; font-size: 22px; font-weight: 100; margin-bottom: 15px; } // Code pre, code { font: $code_font; color: $code_color; word-wrap: normal; } p code, li code { background-color: $code_bg_color; padding: 2px 4px; border-radius: 4px; } pre > code { padding: 0; } // Links a { color: $link_color; text-decoration: none; code { color: inherit; } } // Lists ul { padding-left: 15px; } li { line-height: 1.8em; } // Images img { max-width: 100%; } // Blockquotes blockquote { margin-left: 0; padding: 0 10px; border-left: 4px solid #ccc; } // HRs hr { height: 1px; border: none; background-color: $gray_border_color; } // Footnotes .footnote-ref { display: inline-block; scroll-margin-top: $content-top-offset; } .footnote-def { scroll-margin-top: $content-top-offset; } // General Content Wrapper .content-wrapper { margin: 0 auto; width: $content_wrapper_width; } //////////////////////////////// // Header & Top Breadcrumbs //////////////////////////////// header { font-size: 0.85em; line-height: $header_height; background-color: $bg_color; position: fixed; width: 100%; z-index: 3; img { padding-right: 6px; vertical-align: -3px; height: 16px; } a { color: $white_color; } p { float: left; color: $doc_coverage_color; } .header-right { float: right; margin-left: 16px; } } #breadcrumbs { background-color: $light_gray_bg_color; height: $content_top_offset - $header_height - $breadcrumb_padding_top; padding-top: $breadcrumb_padding_top; position: fixed; width: inherit; z-index: 2; margin-top: $header_height; white-space: nowrap; overflow-x: scroll; #carat { height: 10px; margin: 0 5px; } } //////////////////////////////// // Side Navigation //////////////////////////////// .sidebar { background-color: $sidebar_bg_color; border: $gray_border; overflow-y: auto; overflow-x: hidden; position: fixed; top: $content_top_offset; bottom: 0; width: $sidebar_width; word-wrap: normal; } .nav-groups { list-style-type: none; background: $white_color; padding-left: 0; } .nav-group-name { border-bottom: $gray_border; font-size: 1.1em; font-weight: 100; padding: 15px 0 15px 20px; > a { color: #333; } } .nav-group-tasks { margin-top: 5px; } .nav-group-task { font-size: 0.9em; list-style-type: none; white-space: nowrap; a { color: #888; } } //////////////////////////////// // Main Content //////////////////////////////// .main-content { background-color: $white_color; border: $gray_border; margin-left: $content_body_left_offset; position: absolute; overflow: hidden; padding-bottom: 20px; top: $content_top_offset; width: $content_wrapper_width - $content_body_left_offset; p, a, code, em, ul, table, blockquote { margin-bottom: 1em; } p { line-height: 1.8em; } section { .section:first-child { margin-top: 0; padding-top: 0; } .task-group-section .task-group:first-of-type { padding-top: 10px; .section-name { padding-top: 15px; } } .heading:before { content: ""; display: block; padding-top: $content_top_offset; margin: -$content_top_offset 0 0; } } .section-name { p { margin-bottom: inherit; line-height: inherit; } code { background-color: inherit; padding: inherit; color: inherit; } } } .section { padding: 0 25px; } .highlight { background-color: $code_bg_color; padding: 10px 12px; border: $gray_border; border-radius: 4px; overflow-x: auto; } .declaration .highlight { overflow-x: initial; // This allows the scrollbar to show up inside declarations padding: 0 40px 40px 0; margin-bottom: -25px; background-color: transparent; border: none; } .section-name { margin: 0; margin-left: 18px; } .task-group-section { margin-top: 10px; padding-left: 6px; border-top: $gray_border; } .task-group { padding-top: 0px; } .task-name-container { a[name] { &:before { content: ""; display: block; padding-top: $content_top_offset; margin: -$content_top_offset 0 0; } } } .section-name-container { position: relative; display: inline-block; .section-name-link { position: absolute; top: 0; left: 0; bottom: 0; right: 0; margin-bottom: 0; } .section-name { position: relative; pointer-events: none; z-index: 1; a { pointer-events: auto; } } } .item { padding-top: 8px; width: 100%; list-style-type: none; a[name] { &:before { content: ""; display: block; padding-top: $content_top_offset; margin: -$content_top_offset 0 0; } } code { background-color: transparent; padding: 0; } .token, .direct-link { display: inline-block; text-indent: -20px; padding-left: 3px; margin-left: 35px; font-size: 11.9px; transition: all 300ms; } .token-open { margin-left: 20px; } .discouraged { text-decoration: line-through; } } .declaration-note { font-size: .85em; color: rgba(128,128,128,1); font-style: italic; } .pointer-container { border-bottom: $gray_border; left: -23px; padding-bottom: 13px; position: relative; width: 110%; } .pointer { background: $declaration_bg_color; border-left: $gray_border; border-top: $gray_border; height: 12px; left: 21px; top: -7px; -webkit-transform: rotate(45deg); -moz-transform: rotate(45deg); -o-transform: rotate(45deg); transform: rotate(45deg); position: absolute; width: 12px; } .height-container { display: none; left: -25px; padding: 0 25px; position: relative; width: 100%; overflow: hidden; .section { background: $declaration_bg_color; border-bottom: $gray_border; left: -25px; position: relative; width: 100%; padding-top: 10px; padding-bottom: 5px; } } .aside, .language { padding: 6px 12px; margin: 12px 0; border-left: $aside_border; overflow-y: hidden; .aside-title { font-size: 9px; letter-spacing: 2px; text-transform: uppercase; padding-bottom: 0; margin: 0; color: $aside_color; -webkit-user-select: none; } p:last-child { margin-bottom: 0; } } .language { border-left: $declaration_language_border; .aside-title { color: $declaration_title_language_color; } } .aside-warning, .aside-deprecated, .aside-unavailable { border-left: $aside_warning_border; .aside-title { color: $aside_warning_color; } } .graybox { border-collapse: collapse; width: 100%; p { margin: 0; word-break: break-word; min-width: 50px; } td { border: $gray_border; padding: 5px 25px 5px 10px; vertical-align: middle; } tr td:first-of-type { text-align: right; padding: 7px; vertical-align: top; word-break: normal; width: 40px; } } .slightly-smaller { font-size: 0.9em; } #footer { position: relative; top: 10px; bottom: 0px; margin-left: 25px; p { margin: 0; color: #aaa; font-size: 0.8em; } } //////////////////////////////// // Dash //////////////////////////////// html.dash { header, #breadcrumbs, .sidebar { display: none; } .main-content { width: $content_wrapper_width; margin-left: 0; border: none; width: 100%; top: 0; padding-bottom: 0; } .height-container { display: block; } .item .token { margin-left: 0; } .content-wrapper { width: auto; } #footer { position: static; } } // =========================================================================== // // Search // // =========================================================================== form[role=search] { float: right; input { font: Helvetica, freesans, Arial, sans-serif; margin-top: 6px; font-size: 13px; line-height: 20px; padding: 0px 10px; border: none; border-radius: 1em; .loading & { background: white url(../img/spinner.gif) center right 4px no-repeat; } } // Typeahead elements .tt-menu { margin: 0; min-width: 300px; background: $white_color; color: #333; border: $gray_border; z-index: 4; } .tt-highlight { font-weight: bold; } .tt-suggestion { font: Helvetica, freesans, Arial, sans-serif; font-size: 14px; padding: 0 8px; span { display: table-cell; white-space: nowrap; } .doc-parent-name { width: 100%; text-align: right; font-weight: normal; font-size: 0.9em; padding-left: 16px; } } .tt-suggestion:hover, .tt-suggestion.tt-cursor { cursor: pointer; background-color: #4183c4; color: #fff; } .tt-suggestion:hover .doc-parent-name, .tt-suggestion.tt-cursor .doc-parent-name { color: #fff; } } ================================================ FILE: lib/jazzy/themes/apple/assets/js/jazzy.js ================================================ // Jazzy - https://github.com/realm/jazzy // Copyright Realm Inc. // SPDX-License-Identifier: MIT window.jazzy = {'docset': false} if (typeof window.dash != 'undefined') { document.documentElement.className += ' dash' window.jazzy.docset = true } if (navigator.userAgent.match(/xcode/i)) { document.documentElement.className += ' xcode' window.jazzy.docset = true } function toggleItem($link, $content) { var animationDuration = 300; $link.toggleClass('token-open'); $content.slideToggle(animationDuration); } function itemLinkToContent($link) { return $link.parent().parent().next(); } // On doc load + hash-change, open any targeted item function openCurrentItemIfClosed() { if (window.jazzy.docset) { return; } var $link = $(`a[name="${location.hash.substring(1)}"]`).nextAll('.token'); $content = itemLinkToContent($link); if ($content.is(':hidden')) { toggleItem($link, $content); } } $(openCurrentItemIfClosed); $(window).on('hashchange', openCurrentItemIfClosed); // On item link ('token') click, toggle its discussion $('.token').on('click', function(event) { if (window.jazzy.docset) { return; } var $link = $(this); toggleItem($link, itemLinkToContent($link)); // Keeps the document from jumping to the hash. var href = $link.attr('href'); if (history.pushState) { history.pushState({}, '', href); } else { location.hash = href; } event.preventDefault(); }); // Clicks on links to the current, closed, item need to open the item $("a:not('.token')").on('click', function() { if (location == this.href) { openCurrentItemIfClosed(); } }); // KaTeX rendering if ("katex" in window) { $($('.math').each( (_, element) => { katex.render(element.textContent, element, { displayMode: $(element).hasClass('m-block'), throwOnError: false, trust: true }); })) } ================================================ FILE: lib/jazzy/themes/apple/assets/js/jazzy.search.js ================================================ // Jazzy - https://github.com/realm/jazzy // Copyright Realm Inc. // SPDX-License-Identifier: MIT $(function(){ var $typeahead = $('[data-typeahead]'); var $form = $typeahead.parents('form'); var searchURL = $form.attr('action'); function displayTemplate(result) { return result.name; } function suggestionTemplate(result) { var t = '
      '; t += '' + result.name + ''; if (result.parent_name) { t += '' + result.parent_name + ''; } t += '
      '; return t; } $typeahead.one('focus', function() { $form.addClass('loading'); $.getJSON(searchURL).then(function(searchData) { const searchIndex = lunr(function() { this.ref('url'); this.field('name'); this.field('abstract'); for (const [url, doc] of Object.entries(searchData)) { this.add({url: url, name: doc.name, abstract: doc.abstract}); } }); $typeahead.typeahead( { highlight: true, minLength: 3, autoselect: true }, { limit: 10, display: displayTemplate, templates: { suggestion: suggestionTemplate }, source: function(query, sync) { const lcSearch = query.toLowerCase(); const results = searchIndex.query(function(q) { q.term(lcSearch, { boost: 100 }); q.term(lcSearch, { boost: 10, wildcard: lunr.Query.wildcard.TRAILING }); }).map(function(result) { var doc = searchData[result.ref]; doc.url = result.ref; return doc; }); sync(results); } } ); $form.removeClass('loading'); $typeahead.trigger('focus'); }); }); var baseURL = searchURL.slice(0, -"search.json".length); $typeahead.on('typeahead:select', function(e, result) { window.location = baseURL + result.url; }); }); ================================================ FILE: lib/jazzy/themes/apple/assets/js/typeahead.jquery.js ================================================ /*! * typeahead.js 1.3.3 * https://github.com/corejavascript/typeahead.js * Copyright 2013-2024 Twitter, Inc. and other contributors; Licensed MIT */ (function(root, factory) { if (typeof define === "function" && define.amd) { define([ "jquery" ], function(a0) { return factory(a0); }); } else if (typeof module === "object" && module.exports) { module.exports = factory(require("jquery")); } else { factory(root["jQuery"]); } })(this, function($) { var _ = function() { "use strict"; return { isMsie: function() { return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false; }, isBlankString: function(str) { return !str || /^\s*$/.test(str); }, escapeRegExChars: function(str) { return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); }, isString: function(obj) { return typeof obj === "string"; }, isNumber: function(obj) { return typeof obj === "number"; }, isArray: $.isArray, isFunction: $.isFunction, isObject: $.isPlainObject, isUndefined: function(obj) { return typeof obj === "undefined"; }, isElement: function(obj) { return !!(obj && obj.nodeType === 1); }, isJQuery: function(obj) { return obj instanceof $; }, toStr: function toStr(s) { return _.isUndefined(s) || s === null ? "" : s + ""; }, bind: $.proxy, each: function(collection, cb) { $.each(collection, reverseArgs); function reverseArgs(index, value) { return cb(value, index); } }, map: $.map, filter: $.grep, every: function(obj, test) { var result = true; if (!obj) { return result; } $.each(obj, function(key, val) { if (!(result = test.call(null, val, key, obj))) { return false; } }); return !!result; }, some: function(obj, test) { var result = false; if (!obj) { return result; } $.each(obj, function(key, val) { if (result = test.call(null, val, key, obj)) { return false; } }); return !!result; }, mixin: $.extend, identity: function(x) { return x; }, clone: function(obj) { return $.extend(true, {}, obj); }, getIdGenerator: function() { var counter = 0; return function() { return counter++; }; }, templatify: function templatify(obj) { return $.isFunction(obj) ? obj : template; function template() { return String(obj); } }, defer: function(fn) { setTimeout(fn, 0); }, debounce: function(func, wait, immediate) { var timeout, result; return function() { var context = this, args = arguments, later, callNow; later = function() { timeout = null; if (!immediate) { result = func.apply(context, args); } }; callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); if (callNow) { result = func.apply(context, args); } return result; }; }, throttle: function(func, wait) { var context, args, timeout, result, previous, later; previous = 0; later = function() { previous = new Date(); timeout = null; result = func.apply(context, args); }; return function() { var now = new Date(), remaining = wait - (now - previous); context = this; args = arguments; if (remaining <= 0) { clearTimeout(timeout); timeout = null; previous = now; result = func.apply(context, args); } else if (!timeout) { timeout = setTimeout(later, remaining); } return result; }; }, stringify: function(val) { return _.isString(val) ? val : JSON.stringify(val); }, guid: function() { function _p8(s) { var p = (Math.random().toString(16) + "000000000").substr(2, 8); return s ? "-" + p.substr(0, 4) + "-" + p.substr(4, 4) : p; } return "tt-" + _p8() + _p8(true) + _p8(true) + _p8(); }, noop: function() {} }; }(); var WWW = function() { "use strict"; var defaultClassNames = { wrapper: "twitter-typeahead", input: "tt-input", hint: "tt-hint", menu: "tt-menu", dataset: "tt-dataset", suggestion: "tt-suggestion", selectable: "tt-selectable", empty: "tt-empty", open: "tt-open", cursor: "tt-cursor", highlight: "tt-highlight" }; return build; function build(o) { var www, classes; classes = _.mixin({}, defaultClassNames, o); www = { css: buildCss(), classes: classes, html: buildHtml(classes), selectors: buildSelectors(classes) }; return { css: www.css, html: www.html, classes: www.classes, selectors: www.selectors, mixin: function(o) { _.mixin(o, www); } }; } function buildHtml(c) { return { wrapper: '', menu: '
      ' }; } function buildSelectors(classes) { var selectors = {}; _.each(classes, function(v, k) { selectors[k] = "." + v; }); return selectors; } function buildCss() { var css = { wrapper: { position: "relative", display: "inline-block" }, hint: { position: "absolute", top: "0", left: "0", borderColor: "transparent", boxShadow: "none", opacity: "1" }, input: { position: "relative", verticalAlign: "top", backgroundColor: "transparent" }, inputWithNoHint: { position: "relative", verticalAlign: "top" }, menu: { position: "absolute", top: "100%", left: "0", zIndex: "100", display: "none" }, ltr: { left: "0", right: "auto" }, rtl: { left: "auto", right: " 0" } }; if (_.isMsie()) { _.mixin(css.input, { backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)" }); } return css; } }(); var EventBus = function() { "use strict"; var namespace, deprecationMap; namespace = "typeahead:"; deprecationMap = { render: "rendered", cursorchange: "cursorchanged", select: "selected", autocomplete: "autocompleted" }; function EventBus(o) { if (!o || !o.el) { $.error("EventBus initialized without el"); } this.$el = $(o.el); } _.mixin(EventBus.prototype, { _trigger: function(type, args) { var $e = $.Event(namespace + type); this.$el.trigger.call(this.$el, $e, args || []); return $e; }, before: function(type) { var args, $e; args = [].slice.call(arguments, 1); $e = this._trigger("before" + type, args); return $e.isDefaultPrevented(); }, trigger: function(type) { var deprecatedType; this._trigger(type, [].slice.call(arguments, 1)); if (deprecatedType = deprecationMap[type]) { this._trigger(deprecatedType, [].slice.call(arguments, 1)); } } }); return EventBus; }(); var EventEmitter = function() { "use strict"; var splitter = /\s+/, nextTick = getNextTick(); return { onSync: onSync, onAsync: onAsync, off: off, trigger: trigger }; function on(method, types, cb, context) { var type; if (!cb) { return this; } types = types.split(splitter); cb = context ? bindContext(cb, context) : cb; this._callbacks = this._callbacks || {}; while (type = types.shift()) { this._callbacks[type] = this._callbacks[type] || { sync: [], async: [] }; this._callbacks[type][method].push(cb); } return this; } function onAsync(types, cb, context) { return on.call(this, "async", types, cb, context); } function onSync(types, cb, context) { return on.call(this, "sync", types, cb, context); } function off(types) { var type; if (!this._callbacks) { return this; } types = types.split(splitter); while (type = types.shift()) { delete this._callbacks[type]; } return this; } function trigger(types) { var type, callbacks, args, syncFlush, asyncFlush; if (!this._callbacks) { return this; } types = types.split(splitter); args = [].slice.call(arguments, 1); while ((type = types.shift()) && (callbacks = this._callbacks[type])) { syncFlush = getFlush(callbacks.sync, this, [ type ].concat(args)); asyncFlush = getFlush(callbacks.async, this, [ type ].concat(args)); syncFlush() && nextTick(asyncFlush); } return this; } function getFlush(callbacks, context, args) { return flush; function flush() { var cancelled; for (var i = 0, len = callbacks.length; !cancelled && i < len; i += 1) { cancelled = callbacks[i].apply(context, args) === false; } return !cancelled; } } function getNextTick() { var nextTickFn; if (window.setImmediate) { nextTickFn = function nextTickSetImmediate(fn) { setImmediate(function() { fn(); }); }; } else { nextTickFn = function nextTickSetTimeout(fn) { setTimeout(function() { fn(); }, 0); }; } return nextTickFn; } function bindContext(fn, context) { return fn.bind ? fn.bind(context) : function() { fn.apply(context, [].slice.call(arguments, 0)); }; } }(); var highlight = function(doc) { "use strict"; var defaults = { node: null, pattern: null, tagName: "strong", className: null, wordsOnly: false, caseSensitive: false, diacriticInsensitive: false }; var accented = { A: "[AaªÀ-Åà-åĀ-ąǍǎȀ-ȃȦȧᴬᵃḀḁẚẠ-ảₐ℀℁℻⒜Ⓐⓐ㍱-㍴㎀-㎄㎈㎉㎩-㎯㏂㏊㏟㏿Aa]", B: "[BbᴮᵇḂ-ḇℬ⒝Ⓑⓑ㍴㎅-㎇㏃㏈㏔㏝Bb]", C: "[CcÇçĆ-čᶜ℀ℂ℃℅℆ℭⅭⅽ⒞Ⓒⓒ㍶㎈㎉㎝㎠㎤㏄-㏇Cc]", D: "[DdĎďDŽ-džDZ-dzᴰᵈḊ-ḓⅅⅆⅮⅾ⒟Ⓓⓓ㋏㍲㍷-㍹㎗㎭-㎯㏅㏈Dd]", E: "[EeÈ-Ëè-ëĒ-ěȄ-ȇȨȩᴱᵉḘ-ḛẸ-ẽₑ℡ℯℰⅇ⒠Ⓔⓔ㉐㋍㋎Ee]", F: "[FfᶠḞḟ℉ℱ℻⒡Ⓕⓕ㎊-㎌㎙ff-fflFf]", G: "[GgĜ-ģǦǧǴǵᴳᵍḠḡℊ⒢Ⓖⓖ㋌㋍㎇㎍-㎏㎓㎬㏆㏉㏒㏿Gg]", H: "[HhĤĥȞȟʰᴴḢ-ḫẖℋ-ℎ⒣Ⓗⓗ㋌㍱㎐-㎔㏊㏋㏗Hh]", I: "[IiÌ-Ïì-ïĨ-İIJijǏǐȈ-ȋᴵᵢḬḭỈ-ịⁱℐℑℹⅈⅠ-ⅣⅥ-ⅨⅪⅫⅰ-ⅳⅵ-ⅸⅺⅻ⒤Ⓘⓘ㍺㏌㏕fiffiIi]", J: "[JjIJ-ĵLJ-njǰʲᴶⅉ⒥ⒿⓙⱼJj]", K: "[KkĶķǨǩᴷᵏḰ-ḵK⒦Ⓚⓚ㎄㎅㎉㎏㎑㎘㎞㎢㎦㎪㎸㎾㏀㏆㏍-㏏Kk]", L: "[LlĹ-ŀLJ-ljˡᴸḶḷḺ-ḽℒℓ℡Ⅼⅼ⒧Ⓛⓛ㋏㎈㎉㏐-㏓㏕㏖㏿flfflLl]", M: "[MmᴹᵐḾ-ṃ℠™ℳⅯⅿ⒨Ⓜⓜ㍷-㍹㎃㎆㎎㎒㎖㎙-㎨㎫㎳㎷㎹㎽㎿㏁㏂㏎㏐㏔-㏖㏘㏙㏞㏟Mm]", N: "[NnÑñŃ-ʼnNJ-njǸǹᴺṄ-ṋⁿℕ№⒩Ⓝⓝ㎁㎋㎚㎱㎵㎻㏌㏑Nn]", O: "[OoºÒ-Öò-öŌ-őƠơǑǒǪǫȌ-ȏȮȯᴼᵒỌ-ỏₒ℅№ℴ⒪Ⓞⓞ㍵㏇㏒㏖Oo]", P: "[PpᴾᵖṔ-ṗℙ⒫Ⓟⓟ㉐㍱㍶㎀㎊㎩-㎬㎰㎴㎺㏋㏗-㏚Pp]", Q: "[Qqℚ⒬Ⓠⓠ㏃Qq]", R: "[RrŔ-řȐ-ȓʳᴿᵣṘ-ṛṞṟ₨ℛ-ℝ⒭Ⓡⓡ㋍㍴㎭-㎯㏚㏛Rr]", S: "[SsŚ-šſȘșˢṠ-ṣ₨℁℠⒮Ⓢⓢ㎧㎨㎮-㎳㏛㏜stSs]", T: "[TtŢ-ťȚțᵀᵗṪ-ṱẗ℡™⒯Ⓣⓣ㉐㋏㎔㏏ſtstTt]", U: "[UuÙ-Üù-üŨ-ųƯưǓǔȔ-ȗᵁᵘᵤṲ-ṷỤ-ủ℆⒰Ⓤⓤ㍳㍺Uu]", V: "[VvᵛᵥṼ-ṿⅣ-Ⅷⅳ-ⅷ⒱Ⓥⓥⱽ㋎㍵㎴-㎹㏜㏞Vv]", W: "[WwŴŵʷᵂẀ-ẉẘ⒲Ⓦⓦ㎺-㎿㏝Ww]", X: "[XxˣẊ-ẍₓ℻Ⅸ-Ⅻⅸ-ⅻ⒳Ⓧⓧ㏓Xx]", Y: "[YyÝýÿŶ-ŸȲȳʸẎẏẙỲ-ỹ⒴Ⓨⓨ㏉Yy]", Z: "[ZzŹ-žDZ-dzᶻẐ-ẕℤℨ⒵Ⓩⓩ㎐-㎔Zz]" }; return function hightlight(o) { var regex; o = _.mixin({}, defaults, o); if (!o.node || !o.pattern) { return; } o.pattern = _.isArray(o.pattern) ? o.pattern : [ o.pattern ]; regex = getRegex(o.pattern, o.caseSensitive, o.wordsOnly, o.diacriticInsensitive); traverse(o.node, hightlightTextNode); function hightlightTextNode(textNode) { var match, patternNode, wrapperNode; if (match = regex.exec(textNode.data)) { wrapperNode = doc.createElement(o.tagName); o.className && (wrapperNode.className = o.className); patternNode = textNode.splitText(match.index); patternNode.splitText(match[0].length); wrapperNode.appendChild(patternNode.cloneNode(true)); textNode.parentNode.replaceChild(wrapperNode, patternNode); } return !!match; } function traverse(el, hightlightTextNode) { var childNode, TEXT_NODE_TYPE = 3; for (var i = 0; i < el.childNodes.length; i++) { childNode = el.childNodes[i]; if (childNode.nodeType === TEXT_NODE_TYPE) { i += hightlightTextNode(childNode) ? 1 : 0; } else { traverse(childNode, hightlightTextNode); } } } }; function accent_replacer(chr) { return accented[chr.toUpperCase()] || chr; } function getRegex(patterns, caseSensitive, wordsOnly, diacriticInsensitive) { var escapedPatterns = [], regexStr; for (var i = 0, len = patterns.length; i < len; i++) { var escapedWord = _.escapeRegExChars(patterns[i]); if (diacriticInsensitive) { escapedWord = escapedWord.replace(/\S/g, accent_replacer); } escapedPatterns.push(escapedWord); } regexStr = wordsOnly ? "\\b(" + escapedPatterns.join("|") + ")\\b" : "(" + escapedPatterns.join("|") + ")"; return caseSensitive ? new RegExp(regexStr) : new RegExp(regexStr, "i"); } }(window.document); var Input = function() { "use strict"; var specialKeyCodeMap; specialKeyCodeMap = { 9: "tab", 27: "esc", 37: "left", 39: "right", 13: "enter", 38: "up", 40: "down" }; function Input(o, www) { var id; o = o || {}; if (!o.input) { $.error("input is missing"); } www.mixin(this); this.$hint = $(o.hint); this.$input = $(o.input); this.$menu = $(o.menu); id = this.$input.attr("id") || _.guid(); this.$menu.attr("id", id + "_listbox"); this.$hint.attr({ "aria-hidden": true }); this.$input.attr({ "aria-owns": id + "_listbox", "aria-controls": id + "_listbox", role: "combobox", "aria-autocomplete": "list", "aria-expanded": false }); this.query = this.$input.val(); this.queryWhenFocused = this.hasFocus() ? this.query : null; this.$overflowHelper = buildOverflowHelper(this.$input); this._checkLanguageDirection(); if (this.$hint.length === 0) { this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = _.noop; } this.onSync("cursorchange", this._updateDescendent); } Input.normalizeQuery = function(str) { return _.toStr(str).replace(/^\s*/g, "").replace(/\s{2,}/g, " "); }; _.mixin(Input.prototype, EventEmitter, { _onBlur: function onBlur() { this.resetInputValue(); this.trigger("blurred"); }, _onFocus: function onFocus() { this.queryWhenFocused = this.query; this.trigger("focused"); }, _onKeydown: function onKeydown($e) { var keyName = specialKeyCodeMap[$e.which || $e.keyCode]; this._managePreventDefault(keyName, $e); if (keyName && this._shouldTrigger(keyName, $e)) { this.trigger(keyName + "Keyed", $e); } }, _onInput: function onInput() { this._setQuery(this.getInputValue()); this.clearHintIfInvalid(); this._checkLanguageDirection(); }, _managePreventDefault: function managePreventDefault(keyName, $e) { var preventDefault; switch (keyName) { case "up": case "down": preventDefault = !withModifier($e); break; default: preventDefault = false; } preventDefault && $e.preventDefault(); }, _shouldTrigger: function shouldTrigger(keyName, $e) { var trigger; switch (keyName) { case "tab": trigger = !withModifier($e); break; default: trigger = true; } return trigger; }, _checkLanguageDirection: function checkLanguageDirection() { var dir = (this.$input.css("direction") || "ltr").toLowerCase(); if (this.dir !== dir) { this.dir = dir; this.$hint.attr("dir", dir); this.trigger("langDirChanged", dir); } }, _setQuery: function setQuery(val, silent) { var areEquivalent, hasDifferentWhitespace; areEquivalent = areQueriesEquivalent(val, this.query); hasDifferentWhitespace = areEquivalent ? this.query.length !== val.length : false; this.query = val; if (!silent && !areEquivalent) { this.trigger("queryChanged", this.query); } else if (!silent && hasDifferentWhitespace) { this.trigger("whitespaceChanged", this.query); } }, _updateDescendent: function updateDescendent(event, id) { this.$input.attr("aria-activedescendant", id); }, bind: function() { var that = this, onBlur, onFocus, onKeydown, onInput; onBlur = _.bind(this._onBlur, this); onFocus = _.bind(this._onFocus, this); onKeydown = _.bind(this._onKeydown, this); onInput = _.bind(this._onInput, this); this.$input.on("blur.tt", onBlur).on("focus.tt", onFocus).on("keydown.tt", onKeydown); if (!_.isMsie() || _.isMsie() > 9) { this.$input.on("input.tt", onInput); } else { this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) { if (specialKeyCodeMap[$e.which || $e.keyCode]) { return; } _.defer(_.bind(that._onInput, that, $e)); }); } return this; }, focus: function focus() { this.$input.focus(); }, blur: function blur() { this.$input.blur(); }, getLangDir: function getLangDir() { return this.dir; }, getQuery: function getQuery() { return this.query || ""; }, setQuery: function setQuery(val, silent) { this.setInputValue(val); this._setQuery(val, silent); }, hasQueryChangedSinceLastFocus: function hasQueryChangedSinceLastFocus() { return this.query !== this.queryWhenFocused; }, getInputValue: function getInputValue() { return this.$input.val(); }, setInputValue: function setInputValue(value) { this.$input.val(value); this.clearHintIfInvalid(); this._checkLanguageDirection(); }, resetInputValue: function resetInputValue() { this.setInputValue(this.query); }, getHint: function getHint() { return this.$hint.val(); }, setHint: function setHint(value) { this.$hint.val(value); }, clearHint: function clearHint() { this.setHint(""); }, clearHintIfInvalid: function clearHintIfInvalid() { var val, hint, valIsPrefixOfHint, isValid; val = this.getInputValue(); hint = this.getHint(); valIsPrefixOfHint = val !== hint && hint.indexOf(val) === 0; isValid = val !== "" && valIsPrefixOfHint && !this.hasOverflow(); !isValid && this.clearHint(); }, hasFocus: function hasFocus() { return this.$input.is(":focus"); }, hasOverflow: function hasOverflow() { var constraint = this.$input.width() - 2; this.$overflowHelper.text(this.getInputValue()); return this.$overflowHelper.width() >= constraint; }, isCursorAtEnd: function() { var valueLength, selectionStart, range; valueLength = this.$input.val().length; selectionStart = this.$input[0].selectionStart; if (_.isNumber(selectionStart)) { return selectionStart === valueLength; } else if (document.selection) { range = document.selection.createRange(); range.moveStart("character", -valueLength); return valueLength === range.text.length; } return true; }, destroy: function destroy() { this.$hint.off(".tt"); this.$input.off(".tt"); this.$overflowHelper.remove(); this.$hint = this.$input = this.$overflowHelper = $("
      "); }, setAriaExpanded: function setAriaExpanded(value) { this.$input.attr("aria-expanded", value); } }); return Input; function buildOverflowHelper($input) { return $('').css({ position: "absolute", visibility: "hidden", whiteSpace: "pre", fontFamily: $input.css("font-family"), fontSize: $input.css("font-size"), fontStyle: $input.css("font-style"), fontVariant: $input.css("font-variant"), fontWeight: $input.css("font-weight"), wordSpacing: $input.css("word-spacing"), letterSpacing: $input.css("letter-spacing"), textIndent: $input.css("text-indent"), textRendering: $input.css("text-rendering"), textTransform: $input.css("text-transform") }).insertAfter($input); } function areQueriesEquivalent(a, b) { return Input.normalizeQuery(a) === Input.normalizeQuery(b); } function withModifier($e) { return $e.altKey || $e.ctrlKey || $e.metaKey || $e.shiftKey; } }(); var Dataset = function() { "use strict"; var keys, nameGenerator; keys = { dataset: "tt-selectable-dataset", val: "tt-selectable-display", obj: "tt-selectable-object" }; nameGenerator = _.getIdGenerator(); function Dataset(o, www) { o = o || {}; o.templates = o.templates || {}; o.templates.notFound = o.templates.notFound || o.templates.empty; if (!o.source) { $.error("missing source"); } if (!o.node) { $.error("missing node"); } if (o.name && !isValidName(o.name)) { $.error("invalid dataset name: " + o.name); } www.mixin(this); this.highlight = !!o.highlight; this.name = _.toStr(o.name || nameGenerator()); this.limit = o.limit || 5; this.displayFn = getDisplayFn(o.display || o.displayKey); this.templates = getTemplates(o.templates, this.displayFn); this.source = o.source.__ttAdapter ? o.source.__ttAdapter() : o.source; this.async = _.isUndefined(o.async) ? this.source.length > 2 : !!o.async; this._resetLastSuggestion(); this.$el = $(o.node).attr("role", "presentation").addClass(this.classes.dataset).addClass(this.classes.dataset + "-" + this.name); } Dataset.extractData = function extractData(el) { var $el = $(el); if ($el.data(keys.obj)) { return { dataset: $el.data(keys.dataset) || "", val: $el.data(keys.val) || "", obj: $el.data(keys.obj) || null }; } return null; }; _.mixin(Dataset.prototype, EventEmitter, { _overwrite: function overwrite(query, suggestions) { suggestions = suggestions || []; if (suggestions.length) { this._renderSuggestions(query, suggestions); } else if (this.async && this.templates.pending) { this._renderPending(query); } else if (!this.async && this.templates.notFound) { this._renderNotFound(query); } else { this._empty(); } this.trigger("rendered", suggestions, false, this.name); }, _append: function append(query, suggestions) { suggestions = suggestions || []; if (suggestions.length && this.$lastSuggestion.length) { this._appendSuggestions(query, suggestions); } else if (suggestions.length) { this._renderSuggestions(query, suggestions); } else if (!this.$lastSuggestion.length && this.templates.notFound) { this._renderNotFound(query); } this.trigger("rendered", suggestions, true, this.name); }, _renderSuggestions: function renderSuggestions(query, suggestions) { var $fragment; $fragment = this._getSuggestionsFragment(query, suggestions); this.$lastSuggestion = $fragment.children().last(); this.$el.html($fragment).prepend(this._getHeader(query, suggestions)).append(this._getFooter(query, suggestions)); }, _appendSuggestions: function appendSuggestions(query, suggestions) { var $fragment, $lastSuggestion; $fragment = this._getSuggestionsFragment(query, suggestions); $lastSuggestion = $fragment.children().last(); this.$lastSuggestion.after($fragment); this.$lastSuggestion = $lastSuggestion; }, _renderPending: function renderPending(query) { var template = this.templates.pending; this._resetLastSuggestion(); template && this.$el.html(template({ query: query, dataset: this.name })); }, _renderNotFound: function renderNotFound(query) { var template = this.templates.notFound; this._resetLastSuggestion(); template && this.$el.html(template({ query: query, dataset: this.name })); }, _empty: function empty() { this.$el.empty(); this._resetLastSuggestion(); }, _getSuggestionsFragment: function getSuggestionsFragment(query, suggestions) { var that = this, fragment; fragment = document.createDocumentFragment(); _.each(suggestions, function getSuggestionNode(suggestion) { var $el, context; context = that._injectQuery(query, suggestion); $el = $(that.templates.suggestion(context)).data(keys.dataset, that.name).data(keys.obj, suggestion).data(keys.val, that.displayFn(suggestion)).addClass(that.classes.suggestion + " " + that.classes.selectable); fragment.appendChild($el[0]); }); this.highlight && highlight({ className: this.classes.highlight, node: fragment, pattern: query }); return $(fragment); }, _getFooter: function getFooter(query, suggestions) { return this.templates.footer ? this.templates.footer({ query: query, suggestions: suggestions, dataset: this.name }) : null; }, _getHeader: function getHeader(query, suggestions) { return this.templates.header ? this.templates.header({ query: query, suggestions: suggestions, dataset: this.name }) : null; }, _resetLastSuggestion: function resetLastSuggestion() { this.$lastSuggestion = $(); }, _injectQuery: function injectQuery(query, obj) { return _.isObject(obj) ? _.mixin({ _query: query }, obj) : obj; }, update: function update(query) { var that = this, canceled = false, syncCalled = false, rendered = 0; this.cancel(); this.cancel = function cancel() { canceled = true; that.cancel = $.noop; that.async && that.trigger("asyncCanceled", query, that.name); }; this.source(query, sync, async); !syncCalled && sync([]); function sync(suggestions) { if (syncCalled) { return; } syncCalled = true; suggestions = (suggestions || []).slice(0, that.limit); rendered = suggestions.length; that._overwrite(query, suggestions); if (rendered < that.limit && that.async) { that.trigger("asyncRequested", query, that.name); } } function async(suggestions) { suggestions = suggestions || []; if (!canceled && rendered < that.limit) { that.cancel = $.noop; var idx = Math.abs(rendered - that.limit); rendered += idx; that._append(query, suggestions.slice(0, idx)); that.async && that.trigger("asyncReceived", query, that.name); } } }, cancel: $.noop, clear: function clear() { this._empty(); this.cancel(); this.trigger("cleared"); }, isEmpty: function isEmpty() { return this.$el.is(":empty"); }, destroy: function destroy() { this.$el = $("
      "); } }); return Dataset; function getDisplayFn(display) { display = display || _.stringify; return _.isFunction(display) ? display : displayFn; function displayFn(obj) { return obj[display]; } } function getTemplates(templates, displayFn) { return { notFound: templates.notFound && _.templatify(templates.notFound), pending: templates.pending && _.templatify(templates.pending), header: templates.header && _.templatify(templates.header), footer: templates.footer && _.templatify(templates.footer), suggestion: templates.suggestion ? userSuggestionTemplate : suggestionTemplate }; function userSuggestionTemplate(context) { var template = templates.suggestion; return $(template(context)).attr("id", _.guid()); } function suggestionTemplate(context) { return $('
      ').attr("id", _.guid()).text(displayFn(context)); } } function isValidName(str) { return /^[_a-zA-Z0-9-]+$/.test(str); } }(); var Menu = function() { "use strict"; function Menu(o, www) { var that = this; o = o || {}; if (!o.node) { $.error("node is required"); } www.mixin(this); this.$node = $(o.node); this.query = null; this.datasets = _.map(o.datasets, initializeDataset); function initializeDataset(oDataset) { var node = that.$node.find(oDataset.node).first(); oDataset.node = node.length ? node : $("
      ").appendTo(that.$node); return new Dataset(oDataset, www); } } _.mixin(Menu.prototype, EventEmitter, { _onSelectableClick: function onSelectableClick($e) { this.trigger("selectableClicked", $($e.currentTarget)); }, _onRendered: function onRendered(type, dataset, suggestions, async) { this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty()); this.trigger("datasetRendered", dataset, suggestions, async); }, _onCleared: function onCleared() { this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty()); this.trigger("datasetCleared"); }, _propagate: function propagate() { this.trigger.apply(this, arguments); }, _allDatasetsEmpty: function allDatasetsEmpty() { return _.every(this.datasets, _.bind(function isDatasetEmpty(dataset) { var isEmpty = dataset.isEmpty(); this.$node.attr("aria-expanded", !isEmpty); return isEmpty; }, this)); }, _getSelectables: function getSelectables() { return this.$node.find(this.selectors.selectable); }, _removeCursor: function _removeCursor() { var $selectable = this.getActiveSelectable(); $selectable && $selectable.removeClass(this.classes.cursor); }, _ensureVisible: function ensureVisible($el) { var elTop, elBottom, nodeScrollTop, nodeHeight; elTop = $el.position().top; elBottom = elTop + $el.outerHeight(true); nodeScrollTop = this.$node.scrollTop(); nodeHeight = this.$node.height() + parseInt(this.$node.css("paddingTop"), 10) + parseInt(this.$node.css("paddingBottom"), 10); if (elTop < 0) { this.$node.scrollTop(nodeScrollTop + elTop); } else if (nodeHeight < elBottom) { this.$node.scrollTop(nodeScrollTop + (elBottom - nodeHeight)); } }, bind: function() { var that = this, onSelectableClick; onSelectableClick = _.bind(this._onSelectableClick, this); this.$node.on("click.tt", this.selectors.selectable, onSelectableClick); this.$node.on("mouseover", this.selectors.selectable, function() { that.setCursor($(this)); }); this.$node.on("mouseleave", function() { that._removeCursor(); }); _.each(this.datasets, function(dataset) { dataset.onSync("asyncRequested", that._propagate, that).onSync("asyncCanceled", that._propagate, that).onSync("asyncReceived", that._propagate, that).onSync("rendered", that._onRendered, that).onSync("cleared", that._onCleared, that); }); return this; }, isOpen: function isOpen() { return this.$node.hasClass(this.classes.open); }, open: function open() { this.$node.scrollTop(0); this.$node.addClass(this.classes.open); }, close: function close() { this.$node.attr("aria-expanded", false); this.$node.removeClass(this.classes.open); this._removeCursor(); }, setLanguageDirection: function setLanguageDirection(dir) { this.$node.attr("dir", dir); }, selectableRelativeToCursor: function selectableRelativeToCursor(delta) { var $selectables, $oldCursor, oldIndex, newIndex; $oldCursor = this.getActiveSelectable(); $selectables = this._getSelectables(); oldIndex = $oldCursor ? $selectables.index($oldCursor) : -1; newIndex = oldIndex + delta; newIndex = (newIndex + 1) % ($selectables.length + 1) - 1; newIndex = newIndex < -1 ? $selectables.length - 1 : newIndex; return newIndex === -1 ? null : $selectables.eq(newIndex); }, setCursor: function setCursor($selectable) { this._removeCursor(); if ($selectable = $selectable && $selectable.first()) { $selectable.addClass(this.classes.cursor); this._ensureVisible($selectable); } }, getSelectableData: function getSelectableData($el) { return $el && $el.length ? Dataset.extractData($el) : null; }, getActiveSelectable: function getActiveSelectable() { var $selectable = this._getSelectables().filter(this.selectors.cursor).first(); return $selectable.length ? $selectable : null; }, getTopSelectable: function getTopSelectable() { var $selectable = this._getSelectables().first(); return $selectable.length ? $selectable : null; }, update: function update(query) { var isValidUpdate = query !== this.query; if (isValidUpdate) { this.query = query; _.each(this.datasets, updateDataset); } return isValidUpdate; function updateDataset(dataset) { dataset.update(query); } }, empty: function empty() { _.each(this.datasets, clearDataset); this.query = null; this.$node.addClass(this.classes.empty); function clearDataset(dataset) { dataset.clear(); } }, destroy: function destroy() { this.$node.off(".tt"); this.$node = $("
      "); _.each(this.datasets, destroyDataset); function destroyDataset(dataset) { dataset.destroy(); } } }); return Menu; }(); var Status = function() { "use strict"; function Status(options) { this.$el = $("", { role: "status", "aria-live": "polite" }).css({ position: "absolute", padding: "0", border: "0", height: "1px", width: "1px", "margin-bottom": "-1px", "margin-right": "-1px", overflow: "hidden", clip: "rect(0 0 0 0)", "white-space": "nowrap" }); options.$input.after(this.$el); _.each(options.menu.datasets, _.bind(function(dataset) { if (dataset.onSync) { dataset.onSync("rendered", _.bind(this.update, this)); dataset.onSync("cleared", _.bind(this.cleared, this)); } }, this)); } _.mixin(Status.prototype, { update: function update(event, suggestions) { var length = suggestions.length; var words; if (length === 1) { words = { result: "result", is: "is" }; } else { words = { result: "results", is: "are" }; } this.$el.text(length + " " + words.result + " " + words.is + " available, use up and down arrow keys to navigate."); }, cleared: function() { this.$el.text(""); } }); return Status; }(); var DefaultMenu = function() { "use strict"; var s = Menu.prototype; function DefaultMenu() { Menu.apply(this, [].slice.call(arguments, 0)); } _.mixin(DefaultMenu.prototype, Menu.prototype, { open: function open() { !this._allDatasetsEmpty() && this._show(); return s.open.apply(this, [].slice.call(arguments, 0)); }, close: function close() { this._hide(); return s.close.apply(this, [].slice.call(arguments, 0)); }, _onRendered: function onRendered() { if (this._allDatasetsEmpty()) { this._hide(); } else { this.isOpen() && this._show(); } return s._onRendered.apply(this, [].slice.call(arguments, 0)); }, _onCleared: function onCleared() { if (this._allDatasetsEmpty()) { this._hide(); } else { this.isOpen() && this._show(); } return s._onCleared.apply(this, [].slice.call(arguments, 0)); }, setLanguageDirection: function setLanguageDirection(dir) { this.$node.css(dir === "ltr" ? this.css.ltr : this.css.rtl); return s.setLanguageDirection.apply(this, [].slice.call(arguments, 0)); }, _hide: function hide() { this.$node.hide(); }, _show: function show() { this.$node.css("display", "block"); } }); return DefaultMenu; }(); var Typeahead = function() { "use strict"; function Typeahead(o, www) { var onFocused, onBlurred, onEnterKeyed, onTabKeyed, onEscKeyed, onUpKeyed, onDownKeyed, onLeftKeyed, onRightKeyed, onQueryChanged, onWhitespaceChanged; o = o || {}; if (!o.input) { $.error("missing input"); } if (!o.menu) { $.error("missing menu"); } if (!o.eventBus) { $.error("missing event bus"); } www.mixin(this); this.eventBus = o.eventBus; this.minLength = _.isNumber(o.minLength) ? o.minLength : 1; this.input = o.input; this.menu = o.menu; this.enabled = true; this.autoselect = !!o.autoselect; this.active = false; this.input.hasFocus() && this.activate(); this.dir = this.input.getLangDir(); this._hacks(); this.menu.bind().onSync("selectableClicked", this._onSelectableClicked, this).onSync("asyncRequested", this._onAsyncRequested, this).onSync("asyncCanceled", this._onAsyncCanceled, this).onSync("asyncReceived", this._onAsyncReceived, this).onSync("datasetRendered", this._onDatasetRendered, this).onSync("datasetCleared", this._onDatasetCleared, this); onFocused = c(this, "activate", "open", "_onFocused"); onBlurred = c(this, "deactivate", "_onBlurred"); onEnterKeyed = c(this, "isActive", "isOpen", "_onEnterKeyed"); onTabKeyed = c(this, "isActive", "isOpen", "_onTabKeyed"); onEscKeyed = c(this, "isActive", "_onEscKeyed"); onUpKeyed = c(this, "isActive", "open", "_onUpKeyed"); onDownKeyed = c(this, "isActive", "open", "_onDownKeyed"); onLeftKeyed = c(this, "isActive", "isOpen", "_onLeftKeyed"); onRightKeyed = c(this, "isActive", "isOpen", "_onRightKeyed"); onQueryChanged = c(this, "_openIfActive", "_onQueryChanged"); onWhitespaceChanged = c(this, "_openIfActive", "_onWhitespaceChanged"); this.input.bind().onSync("focused", onFocused, this).onSync("blurred", onBlurred, this).onSync("enterKeyed", onEnterKeyed, this).onSync("tabKeyed", onTabKeyed, this).onSync("escKeyed", onEscKeyed, this).onSync("upKeyed", onUpKeyed, this).onSync("downKeyed", onDownKeyed, this).onSync("leftKeyed", onLeftKeyed, this).onSync("rightKeyed", onRightKeyed, this).onSync("queryChanged", onQueryChanged, this).onSync("whitespaceChanged", onWhitespaceChanged, this).onSync("langDirChanged", this._onLangDirChanged, this); } _.mixin(Typeahead.prototype, { _hacks: function hacks() { var $input, $menu; $input = this.input.$input || $("
      "); $menu = this.menu.$node || $("
      "); $input.on("blur.tt", function($e) { var active, isActive, hasActive; active = document.activeElement; isActive = $menu.is(active); hasActive = $menu.has(active).length > 0; if (_.isMsie() && (isActive || hasActive)) { $e.preventDefault(); $e.stopImmediatePropagation(); _.defer(function() { $input.focus(); }); } }); $menu.on("mousedown.tt", function($e) { $e.preventDefault(); }); }, _onSelectableClicked: function onSelectableClicked(type, $el) { this.select($el); }, _onDatasetCleared: function onDatasetCleared() { this._updateHint(); }, _onDatasetRendered: function onDatasetRendered(type, suggestions, async, dataset) { this._updateHint(); if (this.autoselect) { var cursorClass = this.selectors.cursor.substr(1); this.menu.$node.find(this.selectors.suggestion).first().addClass(cursorClass); } this.eventBus.trigger("render", suggestions, async, dataset); }, _onAsyncRequested: function onAsyncRequested(type, dataset, query) { this.eventBus.trigger("asyncrequest", query, dataset); }, _onAsyncCanceled: function onAsyncCanceled(type, dataset, query) { this.eventBus.trigger("asynccancel", query, dataset); }, _onAsyncReceived: function onAsyncReceived(type, dataset, query) { this.eventBus.trigger("asyncreceive", query, dataset); }, _onFocused: function onFocused() { this._minLengthMet() && this.menu.update(this.input.getQuery()); }, _onBlurred: function onBlurred() { if (this.input.hasQueryChangedSinceLastFocus()) { this.eventBus.trigger("change", this.input.getQuery()); } }, _onEnterKeyed: function onEnterKeyed(type, $e) { var $selectable; if ($selectable = this.menu.getActiveSelectable()) { if (this.select($selectable)) { $e.preventDefault(); $e.stopPropagation(); } } else if (this.autoselect) { if (this.select(this.menu.getTopSelectable())) { $e.preventDefault(); $e.stopPropagation(); } } }, _onTabKeyed: function onTabKeyed(type, $e) { var $selectable; if ($selectable = this.menu.getActiveSelectable()) { this.select($selectable) && $e.preventDefault(); } else if (this.autoselect) { if ($selectable = this.menu.getTopSelectable()) { this.autocomplete($selectable) && $e.preventDefault(); } } }, _onEscKeyed: function onEscKeyed() { this.close(); }, _onUpKeyed: function onUpKeyed() { this.moveCursor(-1); }, _onDownKeyed: function onDownKeyed() { this.moveCursor(+1); }, _onLeftKeyed: function onLeftKeyed() { if (this.dir === "rtl" && this.input.isCursorAtEnd()) { this.autocomplete(this.menu.getActiveSelectable() || this.menu.getTopSelectable()); } }, _onRightKeyed: function onRightKeyed() { if (this.dir === "ltr" && this.input.isCursorAtEnd()) { this.autocomplete(this.menu.getActiveSelectable() || this.menu.getTopSelectable()); } }, _onQueryChanged: function onQueryChanged(e, query) { this._minLengthMet(query) ? this.menu.update(query) : this.menu.empty(); }, _onWhitespaceChanged: function onWhitespaceChanged() { this._updateHint(); }, _onLangDirChanged: function onLangDirChanged(e, dir) { if (this.dir !== dir) { this.dir = dir; this.menu.setLanguageDirection(dir); } }, _openIfActive: function openIfActive() { this.isActive() && this.open(); }, _minLengthMet: function minLengthMet(query) { query = _.isString(query) ? query : this.input.getQuery() || ""; return query.length >= this.minLength; }, _updateHint: function updateHint() { var $selectable, data, val, query, escapedQuery, frontMatchRegEx, match; $selectable = this.menu.getTopSelectable(); data = this.menu.getSelectableData($selectable); val = this.input.getInputValue(); if (data && !_.isBlankString(val) && !this.input.hasOverflow()) { query = Input.normalizeQuery(val); escapedQuery = _.escapeRegExChars(query); frontMatchRegEx = new RegExp("^(?:" + escapedQuery + ")(.+$)", "i"); match = frontMatchRegEx.exec(data.val); match && this.input.setHint(val + match[1]); } else { this.input.clearHint(); } }, isEnabled: function isEnabled() { return this.enabled; }, enable: function enable() { this.enabled = true; }, disable: function disable() { this.enabled = false; }, isActive: function isActive() { return this.active; }, activate: function activate() { if (this.isActive()) { return true; } else if (!this.isEnabled() || this.eventBus.before("active")) { return false; } else { this.active = true; this.eventBus.trigger("active"); return true; } }, deactivate: function deactivate() { if (!this.isActive()) { return true; } else if (this.eventBus.before("idle")) { return false; } else { this.active = false; this.close(); this.eventBus.trigger("idle"); return true; } }, isOpen: function isOpen() { return this.menu.isOpen(); }, open: function open() { if (!this.isOpen() && !this.eventBus.before("open")) { this.input.setAriaExpanded(true); this.menu.open(); this._updateHint(); this.eventBus.trigger("open"); } return this.isOpen(); }, close: function close() { if (this.isOpen() && !this.eventBus.before("close")) { this.input.setAriaExpanded(false); this.menu.close(); this.input.clearHint(); this.input.resetInputValue(); this.eventBus.trigger("close"); } return !this.isOpen(); }, setVal: function setVal(val) { this.input.setQuery(_.toStr(val)); }, getVal: function getVal() { return this.input.getQuery(); }, select: function select($selectable) { var data = this.menu.getSelectableData($selectable); if (data && !this.eventBus.before("select", data.obj, data.dataset)) { this.input.setQuery(data.val, true); this.eventBus.trigger("select", data.obj, data.dataset); this.close(); return true; } return false; }, autocomplete: function autocomplete($selectable) { var query, data, isValid; query = this.input.getQuery(); data = this.menu.getSelectableData($selectable); isValid = data && query !== data.val; if (isValid && !this.eventBus.before("autocomplete", data.obj, data.dataset)) { this.input.setQuery(data.val); this.eventBus.trigger("autocomplete", data.obj, data.dataset); return true; } return false; }, moveCursor: function moveCursor(delta) { var query, $candidate, data, suggestion, datasetName, cancelMove, id; query = this.input.getQuery(); $candidate = this.menu.selectableRelativeToCursor(delta); data = this.menu.getSelectableData($candidate); suggestion = data ? data.obj : null; datasetName = data ? data.dataset : null; id = $candidate ? $candidate.attr("id") : null; this.input.trigger("cursorchange", id); cancelMove = this._minLengthMet() && this.menu.update(query); if (!cancelMove && !this.eventBus.before("cursorchange", suggestion, datasetName)) { this.menu.setCursor($candidate); if (data) { if (typeof data.val === "string") { this.input.setInputValue(data.val); } } else { this.input.resetInputValue(); this._updateHint(); } this.eventBus.trigger("cursorchange", suggestion, datasetName); return true; } return false; }, destroy: function destroy() { this.input.destroy(); this.menu.destroy(); } }); return Typeahead; function c(ctx) { var methods = [].slice.call(arguments, 1); return function() { var args = [].slice.call(arguments); _.each(methods, function(method) { return ctx[method].apply(ctx, args); }); }; } }(); (function() { "use strict"; var old, keys, methods; old = $.fn.typeahead; keys = { www: "tt-www", attrs: "tt-attrs", typeahead: "tt-typeahead" }; methods = { initialize: function initialize(o, datasets) { var www; datasets = _.isArray(datasets) ? datasets : [].slice.call(arguments, 1); o = o || {}; www = WWW(o.classNames); return this.each(attach); function attach() { var $input, $wrapper, $hint, $menu, defaultHint, defaultMenu, eventBus, input, menu, status, typeahead, MenuConstructor; _.each(datasets, function(d) { d.highlight = !!o.highlight; }); $input = $(this); $wrapper = $(www.html.wrapper); $hint = $elOrNull(o.hint); $menu = $elOrNull(o.menu); defaultHint = o.hint !== false && !$hint; defaultMenu = o.menu !== false && !$menu; defaultHint && ($hint = buildHintFromInput($input, www)); defaultMenu && ($menu = $(www.html.menu).css(www.css.menu)); $hint && $hint.val(""); $input = prepInput($input, www); if (defaultHint || defaultMenu) { $wrapper.css(www.css.wrapper); $input.css(defaultHint ? www.css.input : www.css.inputWithNoHint); $input.wrap($wrapper).parent().prepend(defaultHint ? $hint : null).append(defaultMenu ? $menu : null); } MenuConstructor = defaultMenu ? DefaultMenu : Menu; eventBus = new EventBus({ el: $input }); input = new Input({ hint: $hint, input: $input, menu: $menu }, www); menu = new MenuConstructor({ node: $menu, datasets: datasets }, www); status = new Status({ $input: $input, menu: menu }); typeahead = new Typeahead({ input: input, menu: menu, eventBus: eventBus, minLength: o.minLength, autoselect: o.autoselect }, www); $input.data(keys.www, www); $input.data(keys.typeahead, typeahead); } }, isEnabled: function isEnabled() { var enabled; ttEach(this.first(), function(t) { enabled = t.isEnabled(); }); return enabled; }, enable: function enable() { ttEach(this, function(t) { t.enable(); }); return this; }, disable: function disable() { ttEach(this, function(t) { t.disable(); }); return this; }, isActive: function isActive() { var active; ttEach(this.first(), function(t) { active = t.isActive(); }); return active; }, activate: function activate() { ttEach(this, function(t) { t.activate(); }); return this; }, deactivate: function deactivate() { ttEach(this, function(t) { t.deactivate(); }); return this; }, isOpen: function isOpen() { var open; ttEach(this.first(), function(t) { open = t.isOpen(); }); return open; }, open: function open() { ttEach(this, function(t) { t.open(); }); return this; }, close: function close() { ttEach(this, function(t) { t.close(); }); return this; }, select: function select(el) { var success = false, $el = $(el); ttEach(this.first(), function(t) { success = t.select($el); }); return success; }, autocomplete: function autocomplete(el) { var success = false, $el = $(el); ttEach(this.first(), function(t) { success = t.autocomplete($el); }); return success; }, moveCursor: function moveCursoe(delta) { var success = false; ttEach(this.first(), function(t) { success = t.moveCursor(delta); }); return success; }, val: function val(newVal) { var query; if (!arguments.length) { ttEach(this.first(), function(t) { query = t.getVal(); }); return query; } else { ttEach(this, function(t) { t.setVal(_.toStr(newVal)); }); return this; } }, destroy: function destroy() { ttEach(this, function(typeahead, $input) { revert($input); typeahead.destroy(); }); return this; } }; $.fn.typeahead = function(method) { if (methods[method]) { return methods[method].apply(this, [].slice.call(arguments, 1)); } else { return methods.initialize.apply(this, arguments); } }; $.fn.typeahead.noConflict = function noConflict() { $.fn.typeahead = old; return this; }; function ttEach($els, fn) { $els.each(function() { var $input = $(this), typeahead; (typeahead = $input.data(keys.typeahead)) && fn(typeahead, $input); }); } function buildHintFromInput($input, www) { return $input.clone().addClass(www.classes.hint).removeData().css(www.css.hint).css(getBackgroundStyles($input)).prop({ readonly: true, required: false }).removeAttr("id name placeholder").removeClass("required").attr({ spellcheck: "false", tabindex: -1 }); } function prepInput($input, www) { $input.data(keys.attrs, { dir: $input.attr("dir"), autocomplete: $input.attr("autocomplete"), spellcheck: $input.attr("spellcheck"), style: $input.attr("style") }); $input.addClass(www.classes.input).attr({ spellcheck: false }); try { !$input.attr("dir") && $input.attr("dir", "auto"); } catch (e) {} return $input; } function getBackgroundStyles($el) { return { backgroundAttachment: $el.css("background-attachment"), backgroundClip: $el.css("background-clip"), backgroundColor: $el.css("background-color"), backgroundImage: $el.css("background-image"), backgroundOrigin: $el.css("background-origin"), backgroundPosition: $el.css("background-position"), backgroundRepeat: $el.css("background-repeat"), backgroundSize: $el.css("background-size") }; } function revert($input) { var www, $wrapper; www = $input.data(keys.www); $wrapper = $input.parent().filter(www.selectors.wrapper); _.each($input.data(keys.attrs), function(val, key) { _.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val); }); $input.removeData(keys.typeahead).removeData(keys.www).removeData(keys.attr).removeClass(www.classes.input); if ($wrapper.length) { $input.detach().insertAfter($wrapper); $wrapper.remove(); } } function $elOrNull(obj) { var isValid, $el; isValid = _.isJQuery(obj) || _.isElement(obj); $el = isValid ? $(obj).first() : []; return $el.length ? $el : null; } })(); }); ================================================ FILE: lib/jazzy/themes/apple/templates/deprecation.mustache ================================================ {{#deprecation_message}}

      Deprecated

      {{{deprecation_message}}}
      {{/deprecation_message}} {{#unavailable_message}}

      Unavailable

      {{{unavailable_message}}}
      {{/unavailable_message}} ================================================ FILE: lib/jazzy/themes/apple/templates/doc.mustache ================================================ {{name}} {{kind}} Reference {{#enable_katex}} {{/enable_katex}} {{#enable_katex}} {{/enable_katex}} {{{custom_head}}} {{^disable_search}} {{/disable_search}} {{#dash_type}} {{/dash_type}} {{> header}}
      {{> nav}}
      {{^hide_name}}

      {{name}}

      {{/hide_name}} {{> deprecation}} {{#declaration}}
      {{#other_language_declaration}}

      {{language}}

      {{/other_language_declaration}} {{{declaration}}}
      {{#other_language_declaration}}

      Swift

      {{{other_language_declaration}}}
      {{/other_language_declaration}}
      {{/declaration}} {{{overview}}} {{#parameters.any?}}

      Parameters

      {{#parameters}} {{> parameter}} {{/parameters}}
      {{/parameters.any?}} {{#return}}

      Return Value

      {{{return}}}
      {{/return}} {{#source_host_item_url}} {{/source_host_item_url}}
      {{> tasks}}
      {{> footer}}
      ================================================ FILE: lib/jazzy/themes/apple/templates/footer.mustache ================================================ ================================================ FILE: lib/jazzy/themes/apple/templates/header.mustache ================================================

      {{docs_title}}{{#doc_coverage}} ({{doc_coverage}}% documented){{/doc_coverage}}

      {{#source_host_url}}

      {{source_host_name}}View on {{source_host_name}}

      {{/source_host_url}} {{#dash_url}}

      DashInstall in Dash

      {{/dash_url}} {{^disable_search}}
      {{/disable_search}}
      ================================================ FILE: lib/jazzy/themes/apple/templates/nav.mustache ================================================ ================================================ FILE: lib/jazzy/themes/apple/templates/parameter.mustache ================================================ {{name}}
      {{{discussion}}}
      ================================================ FILE: lib/jazzy/themes/apple/templates/task.mustache ================================================
      {{#name}}

      {{{name_html}}}

      {{/name}}
        {{#items}}
      • {{#direct_link}} {{name}} {{/direct_link}} {{^direct_link}} {{^usage_discouraged}} {{{name_html}}} {{/usage_discouraged}} {{#usage_discouraged}} {{{name_html}}} {{/usage_discouraged}} {{#declaration_note}} {{.}} {{/declaration_note}}
        {{> deprecation}} {{#abstract}}
        {{{abstract}}} {{#url}} See more {{/url}}
        {{/abstract}} {{#default_impl_abstract}}

        Default Implementation

        {{{default_impl_abstract}}}
        {{/default_impl_abstract}} {{#declaration}}

        Declaration

        {{language}}

        {{{declaration}}}
        {{#other_language_declaration}}

        Swift

        {{{other_language_declaration}}}
        {{/other_language_declaration}}
        {{/declaration}} {{#parameters.count}}

        Parameters

        {{#parameters}} {{> parameter}} {{/parameters}}
        {{/parameters.count}} {{#return}}

        Return Value

        {{{return}}}
        {{/return}} {{#source_host_item_url}} {{/source_host_item_url}}
        {{/direct_link}}
      • {{/items}}
      ================================================ FILE: lib/jazzy/themes/apple/templates/tasks.mustache ================================================ {{#tasks.count}}
      {{#tasks}} {{> task}} {{/tasks}}
      {{/tasks.count}} ================================================ FILE: lib/jazzy/themes/fullwidth/assets/css/highlight.css.scss ================================================ /*! Jazzy - https://github.com/realm/jazzy * Copyright Realm Inc. * SPDX-License-Identifier: MIT */ /* Credit to https://gist.github.com/wataru420/2048287 */ .highlight { .c { color: #999988; font-style: italic } // Comment .err { color: #a61717; background-color: #e3d2d2 } // Error .k { color: #000000; font-weight: bold } // Keyword .o { color: #000000; font-weight: bold } // Operator .cm { color: #999988; font-style: italic } // Comment.Multiline .cp { color: #999999; font-weight: bold } // Comment.Preproc .c1 { color: #999988; font-style: italic } // Comment.Single .cs { color: #999999; font-weight: bold; font-style: italic } // Comment.Special .gd { color: #000000; background-color: #ffdddd } // Generic.Deleted .gd .x { color: #000000; background-color: #ffaaaa } // Generic.Deleted.Specific .ge { color: #000000; font-style: italic } // Generic.Emph .gr { color: #aa0000 } // Generic.Error .gh { color: #999999 } // Generic.Heading .gi { color: #000000; background-color: #ddffdd } // Generic.Inserted .gi .x { color: #000000; background-color: #aaffaa } // Generic.Inserted.Specific .go { color: #888888 } // Generic.Output .gp { color: #555555 } // Generic.Prompt .gs { font-weight: bold } // Generic.Strong .gu { color: #aaaaaa } // Generic.Subheading .gt { color: #aa0000 } // Generic.Traceback .kc { color: #000000; font-weight: bold } // Keyword.Constant .kd { color: #000000; font-weight: bold } // Keyword.Declaration .kp { color: #000000; font-weight: bold } // Keyword.Pseudo .kr { color: #000000; font-weight: bold } // Keyword.Reserved .kt { color: #445588; } // Keyword.Type .m { color: #009999 } // Literal.Number .s { color: #d14 } // Literal.String .na { color: #008080 } // Name.Attribute .nb { color: #0086B3 } // Name.Builtin .nc { color: #445588; font-weight: bold } // Name.Class .no { color: #008080 } // Name.Constant .ni { color: #800080 } // Name.Entity .ne { color: #990000; font-weight: bold } // Name.Exception .nf { color: #990000; } // Name.Function .nn { color: #555555 } // Name.Namespace .nt { color: #000080 } // Name.Tag .nv { color: #008080 } // Name.Variable .ow { color: #000000; font-weight: bold } // Operator.Word .w { color: #bbbbbb } // Text.Whitespace .mf { color: #009999 } // Literal.Number.Float .mh { color: #009999 } // Literal.Number.Hex .mi { color: #009999 } // Literal.Number.Integer .mo { color: #009999 } // Literal.Number.Oct .sb { color: #d14 } // Literal.String.Backtick .sc { color: #d14 } // Literal.String.Char .sd { color: #d14 } // Literal.String.Doc .s2 { color: #d14 } // Literal.String.Double .se { color: #d14 } // Literal.String.Escape .sh { color: #d14 } // Literal.String.Heredoc .si { color: #d14 } // Literal.String.Interpol .sx { color: #d14 } // Literal.String.Other .sr { color: #009926 } // Literal.String.Regex .s1 { color: #d14 } // Literal.String.Single .ss { color: #990073 } // Literal.String.Symbol .bp { color: #999999 } // Name.Builtin.Pseudo .vc { color: #008080 } // Name.Variable.Class .vg { color: #008080 } // Name.Variable.Global .vi { color: #008080 } // Name.Variable.Instance .il { color: #009999 } // Literal.Number.Integer.Long } ================================================ FILE: lib/jazzy/themes/fullwidth/assets/css/jazzy.css.scss ================================================ /*! Jazzy - https://github.com/realm/jazzy * Copyright Realm Inc. * SPDX-License-Identifier: MIT */ // =========================================================================== // // Variables // // =========================================================================== $body_background: #fff; $body_font: 16px/1.7 'Helvetica Neue', Helvetica, Arial, sans-serif; $text_color: #333; $gray_border: 1px solid #ddd; $heading_weight: 700; $light_heading_color: #777; $quote_color: #858585; $quote_border: 4px solid #e5e5e5; $link_color: #4183c4; $table_alt_row_color: #fbfbfb; $table_border_color: #ddd; $code_bg_color: #f7f7f7; $code_font: Consolas, "Liberation Mono", Menlo, Courier, monospace; // ----- Layout $gutter: 16px; $navigation_max_width: 300px; // ----- Header $header_bg_color: #444; $header_link_color: #fff; $doc_coverage_color: #999; // ----- Breadcrumbs $breadcrumbs_bg_color: #fbfbfb; $breadcrumbs_border_color: #ddd; // ----- Navigation $navigation_max_width: 300px; $navigation_bg_color: #fbfbfb; $navigation_border_color: #ddd; $navigation_title_color: #333; $navigation_task_color: #808080; // ----- Content $declaration_title_language_color: #4183c4; $declaration_language_border: 5px solid #cde9f4; $declaration_bg_color: #fff; $declaration_border_color: #ddd; $aside_color: #aaa; $aside_border: 5px solid lighten($aside_color, 20%); $aside_warning_color: #ff0000; $aside_warning_border: 5px solid lighten($aside_warning_color, 20%); // ----- Footer $footer_bg_color: #444; $footer_text_color: #ddd; $footer_link_color: #fff; // =========================================================================== // // Base // // =========================================================================== *, *:before, *:after { box-sizing: inherit; } body { margin: 0; background: $body_background; color: $text_color; font: $body_font; letter-spacing: .2px; -webkit-font-smoothing: antialiased; box-sizing: border-box; } // ----- Block elements @mixin heading($font-size: 1rem, $margin: 1.275em 0 0.85em) { font-size: $font-size; font-weight: $heading_weight; margin: $margin; } h1 { @include heading(2rem, 1.275em 0 0.6em); } h2 { @include heading(1.75rem, 1.275em 0 0.3em); } h3 { @include heading(1.5rem, 1em 0 0.3em); } h4 { @include heading(1.25rem); } h5 { @include heading; } h6 { @include heading; color: $light_heading_color; } p { margin: 0 0 1em; } ul, ol { padding: 0 0 0 2em; margin: 0 0 0.85em; } blockquote { margin: 0 0 0.85em; padding: 0 15px; color: $quote_color; border-left: $quote_border; } // ----- Inline elements img { max-width: 100%; } a { color: $link_color; text-decoration: none; &:hover, &:focus { outline: 0; text-decoration: underline; } &.discouraged { text-decoration: line-through; &:hover, &:focus { text-decoration: underline line-through; } } } // ----- Tables table { background: $body_background; width: 100%; border-collapse: collapse; border-spacing: 0; overflow: auto; margin: 0 0 0.85em; } tr { &:nth-child(2n) { background-color: $table_alt_row_color; } } th, td { padding: 6px 13px; border: 1px solid $table_border_color; } hr { height: 1px; border: none; background-color: $table_border_color; } // ----- Code pre { margin: 0 0 1.275em; padding: .85em 1em; overflow: auto; background: $code_bg_color; font-size: .85em; font-family: $code_font; } code { font-family: $code_font; } .item-container, .top-matter { p, li { > code { background: $code_bg_color; padding: .2em; &:before, &:after { letter-spacing: -.2em; content: "\00a0"; } } } } pre code { padding: 0; white-space: pre; } // =========================================================================== // // Layout // // =========================================================================== .content-wrapper { display: flex; flex-direction: column; @media (min-width: 768px) { flex-direction: row; } } // =========================================================================== // // Header // // =========================================================================== .header { display: flex; padding: $gutter/2; font-size: 0.875em; background: $header_bg_color; color: $doc_coverage_color; } .header-col { margin: 0; padding: 0 $gutter/2 } .header-col--primary { flex: 1; } .header-link { color: $header_link_color; } .header-icon { padding-right: 2px; vertical-align: -3px; height: 16px; } // =========================================================================== // // Breadcrumbs // // =========================================================================== .breadcrumbs { font-size: 0.875em; padding: $gutter / 2 $gutter; margin: 0; background: $breadcrumbs_bg_color; border-bottom: 1px solid $breadcrumbs_border_color; } .carat { height: 10px; margin: 0 5px; } // =========================================================================== // // Navigation // // =========================================================================== .navigation { order: 2; @media (min-width: 768px) { order: 1; width: 25%; max-width: $navigation_max_width; padding-bottom: $gutter*4; overflow: hidden; word-wrap: normal; background: $navigation_bg_color; border-right: 1px solid $navigation_border_color; } } .nav-groups { list-style-type: none; padding-left: 0; } .nav-group-name { border-bottom: 1px solid $navigation_border_color; padding: $gutter/2 0 $gutter/2 $gutter; } .nav-group-name-link { color: $navigation_title_color; } .nav-group-tasks { margin: $gutter/2 0; padding: 0 0 0 $gutter/2; } .nav-group-task { font-size: 1em; list-style-type: none; white-space: nowrap; } .nav-group-task-link { color: $navigation_task_color; } // =========================================================================== // // Content // // =========================================================================== .main-content { order: 1; @media (min-width: 768px) { order: 2; flex: 1; padding-bottom: 60px; } } .section { padding: 0 $gutter * 2; border-bottom: 1px solid $navigation_border_color; } .section-content { max-width: 834px; margin: 0 auto; padding: $gutter 0; } .section-name { color: #666; display: block; p { margin-bottom: inherit; } } .declaration .highlight { overflow-x: initial; // This allows the scrollbar to show up inside declarations padding: $gutter/2 0; margin: 0; background-color: transparent; border: none; } .task-group-section { border-top: $gray_border; } .task-group { padding-top: 0px; } .task-name-container { a[name] { &:before { content: ""; display: block; } } } .section-name-container { position: relative; .section-name-link { position: absolute; top: 0; left: 0; bottom: 0; right: 0; margin-bottom: 0; } .section-name { position: relative; pointer-events: none; z-index: 1; a { pointer-events: auto; } } } .item-container { padding: 0; } .item { padding-top: 8px; width: 100%; list-style-type: none; a[name] { &:before { content: ""; display: block; } } .token, .direct-link { display: inline-block; text-indent: -20px; padding-left: 3px; margin-left: 20px; font-size: 1rem; } } .declaration-note { font-size: .85em; color: #808080; font-style: italic; } .pointer-container { border-bottom: $gray_border; left: -23px; padding-bottom: 13px; position: relative; width: 110%; } .pointer { left: 21px; top: 7px; display: block; position: absolute; width: 12px; height: 12px; border-left: 1px solid $declaration_border_color; border-top: 1px solid $declaration_border_color; background: $declaration_bg_color; transform: rotate(45deg); } .height-container { display: none; position: relative; width: 100%; overflow: hidden; .section { background: $declaration_bg_color; border: $gray_border; border-top-width: 0; padding-top: 10px; padding-bottom: 5px; padding: $gutter / 2 $gutter; } } .aside, .language { padding: 6px 12px; margin: 12px 0; border-left: $aside_border; overflow-y: hidden; .aside-title { font-size: 9px; letter-spacing: 2px; text-transform: uppercase; padding-bottom: 0; margin: 0; color: $aside_color; -webkit-user-select: none; } p:last-child { margin-bottom: 0; } } .language { border-left: $declaration_language_border; .aside-title { color: $declaration_title_language_color; } } .aside-warning, .aside-deprecated, .aside-unavailable { border-left: $aside_warning_border; .aside-title { color: $aside_warning_color; } } .graybox { border-collapse: collapse; width: 100%; p { margin: 0; word-break: break-word; min-width: 50px; } td { border: $gray_border; padding: 5px 25px 5px 10px; vertical-align: middle; } tr td:first-of-type { text-align: right; padding: 7px; vertical-align: top; word-break: normal; width: 40px; } } .slightly-smaller { font-size: 0.9em; } // =========================================================================== // // Footer // // =========================================================================== .footer { padding: $gutter/2 $gutter; background: $footer_bg_color; color: $footer_text_color; font-size: 0.8em; p { margin: $gutter/2 0; } a { color: $footer_link_color; } } // =========================================================================== // // Dash // // =========================================================================== html.dash { .header, .breadcrumbs, .navigation { display: none; } .height-container { display: block; } } // =========================================================================== // // Search // // =========================================================================== form[role=search] { input { font: $body_font; font-size: 14px; line-height: 24px; padding: 0 10px; margin: 0; border: none; border-radius: 1em; .loading & { background: white url(../img/spinner.gif) center right 4px no-repeat; } } // Typeahead elements .tt-menu { margin: 0; min-width: 300px; background: $navigation_bg_color; color: $text_color; border: 1px solid $navigation_border_color; } .tt-highlight { font-weight: bold; } .tt-suggestion { font: $body_font; padding: 0 $gutter/2; span { display: table-cell; white-space: nowrap; } .doc-parent-name { width: 100%; text-align: right; font-weight: normal; font-size: 0.9em; padding-left: $gutter; } } .tt-suggestion:hover, .tt-suggestion.tt-cursor { cursor: pointer; background-color: $link_color; color: #fff; } .tt-suggestion:hover .doc-parent-name, .tt-suggestion.tt-cursor .doc-parent-name { color: #fff; } } ================================================ FILE: lib/jazzy/themes/fullwidth/assets/js/jazzy.js ================================================ // Jazzy - https://github.com/realm/jazzy // Copyright Realm Inc. // SPDX-License-Identifier: MIT window.jazzy = {'docset': false} if (typeof window.dash != 'undefined') { document.documentElement.className += ' dash' window.jazzy.docset = true } if (navigator.userAgent.match(/xcode/i)) { document.documentElement.className += ' xcode' window.jazzy.docset = true } function toggleItem($link, $content) { var animationDuration = 300; $link.toggleClass('token-open'); $content.slideToggle(animationDuration); } function itemLinkToContent($link) { return $link.parent().parent().next(); } // On doc load + hash-change, open any targeted item function openCurrentItemIfClosed() { if (window.jazzy.docset) { return; } var $link = $(`a[name="${location.hash.substring(1)}"]`).nextAll('.token'); $content = itemLinkToContent($link); if ($content.is(':hidden')) { toggleItem($link, $content); } } $(openCurrentItemIfClosed); $(window).on('hashchange', openCurrentItemIfClosed); // On item link ('token') click, toggle its discussion $('.token').on('click', function(event) { if (window.jazzy.docset) { return; } var $link = $(this); toggleItem($link, itemLinkToContent($link)); // Keeps the document from jumping to the hash. var href = $link.attr('href'); if (history.pushState) { history.pushState({}, '', href); } else { location.hash = href; } event.preventDefault(); }); // Clicks on links to the current, closed, item need to open the item $("a:not('.token')").on('click', function() { if (location == this.href) { openCurrentItemIfClosed(); } }); // KaTeX rendering if ("katex" in window) { $($('.math').each( (_, element) => { katex.render(element.textContent, element, { displayMode: $(element).hasClass('m-block'), throwOnError: false, trust: true }); })) } ================================================ FILE: lib/jazzy/themes/fullwidth/assets/js/jazzy.search.js ================================================ // Jazzy - https://github.com/realm/jazzy // Copyright Realm Inc. // SPDX-License-Identifier: MIT $(function(){ var $typeahead = $('[data-typeahead]'); var $form = $typeahead.parents('form'); var searchURL = $form.attr('action'); function displayTemplate(result) { return result.name; } function suggestionTemplate(result) { var t = '
      '; t += '' + result.name + ''; if (result.parent_name) { t += '' + result.parent_name + ''; } t += '
      '; return t; } $typeahead.one('focus', function() { $form.addClass('loading'); $.getJSON(searchURL).then(function(searchData) { const searchIndex = lunr(function() { this.ref('url'); this.field('name'); this.field('abstract'); for (const [url, doc] of Object.entries(searchData)) { this.add({url: url, name: doc.name, abstract: doc.abstract}); } }); $typeahead.typeahead( { highlight: true, minLength: 3, autoselect: true }, { limit: 10, display: displayTemplate, templates: { suggestion: suggestionTemplate }, source: function(query, sync) { const lcSearch = query.toLowerCase(); const results = searchIndex.query(function(q) { q.term(lcSearch, { boost: 100 }); q.term(lcSearch, { boost: 10, wildcard: lunr.Query.wildcard.TRAILING }); }).map(function(result) { var doc = searchData[result.ref]; doc.url = result.ref; return doc; }); sync(results); } } ); $form.removeClass('loading'); $typeahead.trigger('focus'); }); }); var baseURL = searchURL.slice(0, -"search.json".length); $typeahead.on('typeahead:select', function(e, result) { window.location = baseURL + result.url; }); }); ================================================ FILE: lib/jazzy/themes/fullwidth/assets/js/typeahead.jquery.js ================================================ /*! * typeahead.js 1.3.3 * https://github.com/corejavascript/typeahead.js * Copyright 2013-2024 Twitter, Inc. and other contributors; Licensed MIT */ (function(root, factory) { if (typeof define === "function" && define.amd) { define([ "jquery" ], function(a0) { return factory(a0); }); } else if (typeof module === "object" && module.exports) { module.exports = factory(require("jquery")); } else { factory(root["jQuery"]); } })(this, function($) { var _ = function() { "use strict"; return { isMsie: function() { return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false; }, isBlankString: function(str) { return !str || /^\s*$/.test(str); }, escapeRegExChars: function(str) { return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); }, isString: function(obj) { return typeof obj === "string"; }, isNumber: function(obj) { return typeof obj === "number"; }, isArray: $.isArray, isFunction: $.isFunction, isObject: $.isPlainObject, isUndefined: function(obj) { return typeof obj === "undefined"; }, isElement: function(obj) { return !!(obj && obj.nodeType === 1); }, isJQuery: function(obj) { return obj instanceof $; }, toStr: function toStr(s) { return _.isUndefined(s) || s === null ? "" : s + ""; }, bind: $.proxy, each: function(collection, cb) { $.each(collection, reverseArgs); function reverseArgs(index, value) { return cb(value, index); } }, map: $.map, filter: $.grep, every: function(obj, test) { var result = true; if (!obj) { return result; } $.each(obj, function(key, val) { if (!(result = test.call(null, val, key, obj))) { return false; } }); return !!result; }, some: function(obj, test) { var result = false; if (!obj) { return result; } $.each(obj, function(key, val) { if (result = test.call(null, val, key, obj)) { return false; } }); return !!result; }, mixin: $.extend, identity: function(x) { return x; }, clone: function(obj) { return $.extend(true, {}, obj); }, getIdGenerator: function() { var counter = 0; return function() { return counter++; }; }, templatify: function templatify(obj) { return $.isFunction(obj) ? obj : template; function template() { return String(obj); } }, defer: function(fn) { setTimeout(fn, 0); }, debounce: function(func, wait, immediate) { var timeout, result; return function() { var context = this, args = arguments, later, callNow; later = function() { timeout = null; if (!immediate) { result = func.apply(context, args); } }; callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); if (callNow) { result = func.apply(context, args); } return result; }; }, throttle: function(func, wait) { var context, args, timeout, result, previous, later; previous = 0; later = function() { previous = new Date(); timeout = null; result = func.apply(context, args); }; return function() { var now = new Date(), remaining = wait - (now - previous); context = this; args = arguments; if (remaining <= 0) { clearTimeout(timeout); timeout = null; previous = now; result = func.apply(context, args); } else if (!timeout) { timeout = setTimeout(later, remaining); } return result; }; }, stringify: function(val) { return _.isString(val) ? val : JSON.stringify(val); }, guid: function() { function _p8(s) { var p = (Math.random().toString(16) + "000000000").substr(2, 8); return s ? "-" + p.substr(0, 4) + "-" + p.substr(4, 4) : p; } return "tt-" + _p8() + _p8(true) + _p8(true) + _p8(); }, noop: function() {} }; }(); var WWW = function() { "use strict"; var defaultClassNames = { wrapper: "twitter-typeahead", input: "tt-input", hint: "tt-hint", menu: "tt-menu", dataset: "tt-dataset", suggestion: "tt-suggestion", selectable: "tt-selectable", empty: "tt-empty", open: "tt-open", cursor: "tt-cursor", highlight: "tt-highlight" }; return build; function build(o) { var www, classes; classes = _.mixin({}, defaultClassNames, o); www = { css: buildCss(), classes: classes, html: buildHtml(classes), selectors: buildSelectors(classes) }; return { css: www.css, html: www.html, classes: www.classes, selectors: www.selectors, mixin: function(o) { _.mixin(o, www); } }; } function buildHtml(c) { return { wrapper: '', menu: '
      ' }; } function buildSelectors(classes) { var selectors = {}; _.each(classes, function(v, k) { selectors[k] = "." + v; }); return selectors; } function buildCss() { var css = { wrapper: { position: "relative", display: "inline-block" }, hint: { position: "absolute", top: "0", left: "0", borderColor: "transparent", boxShadow: "none", opacity: "1" }, input: { position: "relative", verticalAlign: "top", backgroundColor: "transparent" }, inputWithNoHint: { position: "relative", verticalAlign: "top" }, menu: { position: "absolute", top: "100%", left: "0", zIndex: "100", display: "none" }, ltr: { left: "0", right: "auto" }, rtl: { left: "auto", right: " 0" } }; if (_.isMsie()) { _.mixin(css.input, { backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)" }); } return css; } }(); var EventBus = function() { "use strict"; var namespace, deprecationMap; namespace = "typeahead:"; deprecationMap = { render: "rendered", cursorchange: "cursorchanged", select: "selected", autocomplete: "autocompleted" }; function EventBus(o) { if (!o || !o.el) { $.error("EventBus initialized without el"); } this.$el = $(o.el); } _.mixin(EventBus.prototype, { _trigger: function(type, args) { var $e = $.Event(namespace + type); this.$el.trigger.call(this.$el, $e, args || []); return $e; }, before: function(type) { var args, $e; args = [].slice.call(arguments, 1); $e = this._trigger("before" + type, args); return $e.isDefaultPrevented(); }, trigger: function(type) { var deprecatedType; this._trigger(type, [].slice.call(arguments, 1)); if (deprecatedType = deprecationMap[type]) { this._trigger(deprecatedType, [].slice.call(arguments, 1)); } } }); return EventBus; }(); var EventEmitter = function() { "use strict"; var splitter = /\s+/, nextTick = getNextTick(); return { onSync: onSync, onAsync: onAsync, off: off, trigger: trigger }; function on(method, types, cb, context) { var type; if (!cb) { return this; } types = types.split(splitter); cb = context ? bindContext(cb, context) : cb; this._callbacks = this._callbacks || {}; while (type = types.shift()) { this._callbacks[type] = this._callbacks[type] || { sync: [], async: [] }; this._callbacks[type][method].push(cb); } return this; } function onAsync(types, cb, context) { return on.call(this, "async", types, cb, context); } function onSync(types, cb, context) { return on.call(this, "sync", types, cb, context); } function off(types) { var type; if (!this._callbacks) { return this; } types = types.split(splitter); while (type = types.shift()) { delete this._callbacks[type]; } return this; } function trigger(types) { var type, callbacks, args, syncFlush, asyncFlush; if (!this._callbacks) { return this; } types = types.split(splitter); args = [].slice.call(arguments, 1); while ((type = types.shift()) && (callbacks = this._callbacks[type])) { syncFlush = getFlush(callbacks.sync, this, [ type ].concat(args)); asyncFlush = getFlush(callbacks.async, this, [ type ].concat(args)); syncFlush() && nextTick(asyncFlush); } return this; } function getFlush(callbacks, context, args) { return flush; function flush() { var cancelled; for (var i = 0, len = callbacks.length; !cancelled && i < len; i += 1) { cancelled = callbacks[i].apply(context, args) === false; } return !cancelled; } } function getNextTick() { var nextTickFn; if (window.setImmediate) { nextTickFn = function nextTickSetImmediate(fn) { setImmediate(function() { fn(); }); }; } else { nextTickFn = function nextTickSetTimeout(fn) { setTimeout(function() { fn(); }, 0); }; } return nextTickFn; } function bindContext(fn, context) { return fn.bind ? fn.bind(context) : function() { fn.apply(context, [].slice.call(arguments, 0)); }; } }(); var highlight = function(doc) { "use strict"; var defaults = { node: null, pattern: null, tagName: "strong", className: null, wordsOnly: false, caseSensitive: false, diacriticInsensitive: false }; var accented = { A: "[AaªÀ-Åà-åĀ-ąǍǎȀ-ȃȦȧᴬᵃḀḁẚẠ-ảₐ℀℁℻⒜Ⓐⓐ㍱-㍴㎀-㎄㎈㎉㎩-㎯㏂㏊㏟㏿Aa]", B: "[BbᴮᵇḂ-ḇℬ⒝Ⓑⓑ㍴㎅-㎇㏃㏈㏔㏝Bb]", C: "[CcÇçĆ-čᶜ℀ℂ℃℅℆ℭⅭⅽ⒞Ⓒⓒ㍶㎈㎉㎝㎠㎤㏄-㏇Cc]", D: "[DdĎďDŽ-džDZ-dzᴰᵈḊ-ḓⅅⅆⅮⅾ⒟Ⓓⓓ㋏㍲㍷-㍹㎗㎭-㎯㏅㏈Dd]", E: "[EeÈ-Ëè-ëĒ-ěȄ-ȇȨȩᴱᵉḘ-ḛẸ-ẽₑ℡ℯℰⅇ⒠Ⓔⓔ㉐㋍㋎Ee]", F: "[FfᶠḞḟ℉ℱ℻⒡Ⓕⓕ㎊-㎌㎙ff-fflFf]", G: "[GgĜ-ģǦǧǴǵᴳᵍḠḡℊ⒢Ⓖⓖ㋌㋍㎇㎍-㎏㎓㎬㏆㏉㏒㏿Gg]", H: "[HhĤĥȞȟʰᴴḢ-ḫẖℋ-ℎ⒣Ⓗⓗ㋌㍱㎐-㎔㏊㏋㏗Hh]", I: "[IiÌ-Ïì-ïĨ-İIJijǏǐȈ-ȋᴵᵢḬḭỈ-ịⁱℐℑℹⅈⅠ-ⅣⅥ-ⅨⅪⅫⅰ-ⅳⅵ-ⅸⅺⅻ⒤Ⓘⓘ㍺㏌㏕fiffiIi]", J: "[JjIJ-ĵLJ-njǰʲᴶⅉ⒥ⒿⓙⱼJj]", K: "[KkĶķǨǩᴷᵏḰ-ḵK⒦Ⓚⓚ㎄㎅㎉㎏㎑㎘㎞㎢㎦㎪㎸㎾㏀㏆㏍-㏏Kk]", L: "[LlĹ-ŀLJ-ljˡᴸḶḷḺ-ḽℒℓ℡Ⅼⅼ⒧Ⓛⓛ㋏㎈㎉㏐-㏓㏕㏖㏿flfflLl]", M: "[MmᴹᵐḾ-ṃ℠™ℳⅯⅿ⒨Ⓜⓜ㍷-㍹㎃㎆㎎㎒㎖㎙-㎨㎫㎳㎷㎹㎽㎿㏁㏂㏎㏐㏔-㏖㏘㏙㏞㏟Mm]", N: "[NnÑñŃ-ʼnNJ-njǸǹᴺṄ-ṋⁿℕ№⒩Ⓝⓝ㎁㎋㎚㎱㎵㎻㏌㏑Nn]", O: "[OoºÒ-Öò-öŌ-őƠơǑǒǪǫȌ-ȏȮȯᴼᵒỌ-ỏₒ℅№ℴ⒪Ⓞⓞ㍵㏇㏒㏖Oo]", P: "[PpᴾᵖṔ-ṗℙ⒫Ⓟⓟ㉐㍱㍶㎀㎊㎩-㎬㎰㎴㎺㏋㏗-㏚Pp]", Q: "[Qqℚ⒬Ⓠⓠ㏃Qq]", R: "[RrŔ-řȐ-ȓʳᴿᵣṘ-ṛṞṟ₨ℛ-ℝ⒭Ⓡⓡ㋍㍴㎭-㎯㏚㏛Rr]", S: "[SsŚ-šſȘșˢṠ-ṣ₨℁℠⒮Ⓢⓢ㎧㎨㎮-㎳㏛㏜stSs]", T: "[TtŢ-ťȚțᵀᵗṪ-ṱẗ℡™⒯Ⓣⓣ㉐㋏㎔㏏ſtstTt]", U: "[UuÙ-Üù-üŨ-ųƯưǓǔȔ-ȗᵁᵘᵤṲ-ṷỤ-ủ℆⒰Ⓤⓤ㍳㍺Uu]", V: "[VvᵛᵥṼ-ṿⅣ-Ⅷⅳ-ⅷ⒱Ⓥⓥⱽ㋎㍵㎴-㎹㏜㏞Vv]", W: "[WwŴŵʷᵂẀ-ẉẘ⒲Ⓦⓦ㎺-㎿㏝Ww]", X: "[XxˣẊ-ẍₓ℻Ⅸ-Ⅻⅸ-ⅻ⒳Ⓧⓧ㏓Xx]", Y: "[YyÝýÿŶ-ŸȲȳʸẎẏẙỲ-ỹ⒴Ⓨⓨ㏉Yy]", Z: "[ZzŹ-žDZ-dzᶻẐ-ẕℤℨ⒵Ⓩⓩ㎐-㎔Zz]" }; return function hightlight(o) { var regex; o = _.mixin({}, defaults, o); if (!o.node || !o.pattern) { return; } o.pattern = _.isArray(o.pattern) ? o.pattern : [ o.pattern ]; regex = getRegex(o.pattern, o.caseSensitive, o.wordsOnly, o.diacriticInsensitive); traverse(o.node, hightlightTextNode); function hightlightTextNode(textNode) { var match, patternNode, wrapperNode; if (match = regex.exec(textNode.data)) { wrapperNode = doc.createElement(o.tagName); o.className && (wrapperNode.className = o.className); patternNode = textNode.splitText(match.index); patternNode.splitText(match[0].length); wrapperNode.appendChild(patternNode.cloneNode(true)); textNode.parentNode.replaceChild(wrapperNode, patternNode); } return !!match; } function traverse(el, hightlightTextNode) { var childNode, TEXT_NODE_TYPE = 3; for (var i = 0; i < el.childNodes.length; i++) { childNode = el.childNodes[i]; if (childNode.nodeType === TEXT_NODE_TYPE) { i += hightlightTextNode(childNode) ? 1 : 0; } else { traverse(childNode, hightlightTextNode); } } } }; function accent_replacer(chr) { return accented[chr.toUpperCase()] || chr; } function getRegex(patterns, caseSensitive, wordsOnly, diacriticInsensitive) { var escapedPatterns = [], regexStr; for (var i = 0, len = patterns.length; i < len; i++) { var escapedWord = _.escapeRegExChars(patterns[i]); if (diacriticInsensitive) { escapedWord = escapedWord.replace(/\S/g, accent_replacer); } escapedPatterns.push(escapedWord); } regexStr = wordsOnly ? "\\b(" + escapedPatterns.join("|") + ")\\b" : "(" + escapedPatterns.join("|") + ")"; return caseSensitive ? new RegExp(regexStr) : new RegExp(regexStr, "i"); } }(window.document); var Input = function() { "use strict"; var specialKeyCodeMap; specialKeyCodeMap = { 9: "tab", 27: "esc", 37: "left", 39: "right", 13: "enter", 38: "up", 40: "down" }; function Input(o, www) { var id; o = o || {}; if (!o.input) { $.error("input is missing"); } www.mixin(this); this.$hint = $(o.hint); this.$input = $(o.input); this.$menu = $(o.menu); id = this.$input.attr("id") || _.guid(); this.$menu.attr("id", id + "_listbox"); this.$hint.attr({ "aria-hidden": true }); this.$input.attr({ "aria-owns": id + "_listbox", "aria-controls": id + "_listbox", role: "combobox", "aria-autocomplete": "list", "aria-expanded": false }); this.query = this.$input.val(); this.queryWhenFocused = this.hasFocus() ? this.query : null; this.$overflowHelper = buildOverflowHelper(this.$input); this._checkLanguageDirection(); if (this.$hint.length === 0) { this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = _.noop; } this.onSync("cursorchange", this._updateDescendent); } Input.normalizeQuery = function(str) { return _.toStr(str).replace(/^\s*/g, "").replace(/\s{2,}/g, " "); }; _.mixin(Input.prototype, EventEmitter, { _onBlur: function onBlur() { this.resetInputValue(); this.trigger("blurred"); }, _onFocus: function onFocus() { this.queryWhenFocused = this.query; this.trigger("focused"); }, _onKeydown: function onKeydown($e) { var keyName = specialKeyCodeMap[$e.which || $e.keyCode]; this._managePreventDefault(keyName, $e); if (keyName && this._shouldTrigger(keyName, $e)) { this.trigger(keyName + "Keyed", $e); } }, _onInput: function onInput() { this._setQuery(this.getInputValue()); this.clearHintIfInvalid(); this._checkLanguageDirection(); }, _managePreventDefault: function managePreventDefault(keyName, $e) { var preventDefault; switch (keyName) { case "up": case "down": preventDefault = !withModifier($e); break; default: preventDefault = false; } preventDefault && $e.preventDefault(); }, _shouldTrigger: function shouldTrigger(keyName, $e) { var trigger; switch (keyName) { case "tab": trigger = !withModifier($e); break; default: trigger = true; } return trigger; }, _checkLanguageDirection: function checkLanguageDirection() { var dir = (this.$input.css("direction") || "ltr").toLowerCase(); if (this.dir !== dir) { this.dir = dir; this.$hint.attr("dir", dir); this.trigger("langDirChanged", dir); } }, _setQuery: function setQuery(val, silent) { var areEquivalent, hasDifferentWhitespace; areEquivalent = areQueriesEquivalent(val, this.query); hasDifferentWhitespace = areEquivalent ? this.query.length !== val.length : false; this.query = val; if (!silent && !areEquivalent) { this.trigger("queryChanged", this.query); } else if (!silent && hasDifferentWhitespace) { this.trigger("whitespaceChanged", this.query); } }, _updateDescendent: function updateDescendent(event, id) { this.$input.attr("aria-activedescendant", id); }, bind: function() { var that = this, onBlur, onFocus, onKeydown, onInput; onBlur = _.bind(this._onBlur, this); onFocus = _.bind(this._onFocus, this); onKeydown = _.bind(this._onKeydown, this); onInput = _.bind(this._onInput, this); this.$input.on("blur.tt", onBlur).on("focus.tt", onFocus).on("keydown.tt", onKeydown); if (!_.isMsie() || _.isMsie() > 9) { this.$input.on("input.tt", onInput); } else { this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) { if (specialKeyCodeMap[$e.which || $e.keyCode]) { return; } _.defer(_.bind(that._onInput, that, $e)); }); } return this; }, focus: function focus() { this.$input.focus(); }, blur: function blur() { this.$input.blur(); }, getLangDir: function getLangDir() { return this.dir; }, getQuery: function getQuery() { return this.query || ""; }, setQuery: function setQuery(val, silent) { this.setInputValue(val); this._setQuery(val, silent); }, hasQueryChangedSinceLastFocus: function hasQueryChangedSinceLastFocus() { return this.query !== this.queryWhenFocused; }, getInputValue: function getInputValue() { return this.$input.val(); }, setInputValue: function setInputValue(value) { this.$input.val(value); this.clearHintIfInvalid(); this._checkLanguageDirection(); }, resetInputValue: function resetInputValue() { this.setInputValue(this.query); }, getHint: function getHint() { return this.$hint.val(); }, setHint: function setHint(value) { this.$hint.val(value); }, clearHint: function clearHint() { this.setHint(""); }, clearHintIfInvalid: function clearHintIfInvalid() { var val, hint, valIsPrefixOfHint, isValid; val = this.getInputValue(); hint = this.getHint(); valIsPrefixOfHint = val !== hint && hint.indexOf(val) === 0; isValid = val !== "" && valIsPrefixOfHint && !this.hasOverflow(); !isValid && this.clearHint(); }, hasFocus: function hasFocus() { return this.$input.is(":focus"); }, hasOverflow: function hasOverflow() { var constraint = this.$input.width() - 2; this.$overflowHelper.text(this.getInputValue()); return this.$overflowHelper.width() >= constraint; }, isCursorAtEnd: function() { var valueLength, selectionStart, range; valueLength = this.$input.val().length; selectionStart = this.$input[0].selectionStart; if (_.isNumber(selectionStart)) { return selectionStart === valueLength; } else if (document.selection) { range = document.selection.createRange(); range.moveStart("character", -valueLength); return valueLength === range.text.length; } return true; }, destroy: function destroy() { this.$hint.off(".tt"); this.$input.off(".tt"); this.$overflowHelper.remove(); this.$hint = this.$input = this.$overflowHelper = $("
      "); }, setAriaExpanded: function setAriaExpanded(value) { this.$input.attr("aria-expanded", value); } }); return Input; function buildOverflowHelper($input) { return $('').css({ position: "absolute", visibility: "hidden", whiteSpace: "pre", fontFamily: $input.css("font-family"), fontSize: $input.css("font-size"), fontStyle: $input.css("font-style"), fontVariant: $input.css("font-variant"), fontWeight: $input.css("font-weight"), wordSpacing: $input.css("word-spacing"), letterSpacing: $input.css("letter-spacing"), textIndent: $input.css("text-indent"), textRendering: $input.css("text-rendering"), textTransform: $input.css("text-transform") }).insertAfter($input); } function areQueriesEquivalent(a, b) { return Input.normalizeQuery(a) === Input.normalizeQuery(b); } function withModifier($e) { return $e.altKey || $e.ctrlKey || $e.metaKey || $e.shiftKey; } }(); var Dataset = function() { "use strict"; var keys, nameGenerator; keys = { dataset: "tt-selectable-dataset", val: "tt-selectable-display", obj: "tt-selectable-object" }; nameGenerator = _.getIdGenerator(); function Dataset(o, www) { o = o || {}; o.templates = o.templates || {}; o.templates.notFound = o.templates.notFound || o.templates.empty; if (!o.source) { $.error("missing source"); } if (!o.node) { $.error("missing node"); } if (o.name && !isValidName(o.name)) { $.error("invalid dataset name: " + o.name); } www.mixin(this); this.highlight = !!o.highlight; this.name = _.toStr(o.name || nameGenerator()); this.limit = o.limit || 5; this.displayFn = getDisplayFn(o.display || o.displayKey); this.templates = getTemplates(o.templates, this.displayFn); this.source = o.source.__ttAdapter ? o.source.__ttAdapter() : o.source; this.async = _.isUndefined(o.async) ? this.source.length > 2 : !!o.async; this._resetLastSuggestion(); this.$el = $(o.node).attr("role", "presentation").addClass(this.classes.dataset).addClass(this.classes.dataset + "-" + this.name); } Dataset.extractData = function extractData(el) { var $el = $(el); if ($el.data(keys.obj)) { return { dataset: $el.data(keys.dataset) || "", val: $el.data(keys.val) || "", obj: $el.data(keys.obj) || null }; } return null; }; _.mixin(Dataset.prototype, EventEmitter, { _overwrite: function overwrite(query, suggestions) { suggestions = suggestions || []; if (suggestions.length) { this._renderSuggestions(query, suggestions); } else if (this.async && this.templates.pending) { this._renderPending(query); } else if (!this.async && this.templates.notFound) { this._renderNotFound(query); } else { this._empty(); } this.trigger("rendered", suggestions, false, this.name); }, _append: function append(query, suggestions) { suggestions = suggestions || []; if (suggestions.length && this.$lastSuggestion.length) { this._appendSuggestions(query, suggestions); } else if (suggestions.length) { this._renderSuggestions(query, suggestions); } else if (!this.$lastSuggestion.length && this.templates.notFound) { this._renderNotFound(query); } this.trigger("rendered", suggestions, true, this.name); }, _renderSuggestions: function renderSuggestions(query, suggestions) { var $fragment; $fragment = this._getSuggestionsFragment(query, suggestions); this.$lastSuggestion = $fragment.children().last(); this.$el.html($fragment).prepend(this._getHeader(query, suggestions)).append(this._getFooter(query, suggestions)); }, _appendSuggestions: function appendSuggestions(query, suggestions) { var $fragment, $lastSuggestion; $fragment = this._getSuggestionsFragment(query, suggestions); $lastSuggestion = $fragment.children().last(); this.$lastSuggestion.after($fragment); this.$lastSuggestion = $lastSuggestion; }, _renderPending: function renderPending(query) { var template = this.templates.pending; this._resetLastSuggestion(); template && this.$el.html(template({ query: query, dataset: this.name })); }, _renderNotFound: function renderNotFound(query) { var template = this.templates.notFound; this._resetLastSuggestion(); template && this.$el.html(template({ query: query, dataset: this.name })); }, _empty: function empty() { this.$el.empty(); this._resetLastSuggestion(); }, _getSuggestionsFragment: function getSuggestionsFragment(query, suggestions) { var that = this, fragment; fragment = document.createDocumentFragment(); _.each(suggestions, function getSuggestionNode(suggestion) { var $el, context; context = that._injectQuery(query, suggestion); $el = $(that.templates.suggestion(context)).data(keys.dataset, that.name).data(keys.obj, suggestion).data(keys.val, that.displayFn(suggestion)).addClass(that.classes.suggestion + " " + that.classes.selectable); fragment.appendChild($el[0]); }); this.highlight && highlight({ className: this.classes.highlight, node: fragment, pattern: query }); return $(fragment); }, _getFooter: function getFooter(query, suggestions) { return this.templates.footer ? this.templates.footer({ query: query, suggestions: suggestions, dataset: this.name }) : null; }, _getHeader: function getHeader(query, suggestions) { return this.templates.header ? this.templates.header({ query: query, suggestions: suggestions, dataset: this.name }) : null; }, _resetLastSuggestion: function resetLastSuggestion() { this.$lastSuggestion = $(); }, _injectQuery: function injectQuery(query, obj) { return _.isObject(obj) ? _.mixin({ _query: query }, obj) : obj; }, update: function update(query) { var that = this, canceled = false, syncCalled = false, rendered = 0; this.cancel(); this.cancel = function cancel() { canceled = true; that.cancel = $.noop; that.async && that.trigger("asyncCanceled", query, that.name); }; this.source(query, sync, async); !syncCalled && sync([]); function sync(suggestions) { if (syncCalled) { return; } syncCalled = true; suggestions = (suggestions || []).slice(0, that.limit); rendered = suggestions.length; that._overwrite(query, suggestions); if (rendered < that.limit && that.async) { that.trigger("asyncRequested", query, that.name); } } function async(suggestions) { suggestions = suggestions || []; if (!canceled && rendered < that.limit) { that.cancel = $.noop; var idx = Math.abs(rendered - that.limit); rendered += idx; that._append(query, suggestions.slice(0, idx)); that.async && that.trigger("asyncReceived", query, that.name); } } }, cancel: $.noop, clear: function clear() { this._empty(); this.cancel(); this.trigger("cleared"); }, isEmpty: function isEmpty() { return this.$el.is(":empty"); }, destroy: function destroy() { this.$el = $("
      "); } }); return Dataset; function getDisplayFn(display) { display = display || _.stringify; return _.isFunction(display) ? display : displayFn; function displayFn(obj) { return obj[display]; } } function getTemplates(templates, displayFn) { return { notFound: templates.notFound && _.templatify(templates.notFound), pending: templates.pending && _.templatify(templates.pending), header: templates.header && _.templatify(templates.header), footer: templates.footer && _.templatify(templates.footer), suggestion: templates.suggestion ? userSuggestionTemplate : suggestionTemplate }; function userSuggestionTemplate(context) { var template = templates.suggestion; return $(template(context)).attr("id", _.guid()); } function suggestionTemplate(context) { return $('
      ').attr("id", _.guid()).text(displayFn(context)); } } function isValidName(str) { return /^[_a-zA-Z0-9-]+$/.test(str); } }(); var Menu = function() { "use strict"; function Menu(o, www) { var that = this; o = o || {}; if (!o.node) { $.error("node is required"); } www.mixin(this); this.$node = $(o.node); this.query = null; this.datasets = _.map(o.datasets, initializeDataset); function initializeDataset(oDataset) { var node = that.$node.find(oDataset.node).first(); oDataset.node = node.length ? node : $("
      ").appendTo(that.$node); return new Dataset(oDataset, www); } } _.mixin(Menu.prototype, EventEmitter, { _onSelectableClick: function onSelectableClick($e) { this.trigger("selectableClicked", $($e.currentTarget)); }, _onRendered: function onRendered(type, dataset, suggestions, async) { this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty()); this.trigger("datasetRendered", dataset, suggestions, async); }, _onCleared: function onCleared() { this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty()); this.trigger("datasetCleared"); }, _propagate: function propagate() { this.trigger.apply(this, arguments); }, _allDatasetsEmpty: function allDatasetsEmpty() { return _.every(this.datasets, _.bind(function isDatasetEmpty(dataset) { var isEmpty = dataset.isEmpty(); this.$node.attr("aria-expanded", !isEmpty); return isEmpty; }, this)); }, _getSelectables: function getSelectables() { return this.$node.find(this.selectors.selectable); }, _removeCursor: function _removeCursor() { var $selectable = this.getActiveSelectable(); $selectable && $selectable.removeClass(this.classes.cursor); }, _ensureVisible: function ensureVisible($el) { var elTop, elBottom, nodeScrollTop, nodeHeight; elTop = $el.position().top; elBottom = elTop + $el.outerHeight(true); nodeScrollTop = this.$node.scrollTop(); nodeHeight = this.$node.height() + parseInt(this.$node.css("paddingTop"), 10) + parseInt(this.$node.css("paddingBottom"), 10); if (elTop < 0) { this.$node.scrollTop(nodeScrollTop + elTop); } else if (nodeHeight < elBottom) { this.$node.scrollTop(nodeScrollTop + (elBottom - nodeHeight)); } }, bind: function() { var that = this, onSelectableClick; onSelectableClick = _.bind(this._onSelectableClick, this); this.$node.on("click.tt", this.selectors.selectable, onSelectableClick); this.$node.on("mouseover", this.selectors.selectable, function() { that.setCursor($(this)); }); this.$node.on("mouseleave", function() { that._removeCursor(); }); _.each(this.datasets, function(dataset) { dataset.onSync("asyncRequested", that._propagate, that).onSync("asyncCanceled", that._propagate, that).onSync("asyncReceived", that._propagate, that).onSync("rendered", that._onRendered, that).onSync("cleared", that._onCleared, that); }); return this; }, isOpen: function isOpen() { return this.$node.hasClass(this.classes.open); }, open: function open() { this.$node.scrollTop(0); this.$node.addClass(this.classes.open); }, close: function close() { this.$node.attr("aria-expanded", false); this.$node.removeClass(this.classes.open); this._removeCursor(); }, setLanguageDirection: function setLanguageDirection(dir) { this.$node.attr("dir", dir); }, selectableRelativeToCursor: function selectableRelativeToCursor(delta) { var $selectables, $oldCursor, oldIndex, newIndex; $oldCursor = this.getActiveSelectable(); $selectables = this._getSelectables(); oldIndex = $oldCursor ? $selectables.index($oldCursor) : -1; newIndex = oldIndex + delta; newIndex = (newIndex + 1) % ($selectables.length + 1) - 1; newIndex = newIndex < -1 ? $selectables.length - 1 : newIndex; return newIndex === -1 ? null : $selectables.eq(newIndex); }, setCursor: function setCursor($selectable) { this._removeCursor(); if ($selectable = $selectable && $selectable.first()) { $selectable.addClass(this.classes.cursor); this._ensureVisible($selectable); } }, getSelectableData: function getSelectableData($el) { return $el && $el.length ? Dataset.extractData($el) : null; }, getActiveSelectable: function getActiveSelectable() { var $selectable = this._getSelectables().filter(this.selectors.cursor).first(); return $selectable.length ? $selectable : null; }, getTopSelectable: function getTopSelectable() { var $selectable = this._getSelectables().first(); return $selectable.length ? $selectable : null; }, update: function update(query) { var isValidUpdate = query !== this.query; if (isValidUpdate) { this.query = query; _.each(this.datasets, updateDataset); } return isValidUpdate; function updateDataset(dataset) { dataset.update(query); } }, empty: function empty() { _.each(this.datasets, clearDataset); this.query = null; this.$node.addClass(this.classes.empty); function clearDataset(dataset) { dataset.clear(); } }, destroy: function destroy() { this.$node.off(".tt"); this.$node = $("
      "); _.each(this.datasets, destroyDataset); function destroyDataset(dataset) { dataset.destroy(); } } }); return Menu; }(); var Status = function() { "use strict"; function Status(options) { this.$el = $("", { role: "status", "aria-live": "polite" }).css({ position: "absolute", padding: "0", border: "0", height: "1px", width: "1px", "margin-bottom": "-1px", "margin-right": "-1px", overflow: "hidden", clip: "rect(0 0 0 0)", "white-space": "nowrap" }); options.$input.after(this.$el); _.each(options.menu.datasets, _.bind(function(dataset) { if (dataset.onSync) { dataset.onSync("rendered", _.bind(this.update, this)); dataset.onSync("cleared", _.bind(this.cleared, this)); } }, this)); } _.mixin(Status.prototype, { update: function update(event, suggestions) { var length = suggestions.length; var words; if (length === 1) { words = { result: "result", is: "is" }; } else { words = { result: "results", is: "are" }; } this.$el.text(length + " " + words.result + " " + words.is + " available, use up and down arrow keys to navigate."); }, cleared: function() { this.$el.text(""); } }); return Status; }(); var DefaultMenu = function() { "use strict"; var s = Menu.prototype; function DefaultMenu() { Menu.apply(this, [].slice.call(arguments, 0)); } _.mixin(DefaultMenu.prototype, Menu.prototype, { open: function open() { !this._allDatasetsEmpty() && this._show(); return s.open.apply(this, [].slice.call(arguments, 0)); }, close: function close() { this._hide(); return s.close.apply(this, [].slice.call(arguments, 0)); }, _onRendered: function onRendered() { if (this._allDatasetsEmpty()) { this._hide(); } else { this.isOpen() && this._show(); } return s._onRendered.apply(this, [].slice.call(arguments, 0)); }, _onCleared: function onCleared() { if (this._allDatasetsEmpty()) { this._hide(); } else { this.isOpen() && this._show(); } return s._onCleared.apply(this, [].slice.call(arguments, 0)); }, setLanguageDirection: function setLanguageDirection(dir) { this.$node.css(dir === "ltr" ? this.css.ltr : this.css.rtl); return s.setLanguageDirection.apply(this, [].slice.call(arguments, 0)); }, _hide: function hide() { this.$node.hide(); }, _show: function show() { this.$node.css("display", "block"); } }); return DefaultMenu; }(); var Typeahead = function() { "use strict"; function Typeahead(o, www) { var onFocused, onBlurred, onEnterKeyed, onTabKeyed, onEscKeyed, onUpKeyed, onDownKeyed, onLeftKeyed, onRightKeyed, onQueryChanged, onWhitespaceChanged; o = o || {}; if (!o.input) { $.error("missing input"); } if (!o.menu) { $.error("missing menu"); } if (!o.eventBus) { $.error("missing event bus"); } www.mixin(this); this.eventBus = o.eventBus; this.minLength = _.isNumber(o.minLength) ? o.minLength : 1; this.input = o.input; this.menu = o.menu; this.enabled = true; this.autoselect = !!o.autoselect; this.active = false; this.input.hasFocus() && this.activate(); this.dir = this.input.getLangDir(); this._hacks(); this.menu.bind().onSync("selectableClicked", this._onSelectableClicked, this).onSync("asyncRequested", this._onAsyncRequested, this).onSync("asyncCanceled", this._onAsyncCanceled, this).onSync("asyncReceived", this._onAsyncReceived, this).onSync("datasetRendered", this._onDatasetRendered, this).onSync("datasetCleared", this._onDatasetCleared, this); onFocused = c(this, "activate", "open", "_onFocused"); onBlurred = c(this, "deactivate", "_onBlurred"); onEnterKeyed = c(this, "isActive", "isOpen", "_onEnterKeyed"); onTabKeyed = c(this, "isActive", "isOpen", "_onTabKeyed"); onEscKeyed = c(this, "isActive", "_onEscKeyed"); onUpKeyed = c(this, "isActive", "open", "_onUpKeyed"); onDownKeyed = c(this, "isActive", "open", "_onDownKeyed"); onLeftKeyed = c(this, "isActive", "isOpen", "_onLeftKeyed"); onRightKeyed = c(this, "isActive", "isOpen", "_onRightKeyed"); onQueryChanged = c(this, "_openIfActive", "_onQueryChanged"); onWhitespaceChanged = c(this, "_openIfActive", "_onWhitespaceChanged"); this.input.bind().onSync("focused", onFocused, this).onSync("blurred", onBlurred, this).onSync("enterKeyed", onEnterKeyed, this).onSync("tabKeyed", onTabKeyed, this).onSync("escKeyed", onEscKeyed, this).onSync("upKeyed", onUpKeyed, this).onSync("downKeyed", onDownKeyed, this).onSync("leftKeyed", onLeftKeyed, this).onSync("rightKeyed", onRightKeyed, this).onSync("queryChanged", onQueryChanged, this).onSync("whitespaceChanged", onWhitespaceChanged, this).onSync("langDirChanged", this._onLangDirChanged, this); } _.mixin(Typeahead.prototype, { _hacks: function hacks() { var $input, $menu; $input = this.input.$input || $("
      "); $menu = this.menu.$node || $("
      "); $input.on("blur.tt", function($e) { var active, isActive, hasActive; active = document.activeElement; isActive = $menu.is(active); hasActive = $menu.has(active).length > 0; if (_.isMsie() && (isActive || hasActive)) { $e.preventDefault(); $e.stopImmediatePropagation(); _.defer(function() { $input.focus(); }); } }); $menu.on("mousedown.tt", function($e) { $e.preventDefault(); }); }, _onSelectableClicked: function onSelectableClicked(type, $el) { this.select($el); }, _onDatasetCleared: function onDatasetCleared() { this._updateHint(); }, _onDatasetRendered: function onDatasetRendered(type, suggestions, async, dataset) { this._updateHint(); if (this.autoselect) { var cursorClass = this.selectors.cursor.substr(1); this.menu.$node.find(this.selectors.suggestion).first().addClass(cursorClass); } this.eventBus.trigger("render", suggestions, async, dataset); }, _onAsyncRequested: function onAsyncRequested(type, dataset, query) { this.eventBus.trigger("asyncrequest", query, dataset); }, _onAsyncCanceled: function onAsyncCanceled(type, dataset, query) { this.eventBus.trigger("asynccancel", query, dataset); }, _onAsyncReceived: function onAsyncReceived(type, dataset, query) { this.eventBus.trigger("asyncreceive", query, dataset); }, _onFocused: function onFocused() { this._minLengthMet() && this.menu.update(this.input.getQuery()); }, _onBlurred: function onBlurred() { if (this.input.hasQueryChangedSinceLastFocus()) { this.eventBus.trigger("change", this.input.getQuery()); } }, _onEnterKeyed: function onEnterKeyed(type, $e) { var $selectable; if ($selectable = this.menu.getActiveSelectable()) { if (this.select($selectable)) { $e.preventDefault(); $e.stopPropagation(); } } else if (this.autoselect) { if (this.select(this.menu.getTopSelectable())) { $e.preventDefault(); $e.stopPropagation(); } } }, _onTabKeyed: function onTabKeyed(type, $e) { var $selectable; if ($selectable = this.menu.getActiveSelectable()) { this.select($selectable) && $e.preventDefault(); } else if (this.autoselect) { if ($selectable = this.menu.getTopSelectable()) { this.autocomplete($selectable) && $e.preventDefault(); } } }, _onEscKeyed: function onEscKeyed() { this.close(); }, _onUpKeyed: function onUpKeyed() { this.moveCursor(-1); }, _onDownKeyed: function onDownKeyed() { this.moveCursor(+1); }, _onLeftKeyed: function onLeftKeyed() { if (this.dir === "rtl" && this.input.isCursorAtEnd()) { this.autocomplete(this.menu.getActiveSelectable() || this.menu.getTopSelectable()); } }, _onRightKeyed: function onRightKeyed() { if (this.dir === "ltr" && this.input.isCursorAtEnd()) { this.autocomplete(this.menu.getActiveSelectable() || this.menu.getTopSelectable()); } }, _onQueryChanged: function onQueryChanged(e, query) { this._minLengthMet(query) ? this.menu.update(query) : this.menu.empty(); }, _onWhitespaceChanged: function onWhitespaceChanged() { this._updateHint(); }, _onLangDirChanged: function onLangDirChanged(e, dir) { if (this.dir !== dir) { this.dir = dir; this.menu.setLanguageDirection(dir); } }, _openIfActive: function openIfActive() { this.isActive() && this.open(); }, _minLengthMet: function minLengthMet(query) { query = _.isString(query) ? query : this.input.getQuery() || ""; return query.length >= this.minLength; }, _updateHint: function updateHint() { var $selectable, data, val, query, escapedQuery, frontMatchRegEx, match; $selectable = this.menu.getTopSelectable(); data = this.menu.getSelectableData($selectable); val = this.input.getInputValue(); if (data && !_.isBlankString(val) && !this.input.hasOverflow()) { query = Input.normalizeQuery(val); escapedQuery = _.escapeRegExChars(query); frontMatchRegEx = new RegExp("^(?:" + escapedQuery + ")(.+$)", "i"); match = frontMatchRegEx.exec(data.val); match && this.input.setHint(val + match[1]); } else { this.input.clearHint(); } }, isEnabled: function isEnabled() { return this.enabled; }, enable: function enable() { this.enabled = true; }, disable: function disable() { this.enabled = false; }, isActive: function isActive() { return this.active; }, activate: function activate() { if (this.isActive()) { return true; } else if (!this.isEnabled() || this.eventBus.before("active")) { return false; } else { this.active = true; this.eventBus.trigger("active"); return true; } }, deactivate: function deactivate() { if (!this.isActive()) { return true; } else if (this.eventBus.before("idle")) { return false; } else { this.active = false; this.close(); this.eventBus.trigger("idle"); return true; } }, isOpen: function isOpen() { return this.menu.isOpen(); }, open: function open() { if (!this.isOpen() && !this.eventBus.before("open")) { this.input.setAriaExpanded(true); this.menu.open(); this._updateHint(); this.eventBus.trigger("open"); } return this.isOpen(); }, close: function close() { if (this.isOpen() && !this.eventBus.before("close")) { this.input.setAriaExpanded(false); this.menu.close(); this.input.clearHint(); this.input.resetInputValue(); this.eventBus.trigger("close"); } return !this.isOpen(); }, setVal: function setVal(val) { this.input.setQuery(_.toStr(val)); }, getVal: function getVal() { return this.input.getQuery(); }, select: function select($selectable) { var data = this.menu.getSelectableData($selectable); if (data && !this.eventBus.before("select", data.obj, data.dataset)) { this.input.setQuery(data.val, true); this.eventBus.trigger("select", data.obj, data.dataset); this.close(); return true; } return false; }, autocomplete: function autocomplete($selectable) { var query, data, isValid; query = this.input.getQuery(); data = this.menu.getSelectableData($selectable); isValid = data && query !== data.val; if (isValid && !this.eventBus.before("autocomplete", data.obj, data.dataset)) { this.input.setQuery(data.val); this.eventBus.trigger("autocomplete", data.obj, data.dataset); return true; } return false; }, moveCursor: function moveCursor(delta) { var query, $candidate, data, suggestion, datasetName, cancelMove, id; query = this.input.getQuery(); $candidate = this.menu.selectableRelativeToCursor(delta); data = this.menu.getSelectableData($candidate); suggestion = data ? data.obj : null; datasetName = data ? data.dataset : null; id = $candidate ? $candidate.attr("id") : null; this.input.trigger("cursorchange", id); cancelMove = this._minLengthMet() && this.menu.update(query); if (!cancelMove && !this.eventBus.before("cursorchange", suggestion, datasetName)) { this.menu.setCursor($candidate); if (data) { if (typeof data.val === "string") { this.input.setInputValue(data.val); } } else { this.input.resetInputValue(); this._updateHint(); } this.eventBus.trigger("cursorchange", suggestion, datasetName); return true; } return false; }, destroy: function destroy() { this.input.destroy(); this.menu.destroy(); } }); return Typeahead; function c(ctx) { var methods = [].slice.call(arguments, 1); return function() { var args = [].slice.call(arguments); _.each(methods, function(method) { return ctx[method].apply(ctx, args); }); }; } }(); (function() { "use strict"; var old, keys, methods; old = $.fn.typeahead; keys = { www: "tt-www", attrs: "tt-attrs", typeahead: "tt-typeahead" }; methods = { initialize: function initialize(o, datasets) { var www; datasets = _.isArray(datasets) ? datasets : [].slice.call(arguments, 1); o = o || {}; www = WWW(o.classNames); return this.each(attach); function attach() { var $input, $wrapper, $hint, $menu, defaultHint, defaultMenu, eventBus, input, menu, status, typeahead, MenuConstructor; _.each(datasets, function(d) { d.highlight = !!o.highlight; }); $input = $(this); $wrapper = $(www.html.wrapper); $hint = $elOrNull(o.hint); $menu = $elOrNull(o.menu); defaultHint = o.hint !== false && !$hint; defaultMenu = o.menu !== false && !$menu; defaultHint && ($hint = buildHintFromInput($input, www)); defaultMenu && ($menu = $(www.html.menu).css(www.css.menu)); $hint && $hint.val(""); $input = prepInput($input, www); if (defaultHint || defaultMenu) { $wrapper.css(www.css.wrapper); $input.css(defaultHint ? www.css.input : www.css.inputWithNoHint); $input.wrap($wrapper).parent().prepend(defaultHint ? $hint : null).append(defaultMenu ? $menu : null); } MenuConstructor = defaultMenu ? DefaultMenu : Menu; eventBus = new EventBus({ el: $input }); input = new Input({ hint: $hint, input: $input, menu: $menu }, www); menu = new MenuConstructor({ node: $menu, datasets: datasets }, www); status = new Status({ $input: $input, menu: menu }); typeahead = new Typeahead({ input: input, menu: menu, eventBus: eventBus, minLength: o.minLength, autoselect: o.autoselect }, www); $input.data(keys.www, www); $input.data(keys.typeahead, typeahead); } }, isEnabled: function isEnabled() { var enabled; ttEach(this.first(), function(t) { enabled = t.isEnabled(); }); return enabled; }, enable: function enable() { ttEach(this, function(t) { t.enable(); }); return this; }, disable: function disable() { ttEach(this, function(t) { t.disable(); }); return this; }, isActive: function isActive() { var active; ttEach(this.first(), function(t) { active = t.isActive(); }); return active; }, activate: function activate() { ttEach(this, function(t) { t.activate(); }); return this; }, deactivate: function deactivate() { ttEach(this, function(t) { t.deactivate(); }); return this; }, isOpen: function isOpen() { var open; ttEach(this.first(), function(t) { open = t.isOpen(); }); return open; }, open: function open() { ttEach(this, function(t) { t.open(); }); return this; }, close: function close() { ttEach(this, function(t) { t.close(); }); return this; }, select: function select(el) { var success = false, $el = $(el); ttEach(this.first(), function(t) { success = t.select($el); }); return success; }, autocomplete: function autocomplete(el) { var success = false, $el = $(el); ttEach(this.first(), function(t) { success = t.autocomplete($el); }); return success; }, moveCursor: function moveCursoe(delta) { var success = false; ttEach(this.first(), function(t) { success = t.moveCursor(delta); }); return success; }, val: function val(newVal) { var query; if (!arguments.length) { ttEach(this.first(), function(t) { query = t.getVal(); }); return query; } else { ttEach(this, function(t) { t.setVal(_.toStr(newVal)); }); return this; } }, destroy: function destroy() { ttEach(this, function(typeahead, $input) { revert($input); typeahead.destroy(); }); return this; } }; $.fn.typeahead = function(method) { if (methods[method]) { return methods[method].apply(this, [].slice.call(arguments, 1)); } else { return methods.initialize.apply(this, arguments); } }; $.fn.typeahead.noConflict = function noConflict() { $.fn.typeahead = old; return this; }; function ttEach($els, fn) { $els.each(function() { var $input = $(this), typeahead; (typeahead = $input.data(keys.typeahead)) && fn(typeahead, $input); }); } function buildHintFromInput($input, www) { return $input.clone().addClass(www.classes.hint).removeData().css(www.css.hint).css(getBackgroundStyles($input)).prop({ readonly: true, required: false }).removeAttr("id name placeholder").removeClass("required").attr({ spellcheck: "false", tabindex: -1 }); } function prepInput($input, www) { $input.data(keys.attrs, { dir: $input.attr("dir"), autocomplete: $input.attr("autocomplete"), spellcheck: $input.attr("spellcheck"), style: $input.attr("style") }); $input.addClass(www.classes.input).attr({ spellcheck: false }); try { !$input.attr("dir") && $input.attr("dir", "auto"); } catch (e) {} return $input; } function getBackgroundStyles($el) { return { backgroundAttachment: $el.css("background-attachment"), backgroundClip: $el.css("background-clip"), backgroundColor: $el.css("background-color"), backgroundImage: $el.css("background-image"), backgroundOrigin: $el.css("background-origin"), backgroundPosition: $el.css("background-position"), backgroundRepeat: $el.css("background-repeat"), backgroundSize: $el.css("background-size") }; } function revert($input) { var www, $wrapper; www = $input.data(keys.www); $wrapper = $input.parent().filter(www.selectors.wrapper); _.each($input.data(keys.attrs), function(val, key) { _.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val); }); $input.removeData(keys.typeahead).removeData(keys.www).removeData(keys.attr).removeClass(www.classes.input); if ($wrapper.length) { $input.detach().insertAfter($wrapper); $wrapper.remove(); } } function $elOrNull(obj) { var isValid, $el; isValid = _.isJQuery(obj) || _.isElement(obj); $el = isValid ? $(obj).first() : []; return $el.length ? $el : null; } })(); }); ================================================ FILE: lib/jazzy/themes/fullwidth/templates/deprecation.mustache ================================================ {{#deprecation_message}}

      Deprecated

      {{{deprecation_message}}}
      {{/deprecation_message}} {{#unavailable_message}}

      Unavailable

      {{{unavailable_message}}}
      {{/unavailable_message}} ================================================ FILE: lib/jazzy/themes/fullwidth/templates/doc.mustache ================================================ {{name}} {{kind}} Reference {{#enable_katex}} {{/enable_katex}} {{#enable_katex}} {{/enable_katex}} {{{custom_head}}} {{^disable_search}} {{/disable_search}} {{#dash_type}} {{/dash_type}} {{> header}}
      {{> nav}}
      {{^hide_name}}

      {{name}}

      {{/hide_name}} {{> deprecation}} {{#declaration}}
      {{#other_language_declaration}}

      {{language}}

      {{/other_language_declaration}} {{{declaration}}}
      {{#other_language_declaration}}

      Swift

      {{{other_language_declaration}}}
      {{/other_language_declaration}}
      {{/declaration}} {{{overview}}} {{#parameters.any?}}

      Parameters

      {{#parameters}} {{> parameter}} {{/parameters}}
      {{/parameters.any?}} {{#return}}

      Return Value

      {{{return}}}
      {{/return}} {{#source_host_item_url}} {{/source_host_item_url}}
      {{> tasks}}
      {{> footer}} ================================================ FILE: lib/jazzy/themes/fullwidth/templates/footer.mustache ================================================ ================================================ FILE: lib/jazzy/themes/fullwidth/templates/header.mustache ================================================

      {{docs_title}} {{#doc_coverage}} ({{doc_coverage}}% documented){{/doc_coverage}}

      {{^disable_search}}
      {{/disable_search}} {{#source_host_url}}

      {{source_host_name}} View on {{source_host_name}}

      {{/source_host_url}} {{#dash_url}}

      Dash Install in Dash

      {{/dash_url}}
      ================================================ FILE: lib/jazzy/themes/fullwidth/templates/nav.mustache ================================================ ================================================ FILE: lib/jazzy/themes/fullwidth/templates/parameter.mustache ================================================ {{name}}
      {{{discussion}}}
      ================================================ FILE: lib/jazzy/themes/fullwidth/templates/task.mustache ================================================
      {{#name}}

      {{{name_html}}}

      {{/name}}
        {{#items}}
      • {{#direct_link}} {{name}} {{/direct_link}} {{^direct_link}} {{^usage_discouraged}} {{{name_html}}} {{/usage_discouraged}} {{#usage_discouraged}} {{{name_html}}} {{/usage_discouraged}} {{#declaration_note}} {{.}} {{/declaration_note}}
        {{> deprecation}} {{#abstract}}
        {{{abstract}}} {{#url}} See more {{/url}}
        {{/abstract}} {{#default_impl_abstract}}

        Default Implementation

        {{{default_impl_abstract}}}
        {{/default_impl_abstract}} {{#declaration}}

        Declaration

        {{language}}

        {{{declaration}}}
        {{#other_language_declaration}}

        Swift

        {{{other_language_declaration}}}
        {{/other_language_declaration}}
        {{/declaration}} {{#parameters.count}}

        Parameters

        {{#parameters}} {{> parameter}} {{/parameters}}
        {{/parameters.count}} {{#return}}

        Return Value

        {{{return}}}
        {{/return}} {{#source_host_item_url}} {{/source_host_item_url}}
        {{/direct_link}}
      • {{/items}}
      ================================================ FILE: lib/jazzy/themes/fullwidth/templates/tasks.mustache ================================================ {{#tasks.count}}
      {{#tasks}} {{> task}} {{/tasks}}
      {{/tasks.count}} ================================================ FILE: lib/jazzy/themes/jony/assets/css/highlight.css.scss ================================================ /*! Jazzy - https://github.com/realm/jazzy * Copyright Realm Inc. * SPDX-License-Identifier: MIT */ /* Credit to https://gist.github.com/wataru420/2048287 */ .highlight { .c { color: #999988; font-style: italic } // Comment .err { color: #a61717; background-color: #e3d2d2 } // Error .k { color: #000000; font-weight: bold } // Keyword .o { color: #000000; font-weight: bold } // Operator .cm { color: #999988; font-style: italic } // Comment.Multiline .cp { color: #999999; font-weight: bold } // Comment.Preproc .c1 { color: #999988; font-style: italic } // Comment.Single .cs { color: #999999; font-weight: bold; font-style: italic } // Comment.Special .gd { color: #000000; background-color: #ffdddd } // Generic.Deleted .gd .x { color: #000000; background-color: #ffaaaa } // Generic.Deleted.Specific .ge { color: #000000; font-style: italic } // Generic.Emph .gr { color: #aa0000 } // Generic.Error .gh { color: #999999 } // Generic.Heading .gi { color: #000000; background-color: #ddffdd } // Generic.Inserted .gi .x { color: #000000; background-color: #aaffaa } // Generic.Inserted.Specific .go { color: #888888 } // Generic.Output .gp { color: #555555 } // Generic.Prompt .gs { font-weight: bold } // Generic.Strong .gu { color: #aaaaaa } // Generic.Subheading .gt { color: #aa0000 } // Generic.Traceback .kc { color: #000000; font-weight: bold } // Keyword.Constant .kd { color: #000000; font-weight: bold } // Keyword.Declaration .kp { color: #000000; font-weight: bold } // Keyword.Pseudo .kr { color: #000000; font-weight: bold } // Keyword.Reserved .kt { color: #445588; } // Keyword.Type .m { color: #009999 } // Literal.Number .s { color: #d14 } // Literal.String .na { color: #008080 } // Name.Attribute .nb { color: #0086B3 } // Name.Builtin .nc { color: #445588; font-weight: bold } // Name.Class .no { color: #008080 } // Name.Constant .ni { color: #800080 } // Name.Entity .ne { color: #990000; font-weight: bold } // Name.Exception .nf { color: #990000; } // Name.Function .nn { color: #555555 } // Name.Namespace .nt { color: #000080 } // Name.Tag .nv { color: #008080 } // Name.Variable .ow { color: #000000; font-weight: bold } // Operator.Word .w { color: #bbbbbb } // Text.Whitespace .mf { color: #009999 } // Literal.Number.Float .mh { color: #009999 } // Literal.Number.Hex .mi { color: #009999 } // Literal.Number.Integer .mo { color: #009999 } // Literal.Number.Oct .sb { color: #d14 } // Literal.String.Backtick .sc { color: #d14 } // Literal.String.Char .sd { color: #d14 } // Literal.String.Doc .s2 { color: #d14 } // Literal.String.Double .se { color: #d14 } // Literal.String.Escape .sh { color: #d14 } // Literal.String.Heredoc .si { color: #d14 } // Literal.String.Interpol .sx { color: #d14 } // Literal.String.Other .sr { color: #009926 } // Literal.String.Regex .s1 { color: #d14 } // Literal.String.Single .ss { color: #990073 } // Literal.String.Symbol .bp { color: #999999 } // Name.Builtin.Pseudo .vc { color: #008080 } // Name.Variable.Class .vg { color: #008080 } // Name.Variable.Global .vi { color: #008080 } // Name.Variable.Instance .il { color: #009999 } // Literal.Number.Integer.Long } ================================================ FILE: lib/jazzy/themes/jony/assets/css/jazzy.css.scss ================================================ /*! Jazzy - https://github.com/realm/jazzy * Copyright Realm Inc. * SPDX-License-Identifier: MIT */ //////////////////////////////// // Constants //////////////////////////////// $bg_color: #2C2C2C; $doc_coverage_color: #999; $code_color: #777; $code_bg_color: #eee; $link_color: #0088cc; $white_color: #fff; $light_gray_bg_color: #fafafa; $normal_gray_bg_color: #f2f2f2; $declaration_bg_color: #f9f9f9; $declaration_title_language_color: #4b8afb; $content_wrapper_width: calc(100% - 32px); $content_wrapper_max_width: 980px; $article_max_width: 750px; $mobile_breakpoint: 767px; $content_top_offset: 87px; $header_height: 48px; $breadcrumb_padding: 10px; $code_font: 'SF Mono', Menlo, monospace; $gray_border_color: #e2e2e2; $gray_border: 1px solid $gray_border_color; $declaration_language_border: 5px solid #cde9f4; $aside_color: #aaa; $aside_border: 5px solid lighten($aside_color, 20%); $aside_warning_color: #ff0000; $aside_warning_border: 5px solid lighten($aside_warning_color, 20%); //////////////////////////////// // Reset //////////////////////////////// html, body, div, span, h1, h3, h4, p, a, code, em, img, ul, li, table, tbody, tr, td { background: transparent; border: 0; margin: 0; outline: 0; padding: 0; vertical-align: baseline; } //////////////////////////////// // Global //////////////////////////////// body { background-color: $white_color; font-family: -apple-system, Helvetica, freesans, Arial, sans-serif; font-size: 16px; line-height: 1.6; -webkit-font-smoothing: subpixel-antialiased; word-wrap: break-word; min-height: 100vh; } // Headers h1, h2, h3 { margin-top: 0.8em; margin-bottom: 0.3em; font-weight: 400; color: black; } h1 { font-size: 2.5em; } h2 { font-size: 2em; border-bottom: $gray_border; } h4 { font-size: 13px; line-height: 1.5; margin-top: 21px; } h5 { font-size: 1.1em; } h6 { font-size: 1.1em; color: $code_color; } @media screen and (max-width: $mobile_breakpoint) { h1 { font-size: 1.75em; } h2 { font-size: 1.4em; } } // Code pre, code { font-family: $code_font; font-size: 0.95em; color: $code_color; word-wrap: normal; } pre { line-height: 1.6; } // Links a { color: $link_color; text-decoration: none; code { color: inherit; } } // Lists ul { padding-left: 15px; } li { line-height: 1.8em; } // Images img { max-width: 100%; } // Blockquotes blockquote { margin-left: 0; padding: 0 10px; border-left: 4px solid #ccc; } // HRs hr { height: 1px; border: none; background-color: $gray_border_color; } // Footnotes .footnote-ref { display: inline-block; scroll-margin-top: $content-top-offset; } .footnote-def { scroll-margin-top: $content-top-offset; } // General Content Wrapper .content-wrapper { margin: 0 auto; width: $content_wrapper_width; max-width: $content_wrapper_max_width; } //////////////////////////////// // Content Wrappers //////////////////////////////// .wrapper { display: flex; flex-direction: column; min-height: inherit; overflow: auto; } .article-wrapper > *, .nav-wrapper > *, .footer-wrapper > * { margin: 0 auto; width: $content_wrapper_width; max-width: $content_wrapper_max_width; } .article-wrapper { flex: 1; background-color: $white_color; } .nav-wrapper { background-color: $light_gray_bg_color; } .footer-wrapper { background-color: $normal_gray_bg_color; } //////////////////////////////// // Header & Top Breadcrumbs //////////////////////////////// header { // font-size: 0.85em; line-height: $header_height; background-color: $bg_color; position: fixed; width: 100%; z-index: 3; img { padding-right: 6px; vertical-align: -2px; height: 16px; } a { color: $white_color; } p { float: left; color: $doc_coverage_color; } .header-right { float: right; margin-left: 16px; } } #breadcrumbs-container { background-color: $bg_color; position: fixed; z-index: 2; width: 100%; } #breadcrumbs { color: rgba(255, 255, 255, 0.6); height: $content_top_offset - $header_height - $breadcrumb_padding; padding-bottom: $breadcrumb_padding; width: 100%; margin-top: $header_height; white-space: nowrap; overflow-x: scroll; #carat { height: 10px; margin: 0 5px; } a { color: white; } } @media screen and (max-width: $mobile_breakpoint) { #breadcrumbs { color: white; } } //////////////////////////////// // Navigation //////////////////////////////// .nav-groups { list-style-type: none; padding-left: 0; } .nav-group-name { font-size: 1.5rem; font-weight: 500; padding: 20px 0; &:not(:last-child) { border-bottom: $gray_border; } > a { color: #333; } } .nav-group-tasks { column-count: 2; list-style: none; padding: 0; margin-top: 5px; } .nav-group-task { font-size: 1.1rem; font-weight: 400; a { color: #888; } } @media screen and (max-width: $mobile_breakpoint) { .nav-group-tasks { column-count: 1; } } //////////////////////////////// // Main Content //////////////////////////////// .main-content { overflow: hidden; padding-bottom: 60px; margin-top: $content_top_offset; p, a, code, em, ul, table, blockquote { margin-bottom: 1em; } p { line-height: 1.5; } section { max-width: $article_max_width; .section:first-child { margin-top: 0; padding-top: 0; } .task-group-section .task-group:first-of-type { padding-top: 10px; .section-name { padding-top: 15px; } } .heading:before { content: ""; display: block; padding-top: $content_top_offset; margin: -$content_top_offset 0 0; } } .section-name { p { margin-bottom: inherit; line-height: inherit; } code { color: inherit; } } } .highlight { background-color: $code_bg_color; padding: 10px 12px; border: $gray_border; border-radius: 4px; overflow-x: auto; } .declaration .highlight { overflow-x: initial; // This allows the scrollbar to show up inside declarations padding: 0 40px 40px 0; margin-bottom: -25px; background-color: transparent; border: none; } .section-name { font-size: 1.5rem; font-weight: 500; margin: 0; } .task-group-section { margin-top: 10px; border-top: $gray_border; } .task-group { padding-top: 0px; > ul { padding-left: 0; } } .task-name-container { a[name] { &:before { content: ""; display: block; padding-top: $content_top_offset; margin: -$content_top_offset 0 0; } } } .section-name-container { position: relative; display: inline-block; .section-name-link { position: absolute; top: 0; left: 0; bottom: 0; right: 0; margin-bottom: 0; } .section-name { position: relative; pointer-events: none; z-index: 1; a { pointer-events: auto; } } } .item { padding-top: 8px; width: 100%; list-style-type: none; a[name] { &:before { content: ""; display: block; padding-top: $content_top_offset; margin: -$content_top_offset 0 0; } } code { background-color: transparent; padding: 0; } .token, .direct-link { display: inline-block; text-indent: -20px; padding-left: 3px; margin-left: 55px; transition: all 300ms; } .discouraged { text-decoration: line-through; } .token-open { margin-left: 45px; } } .declaration-note { font-size: .85em; color: rgba(128,128,128,1); font-style: italic; } .pointer-container { left: -23px; padding-bottom: 13px; position: relative; width: 110%; } .pointer { background: $declaration_bg_color; border-left: $gray_border; border-top: $gray_border; height: 12px; left: 21px; top: -7px; -webkit-transform: rotate(45deg); -moz-transform: rotate(45deg); -o-transform: rotate(45deg); transform: rotate(45deg); position: absolute; width: 12px; } .height-container { display: none; position: relative; width: 100%; overflow: hidden; .section { position: relative; background: $declaration_bg_color; width: 100%; padding: 10px 25px; border: $gray_border; border-radius: 8px; box-sizing: border-box; } } .aside, .language { padding: 6px 12px; margin: 12px 0; border-left: $aside_border; overflow-y: hidden; .aside-title { font-size: 12px; font-weight: 600; letter-spacing: 2px; text-transform: uppercase; padding-bottom: 0; margin: 0; color: $aside_color; -webkit-user-select: none; } p:last-child { margin-bottom: 0; } } .language { border-left: $declaration_language_border; .aside-title { color: $declaration_title_language_color; } } .aside-warning, .aside-deprecated, .aside-unavailable { border-left: $aside_warning_border; .aside-title { color: $aside_warning_color; } } .graybox { border-collapse: collapse; width: 100%; p { margin: 0; word-break: break-word; min-width: 50px; } td { border: $gray_border; padding: 5px 25px 5px 10px; vertical-align: middle; } tr td:first-of-type { text-align: right; padding: 7px; vertical-align: top; word-break: normal; width: 40px; } } .slightly-smaller { font-size: 0.9em; } #footer { padding: 25px 0; box-sizing: border-box; p { margin: 0; color: #aaa; font-size: 0.8em; } } //////////////////////////////// // Dash //////////////////////////////// html.dash { header, #breadcrumbs { display: none; } .main-content { width: $content_wrapper_width; max-width: $content_wrapper_max_width; margin-left: 0; border: none; width: 100%; top: 0; padding-bottom: 0; } .height-container { display: block; } .item .token { margin-left: 0; } .content-wrapper { width: auto; } #footer { position: static; } } //////////////////////////////// // Responsive design //////////////////////////////// @media screen and (max-width: $mobile_breakpoint) { .no-mobile { display: none; } } ================================================ FILE: lib/jazzy/themes/jony/assets/js/jazzy.js ================================================ // Jazzy - https://github.com/realm/jazzy // Copyright Realm Inc. // SPDX-License-Identifier: MIT window.jazzy = {'docset': false} if (typeof window.dash != 'undefined') { document.documentElement.className += ' dash' window.jazzy.docset = true } if (navigator.userAgent.match(/xcode/i)) { document.documentElement.className += ' xcode' window.jazzy.docset = true } function toggleItem($link, $content) { var animationDuration = 300; $link.toggleClass('token-open'); $content.slideToggle(animationDuration); } function itemLinkToContent($link) { return $link.parent().parent().next(); } // On doc load + hash-change, open any targeted item function openCurrentItemIfClosed() { if (window.jazzy.docset) { return; } var $link = $(`a[name="${location.hash.substring(1)}"]`).nextAll('.token'); $content = itemLinkToContent($link); if ($content.is(':hidden')) { toggleItem($link, $content); } } $(openCurrentItemIfClosed); $(window).on('hashchange', openCurrentItemIfClosed); // On item link ('token') click, toggle its discussion $('.token').on('click', function(event) { if (window.jazzy.docset) { return; } var $link = $(this); toggleItem($link, itemLinkToContent($link)); // Keeps the document from jumping to the hash. var href = $link.attr('href'); if (history.pushState) { history.pushState({}, '', href); } else { location.hash = href; } event.preventDefault(); }); // Clicks on links to the current, closed, item need to open the item $("a:not('.token')").on('click', function() { if (location == this.href) { openCurrentItemIfClosed(); } }); // KaTeX rendering if ("katex" in window) { $($('.math').each( (_, element) => { katex.render(element.textContent, element, { displayMode: $(element).hasClass('m-block'), throwOnError: false, trust: true }); })) } ================================================ FILE: lib/jazzy/themes/jony/templates/deprecation.mustache ================================================ {{#deprecation_message}}

      Deprecated

      {{{deprecation_message}}}
      {{/deprecation_message}} {{#unavailable_message}}

      Unavailable

      {{{unavailable_message}}}
      {{/unavailable_message}} ================================================ FILE: lib/jazzy/themes/jony/templates/doc.mustache ================================================ {{name}} {{kind}} Reference {{#enable_katex}} {{/enable_katex}} {{#enable_katex}} {{/enable_katex}} {{{custom_head}}} {{#dash_type}} {{/dash_type}} {{> header}}
      {{^hide_name}}

      {{name}}

      {{/hide_name}} {{> deprecation}} {{#declaration}}
      {{#other_language_declaration}}

      {{language}}

      {{/other_language_declaration}} {{{declaration}}}
      {{#other_language_declaration}}

      Swift

      {{{other_language_declaration}}}
      {{/other_language_declaration}}
      {{/declaration}} {{{overview}}} {{#parameters.any?}}

      Parameters

      {{#parameters}} {{> parameter}} {{/parameters}}
      {{/parameters.any?}} {{#return}}

      Return Value

      {{{return}}}
      {{/return}} {{#source_host_item_url}} {{/source_host_item_url}}
      {{> tasks}}
      ================================================ FILE: lib/jazzy/themes/jony/templates/footer.mustache ================================================ ================================================ FILE: lib/jazzy/themes/jony/templates/header.mustache ================================================

      {{docs_title}} {{#doc_coverage}} ({{doc_coverage}}% documented){{/doc_coverage}}

      {{#source_host_url}}

      {{source_host_name}} View on {{source_host_name}}

      {{/source_host_url}} {{#dash_url}}

      Dash Install in Dash

      {{/dash_url}}
      ================================================ FILE: lib/jazzy/themes/jony/templates/nav.mustache ================================================ ================================================ FILE: lib/jazzy/themes/jony/templates/parameter.mustache ================================================ {{name}}
      {{{discussion}}}
      ================================================ FILE: lib/jazzy/themes/jony/templates/task.mustache ================================================
      {{#name}}

      {{{name_html}}}

      {{/name}}
        {{#items}}
      • {{#direct_link}} {{name}} {{/direct_link}} {{^direct_link}} {{^usage_discouraged}} {{{name_html}}} {{/usage_discouraged}} {{#usage_discouraged}} {{{name_html}}} {{/usage_discouraged}} {{#declaration_note}} {{.}} {{/declaration_note}}
        {{> deprecation}} {{#abstract}}
        {{{abstract}}} {{#url}} See more {{/url}}
        {{/abstract}} {{#default_impl_abstract}}

        Default Implementation

        {{{default_impl_abstract}}}
        {{/default_impl_abstract}} {{#declaration}}

        Declaration

        {{language}}

        {{{declaration}}}
        {{#other_language_declaration}}

        Swift

        {{{other_language_declaration}}}
        {{/other_language_declaration}}
        {{/declaration}} {{#parameters.count}}

        Parameters

        {{#parameters}} {{> parameter}} {{/parameters}}
        {{/parameters.count}} {{#return}}

        Return Value

        {{{return}}}
        {{/return}} {{#source_host_item_url}} {{/source_host_item_url}}
        {{/direct_link}}
      • {{/items}}
      ================================================ FILE: lib/jazzy/themes/jony/templates/tasks.mustache ================================================ {{#tasks.count}}
      {{#tasks}} {{> task}} {{/tasks}}
      {{/tasks.count}} ================================================ FILE: lib/jazzy.rb ================================================ # frozen_string_literal: true require 'jazzy/config' require 'jazzy/doc_builder' Encoding.default_external = Encoding::UTF_8 ================================================ FILE: spec/Moya.podspec ================================================ Pod::Spec.new do |s| s.name = "Moya" s.version = "15.0.0" s.summary = "Network abstraction layer written in Swift" s.description = <<-EOS Moya abstracts network commands using Swift Generics to provide developers with more compile-time confidence. ReactiveSwift and RxSwift extensions exist as well. Instructions for installation are in [the README](https://github.com/Moya/Moya). EOS s.homepage = "https://github.com/Moya/Moya" s.license = { :type => "MIT", :file => "License.md" } s.author = { "Ash Furrow" => "ash@ashfurrow.com" } s.social_media_url = "http://twitter.com/ashfurrow" s.ios.deployment_target = '11.0' s.osx.deployment_target = '10.13' s.tvos.deployment_target = '10.0' s.watchos.deployment_target = '3.0' s.source = { :git => "https://github.com/Moya/Moya.git", :tag => s.version } s.default_subspec = "Core" s.swift_version = '5.3' s.cocoapods_version = '>= 1.4.0' s.subspec "Core" do |ss| ss.source_files = "Sources/Moya/", "Sources/Moya/Plugins/" ss.dependency "Alamofire", "~> 5.0" ss.framework = "Foundation" s.ios.deployment_target = '11.0' s.osx.deployment_target = '10.13' s.tvos.deployment_target = '10.0' s.watchos.deployment_target = '3.0' end s.subspec "Combine" do |ss| ss.source_files = "Sources/CombineMoya/" ss.dependency "Moya/Core" ss.framework = "Combine" ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' ss.tvos.deployment_target = '13.0' ss.watchos.deployment_target = '6.0' end s.subspec "ReactiveSwift" do |ss| ss.source_files = "Sources/ReactiveMoya/" ss.dependency "Moya/Core" ss.dependency "ReactiveSwift", "~> 7.0" ss.ios.deployment_target = '11.0' ss.osx.deployment_target = '10.13' ss.tvos.deployment_target = '10.0' ss.watchos.deployment_target = '3.0' end end ================================================ FILE: spec/integration_spec.rb ================================================ # frozen_string_literal: true # ------------------------------------ # # Jazzy Integration tests # # ------------------------------------ # #-----------------------------------------------------------------------------# # The following integrations tests are based on file comparison. # # 1. For each test there is a folder with a `before` and `after` subfolders. # 2. The contents of the before folder are copied to the `TMP_DIR` folder and # then the given arguments are passed to the `JAZZY_BINARY`. # 3. After the jazzy command completes the execution the each file in the # `after` subfolder is compared to the contents of the temporary # directory. If the contents of the file do not match an error is # registered. # # Notes: # # - The output of the jazzy command is saved in the `execution_output.txt` file # which should be added to the `after` folder to test the Jazzy UI. # - To create a new test, just create a before folder with the environment to # test, copy it to the after folder and run the tested pod command inside. # Then just add the tests below this files with the name of the folder and # the arguments. # # Rationale: # # - Have a way to track precisely the evolution of the artifacts (and of the # UI) produced by jazzy (git diff of the after folders). # - Allow uses to submit pull requests with the environment necessary to # reproduce an issue. # - Have robust tests which don't depend on the programmatic interface of # Jazzy. These tests depend only the binary and its arguments an thus are # suitable for testing Jazzy regardless of the implementation (they could even # work for a Swift one) #-----------------------------------------------------------------------------# # @return [Pathname] The root of the repo. # ROOT = Pathname.new(File.expand_path('..', __dir__)) unless defined? ROOT $:.unshift((ROOT + 'spec').to_s) require 'rubygems' require 'bundler/setup' require 'pretty_bacon' require 'colored2' require 'CLIntegracon' require 'cocoapods' def configure_cocoapods Pod::Config.instance.with_changes(silent: true) do Pod::Command::Setup.invoke Pod::Command::Repo::AddCDN.invoke(%w[trunk https://cdn.cocoapods.org/]) Pod::Command::Repo::Update.invoke(%w[trunk]) end end CLIntegracon.configure do |c| c.spec_path = ROOT + 'spec/integration_specs' c.temp_path = ROOT + 'tmp' # Ignore certain OSX files c.ignores '.DS_Store' c.ignores '.git' c.ignores %r{^(?!((api-)?docs(/|\z)|execution_output.txt))} c.ignores '**/*.tgz' # Remove absolute paths from output c.transform_produced '**/undocumented.json' do |path| File.write( path, File.read(path).gsub( c.temp_path.to_s, '', ).gsub( c.spec_path.to_s, '', ).gsub( '/transformed', '', ), ) end # Transform produced databases to csv c.transform_produced '**/*.dsidx' do |path| File.write("#{path}.csv", `sqlite3 -header -csv #{path} "select * from searchIndex;"`) end # Now that we're comparing the CSV, we don't care about the binary c.ignores '**/*.dsidx' c.hook_into :bacon end describe_cli 'jazzy' do subject do |s| s.executable = "ruby #{ROOT + 'bin/jazzy'}" s.environment_vars = { 'JAZZY_FAKE_DATE' => 'YYYY-MM-DD', 'JAZZY_FAKE_VERSION' => 'X.X.X', 'COCOAPODS_SKIP_UPDATE_MESSAGE' => 'TRUE', 'JAZZY_INTEGRATION_SPECS' => 'TRUE', 'JAZZY_FAKE_MODULE_VERSION' => 'Y.Y.Y', } s.default_args = [] s.replace_path ROOT.to_s, 'ROOT' s.replace_pattern /^[\d\s:.-]+ ruby\[\d+:\d+\] warning:.*$\n?/, '' # Remove version numbers from CocoaPods dependencies # to make specs resilient against dependency updates. s.replace_pattern(/(Installing \w+ )\((.*)\)/, '\1(X.Y.Z)') # Xcode 13.3 etc workaround s.replace_pattern(/202[\d.:\- ]+xcodebuild.*?\n/, '') # Xcode 14 / in-proc sourcekitd workaround s.replace_pattern(/:0: remark.*?\n/, '') # CLIntegracon 0.8 s.replace_pattern(%r{/transformed/}, '/') # Xcode 15 workaround s.replace_pattern(/objc\[.....\]: Class _?DTX\w+ is implemented in both.*?\n/, '') # arm vs. intel workaround s.replace_pattern(%r{(?<=build/)(arm64|x86_64)(?=-apple)}, '') end require 'shellwords' realm_head = <<-HTML HTML realm_jazzy_yaml = <<-YAML build_tool_arguments: - "-scheme" - "RealmSwift" - "SWIFT_VERSION=4.2" - "-destination" - "platform=OS X,arch=x86_64" YAML spec_subset = ENV.fetch('JAZZY_SPEC_SUBSET', nil) # rubocop:disable Style/MultilineIfModifier describe 'jazzy objective-c' do describe 'Creates Realm Objective-C docs' do realm_version = '' relative_path = 'spec/integration_specs/document_realm_objc/before' Dir.chdir(ROOT + relative_path) do realm_version = `./build.sh get-version`.chomp # jazzy will fail if it can't find all public header files `touch Realm/RLMPlatform.h` end behaves_like cli_spec 'document_realm_objc', '--objc ' \ '--author Realm ' \ '--author_url "https://realm.io" ' \ '--source-host-url ' \ 'https://github.com/realm/realm-cocoa ' \ '--source-host-files-url https://github.com/realm/' \ "realm-cocoa/tree/v#{realm_version} " \ '--module Realm ' \ "--module-version #{realm_version} " \ '--root-url https://realm.io/docs/objc/' \ "#{realm_version}/api/ " \ '--umbrella-header Realm/Realm.h ' \ '--framework-root . ' \ "--head #{realm_head.shellescape}" end describe 'Creates docs for ObjC-Swift project with a variety of contents' do base = ROOT + 'spec/integration_specs/misc_jazzy_objc_features/before' Dir.chdir(base) do sourcekitten = ROOT + 'bin/sourcekitten' sdk = `xcrun --show-sdk-path --sdk iphonesimulator`.chomp objc_args = "#{base}/MiscJazzyObjCFeatures/MiscJazzyObjCFeatures.h " \ '-- -x objective-c ' \ "-isysroot #{sdk} " \ "-I #{base} " \ '-fmodules' `#{sourcekitten} doc --objc #{objc_args} > objc.json` `#{sourcekitten} doc -- clean build > swift.json` end behaves_like cli_spec 'misc_jazzy_objc_features', '--theme fullwidth ' \ '-s objc.json,swift.json' end end if !spec_subset || spec_subset == 'objc' describe 'jazzy swift' do describe 'Creates docs with a module name, author name, project URL, ' \ 'xcodebuild options, and github info' do behaves_like cli_spec 'document_alamofire', '--skip-undocumented ' \ '--clean ' \ '--xcodebuild-arguments ' \ "-destination,'platform=OS X'" end describe 'Creates Realm Swift docs' do realm_version = '' realm_path = ROOT + 'spec/integration_specs/document_realm_swift/before' realm_jazzy_path = realm_path + '.jazzy.yaml' Dir.chdir(realm_path) do realm_version = `./build.sh get-version`.chomp end # Xcode 16 workaround File.write(realm_jazzy_path, realm_jazzy_yaml) behaves_like cli_spec 'document_realm_swift', '--author Realm ' \ '--author_url "https://realm.io" ' \ '--source-host-url ' \ 'https://github.com/realm/realm-cocoa ' \ '--source-host-files-url https://github.com/realm/' \ "realm-cocoa/tree/v#{realm_version} " \ '--module RealmSwift ' \ "--module-version #{realm_version} " \ '--root-url https://realm.io/docs/swift/' \ "#{realm_version}/api/ " \ "--head #{realm_head.shellescape}" FileUtils.rm_rf realm_jazzy_path end describe 'Creates Siesta docs' do # Siesta already has Docs/ # Use the default Swift version rather than the specified 4.0 behaves_like cli_spec 'document_siesta', '--output api-docs ' \ '--swift-version= ' end describe 'Creates docs for Swift project with a variety of contents' do behaves_like cli_spec 'misc_jazzy_features' end describe 'Creates docs for Swift project from a .swiftmodule' do build_path = Dir.getwd + '/tmp/.build' package_path = ROOT + 'spec/integration_specs/misc_jazzy_symgraph_features/before' `swift build --package-path #{package_path} --scratch-path #{build_path}` module_path = `swift build --scratch-path #{build_path} --show-bin-path` behaves_like cli_spec 'misc_jazzy_symgraph_features', '--swift-build-tool symbolgraph ' \ '--build-tool-arguments ' \ '-emit-extension-block-symbols,-I,' \ "#{module_path.chomp}/Modules" end describe 'Creates docs for a multiple-module project' do behaves_like cli_spec 'jazzy_multi_modules' end end if !spec_subset || spec_subset == 'swift' describe 'jazzy cocoapods' do # Xcode 14.3 workaround, special podspec podspec_patch = ROOT + 'spec/Moya.podspec' podspec_used = ROOT + 'spec/integration_specs/document_moya_podspec/before/Moya.podspec' podspec_save = ROOT + 'spec/Moya.podspec.safe' FileUtils.cp_r podspec_used, podspec_save, remove_destination: true FileUtils.cp_r podspec_patch, podspec_used, remove_destination: true configure_cocoapods describe 'Creates docs for a podspec with dependencies and subspecs' do behaves_like cli_spec 'document_moya_podspec', '--podspec=Moya.podspec' end FileUtils.cp_r podspec_save, podspec_used, remove_destination: true FileUtils.rm_rf podspec_save end if !spec_subset || spec_subset == 'cocoapods' # rubocop:enable Style/MultilineIfModifier end ================================================ FILE: spec/spec_helper/pre_flight.rb ================================================ # frozen_string_literal: true # Restores the config to the default state before each requirement module Bacon class Context old_run_requirement = instance_method(:run_requirement) define_method(:run_requirement) do |description, spec| temporary_directory = SpecHelper.temporary_directory ::Jazzy::Config.instance = nil ::Jazzy::Config.instance.tap do |c| c.source_directory = temporary_directory end temporary_directory.rmtree if temporary_directory.exist? temporary_directory.mkpath old_run_requirement.bind(self).call(description, spec) end end end ================================================ FILE: spec/spec_helper.rb ================================================ # frozen_string_literal: true require 'rubygems' require 'bundler/setup' require 'bacon' require 'mocha-on-bacon' require 'pretty_bacon' require 'pathname' ROOT = Pathname.new(File.expand_path('..', __dir__)) $LOAD_PATH.unshift((ROOT + 'lib').to_s) $LOAD_PATH.unshift((ROOT + 'spec').to_s) require 'jazzy' require 'spec_helper/pre_flight' Bacon.summary_at_exit module Bacon class Context include Jazzy::Config::Mixin def temporary_directory SpecHelper.temporary_directory end end end Mocha::Configuration.prevent(:stubbing_non_existent_method) module SpecHelper def self.temporary_directory ROOT + 'tmp' end end