From e383d0f1f2be570f1e3eaa46012b25c6995b372c Mon Sep 17 00:00:00 2001 From: Jim Hildensperger Date: Mon, 13 Aug 2018 13:03:48 +0200 Subject: [PATCH 1/6] Clean up examples for headings supported by Xcode --- Sources/SourceDocsDemo/SampleCode.swift | 15 +++++++++++---- .../SourceDocsDemo/Enums/DomesticationState.md | 14 ++++++++++---- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/Sources/SourceDocsDemo/SampleCode.swift b/Sources/SourceDocsDemo/SampleCode.swift index bbbeaaa..a8c66cb 100644 --- a/Sources/SourceDocsDemo/SampleCode.swift +++ b/Sources/SourceDocsDemo/SampleCode.swift @@ -40,13 +40,20 @@ public protocol Animal { /// Describes the state of domestication /// -/// ### h3 _this is italics_ +/// _Italics_ are supported with a leading and trailing underscore /// -/// # h1 **this is bold** +/// **Bold** is supported with 2 leading and trailing asterisks /// -/// ## h2 WHAT +/// These are the supported headings using `#`: +/// +/// # h1 +/// +/// ## h2 +/// +/// ### h3 +/// +/// #### h4 /// -/// ###### h6 /// --- /// *** /// - domesticated: For animal that are domesticated. diff --git a/docs/reference/SourceDocsDemo/Enums/DomesticationState.md b/docs/reference/SourceDocsDemo/Enums/DomesticationState.md index d7d682c..2806e53 100644 --- a/docs/reference/SourceDocsDemo/Enums/DomesticationState.md +++ b/docs/reference/SourceDocsDemo/Enums/DomesticationState.md @@ -8,10 +8,16 @@ public enum DomesticationState > Describes the state of domestication > -> ### h3 _this is italics_ -> # h1 __this is bold__ -> ## h2 WHAT -> ###### h6 +> _Italics_ are supported with a leading and trailing underscore +> +> __Bold__ is supported with 2 leading and trailing asterisks +> +> These are the supported headings using `#`: +> +> # h1 +> ## h2 +> ### h3 +> #### h4 > --- > --- > From 164bd51661c15a4b847924b1f6361ba8e4eaa5cf Mon Sep 17 00:00:00 2001 From: Jim Hildensperger Date: Mon, 13 Aug 2018 13:04:58 +0200 Subject: [PATCH 2/6] Reorganise project structure to enable testability --- Package.resolved | 2 +- Package.swift | 5 +- Sources/SourceDocs/Classes/SourceDocs.swift | 31 --------- .../Extensions/Dictionary+Sourcedocs.swift | 35 ---------- Sources/SourceDocs/main.swift | 3 +- Sources/SourceDocsLib/Classes/Constants.swift | 17 +++++ .../Classes/CoverageBadge.swift | 2 +- .../Classes/DocumentationStatus.swift | 0 .../Classes/DocumentationStatusFile.swift | 2 +- .../Classes/MarkdownIndex.swift | 69 ++++++++++--------- .../Classes/SwiftDocDiscussionKey.swift | 0 .../Commands/Clean.swift | 4 +- .../Commands/Generate.swift | 19 +++-- .../Commands/Version.swift | 2 +- .../Elements/MarkdownEnum.swift | 21 +++--- .../Elements/MarkdownExtension.swift | 14 ++-- .../Elements/MarkdownMethod.swift | 6 +- .../Elements/MarkdownObject.swift | 16 ++--- .../Elements/MarkdownProtocol.swift | 16 ++--- .../Elements/MarkdownTypealias.swift | 4 +- .../Elements/MarkdownVariable.swift | 4 +- .../Extensions/Dictionary+Sourcedocs.swift | 60 ++++++++++++++++ .../SourceDocsError+Sourcedocs.swift | 4 +- .../Extensions/String+Sourcedocs.swift | 0 .../Protocols/Documentable.swift | 4 +- .../SwiftDocDictionaryInitializable.swift | 18 ++--- .../Protocols/Writeable.swift | 0 Sources/SourceDocsLib/SourceDocs.swift | 22 ++++++ Tests/SourceDocsTests/SourceDocsTests.swift | 15 ++++ .../SwiftDocDictionaryTests.swift | 20 ------ 30 files changed, 228 insertions(+), 187 deletions(-) delete mode 100644 Sources/SourceDocs/Classes/SourceDocs.swift delete mode 100644 Sources/SourceDocs/Extensions/Dictionary+Sourcedocs.swift create mode 100644 Sources/SourceDocsLib/Classes/Constants.swift rename Sources/{SourceDocs => SourceDocsLib}/Classes/CoverageBadge.swift (97%) rename Sources/{SourceDocs => SourceDocsLib}/Classes/DocumentationStatus.swift (100%) rename Sources/{SourceDocs => SourceDocsLib}/Classes/DocumentationStatusFile.swift (85%) rename Sources/{SourceDocs => SourceDocsLib}/Classes/MarkdownIndex.swift (71%) rename Sources/{SourceDocs => SourceDocsLib}/Classes/SwiftDocDiscussionKey.swift (100%) rename Sources/{SourceDocs => SourceDocsLib}/Commands/Clean.swift (96%) rename Sources/{SourceDocs => SourceDocsLib}/Commands/Generate.swift (91%) rename Sources/{SourceDocs => SourceDocsLib}/Commands/Version.swift (88%) rename Sources/{SourceDocs => SourceDocsLib}/Elements/MarkdownEnum.swift (85%) rename Sources/{SourceDocs => SourceDocsLib}/Elements/MarkdownExtension.swift (81%) rename Sources/{SourceDocs => SourceDocsLib}/Elements/MarkdownMethod.swift (91%) rename Sources/{SourceDocs => SourceDocsLib}/Elements/MarkdownObject.swift (83%) rename Sources/{SourceDocs => SourceDocsLib}/Elements/MarkdownProtocol.swift (75%) rename Sources/{SourceDocs => SourceDocsLib}/Elements/MarkdownTypealias.swift (81%) rename Sources/{SourceDocs => SourceDocsLib}/Elements/MarkdownVariable.swift (84%) create mode 100644 Sources/SourceDocsLib/Extensions/Dictionary+Sourcedocs.swift rename Sources/{SourceDocs => SourceDocsLib}/Extensions/SourceDocsError+Sourcedocs.swift (79%) rename Sources/{SourceDocs => SourceDocsLib}/Extensions/String+Sourcedocs.swift (100%) rename Sources/{SourceDocs => SourceDocsLib}/Protocols/Documentable.swift (69%) rename Sources/{SourceDocs => SourceDocsLib}/Protocols/SwiftDocDictionaryInitializable.swift (91%) rename Sources/{SourceDocs => SourceDocsLib}/Protocols/Writeable.swift (100%) create mode 100644 Sources/SourceDocsLib/SourceDocs.swift create mode 100644 Tests/SourceDocsTests/SourceDocsTests.swift delete mode 100644 Tests/SourceDocsTests/SwiftDocDictionaryTests.swift diff --git a/Package.resolved b/Package.resolved index 18baac0..4c5c59c 100644 --- a/Package.resolved +++ b/Package.resolved @@ -88,7 +88,7 @@ "state": { "branch": null, "revision": "618582e09699b577fa183bab7d88e3ee7d9a1d19", - "version": "0.6.0" + "version": "1.0.0" } } ] diff --git a/Package.swift b/Package.swift index 3a1d9cc..621912a 100644 --- a/Package.swift +++ b/Package.swift @@ -14,14 +14,15 @@ let package = Package( .package(url: "https://github.com/thoughtbot/Curry.git", from: "4.0.1"), ], targets: [ - .target(name: "SourceDocs", dependencies: [ + .target(name: "SourceDocsLib", dependencies: [ "SourceKittenFramework", "MarkdownGenerator", "Rainbow", "Commandant", "Curry" ]), - .testTarget(name: "SourceDocsTests", dependencies: ["SourceDocs"]), + .target(name: "SourceDocs", dependencies: ["SourceDocsLib"]), + .testTarget(name: "SourceDocsTests", dependencies: ["SourceDocsLib"]), .target(name: "SourceDocsDemo", dependencies: []), ] ) diff --git a/Sources/SourceDocs/Classes/SourceDocs.swift b/Sources/SourceDocs/Classes/SourceDocs.swift deleted file mode 100644 index 50de6eb..0000000 --- a/Sources/SourceDocs/Classes/SourceDocs.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// SourceDocs.swift -// SourceDocs -// -// Created by Eneko Alonso on 10/5/17. -// - -import Foundation -import Commandant -import Rainbow - -struct SourceDocs { - static let version = "0.6.0" - static let defaultOutputDirectory = "Documentation/Reference" - static let defaultContentsFilename = "README" - static let defaultStatusFilename = "documentation_status.json" - static let defaultCoverageSvgFilename = "documentation_coverage.svg" - - func run() { - let registry = CommandRegistry() - registry.register(CleanCommand()) - registry.register(GenerateCommand()) - registry.register(VersionCommand()) - registry.register(HelpCommand(registry: registry)) - - registry.main(defaultVerb: "help") { error in - fputs("\(error.localizedDescription)\n)".red, stderr) - } - } - -} diff --git a/Sources/SourceDocs/Extensions/Dictionary+Sourcedocs.swift b/Sources/SourceDocs/Extensions/Dictionary+Sourcedocs.swift deleted file mode 100644 index 2640a57..0000000 --- a/Sources/SourceDocs/Extensions/Dictionary+Sourcedocs.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// SwiftDocDictionary.swift -// sourcekitten -// -// Created by Eneko Alonso on 10/3/17. -// - -import Foundation -import SourceKittenFramework -import MarkdownGenerator - -typealias SwiftDocDictionary = [String: Any] - -extension Dictionary where Key == String, Value == Any { - var hasPublicACL: Bool { - let accessLevel: String? = get(.accessibility) - return accessLevel == "source.lang.swift.accessibility.public" || accessLevel == "source.lang.swift.accessibility.open" - } - - func get(_ key: SwiftDocKey) -> T? { - return self[key.rawValue] as? T - } - - func isKind(_ kind: SwiftDeclarationKind) -> Bool { - return SwiftDeclarationKind(rawValue: get(.kind) ?? "") == kind - } - - func isKind(_ kinds: [SwiftDeclarationKind]) -> Bool { - guard let value: String = get(.kind), let kind = SwiftDeclarationKind(rawValue: value) else { - return false - } - return kinds.contains(kind) - } -} - diff --git a/Sources/SourceDocs/main.swift b/Sources/SourceDocs/main.swift index d43715c..928defa 100644 --- a/Sources/SourceDocs/main.swift +++ b/Sources/SourceDocs/main.swift @@ -1,3 +1,4 @@ import Foundation +import SourceDocsLib -SourceDocs().run() +run() diff --git a/Sources/SourceDocsLib/Classes/Constants.swift b/Sources/SourceDocsLib/Classes/Constants.swift new file mode 100644 index 0000000..7c9001c --- /dev/null +++ b/Sources/SourceDocsLib/Classes/Constants.swift @@ -0,0 +1,17 @@ +// +// Constants.swift +// SourceDocs +// +// Created by Jim Hildensperger on 13/08/2018. +// + +import Foundation + +struct Constants { + static let version = "0.6.0" + static let defaultOutputDirectory = "Documentation/Reference" + static let defaultContentsFilename = "README" + static let defaultStatusFilename = "documentation_status.json" + static let defaultCoverageSvgFilename = "documentation_coverage.svg" + static let defaultAccessLevelString = SwiftAccessLevel.public.rawValue +} diff --git a/Sources/SourceDocs/Classes/CoverageBadge.swift b/Sources/SourceDocsLib/Classes/CoverageBadge.swift similarity index 97% rename from Sources/SourceDocs/Classes/CoverageBadge.swift rename to Sources/SourceDocsLib/Classes/CoverageBadge.swift index 72bc6db..0b4c859 100644 --- a/Sources/SourceDocs/Classes/CoverageBadge.swift +++ b/Sources/SourceDocsLib/Classes/CoverageBadge.swift @@ -13,7 +13,7 @@ struct CoverageBadge: Writeable { let basePath: String var filePath: String { - return basePath + "/" + SourceDocs.defaultCoverageSvgFilename + return basePath + "/" + Constants.defaultCoverageSvgFilename } private var colorForCoverage: String { diff --git a/Sources/SourceDocs/Classes/DocumentationStatus.swift b/Sources/SourceDocsLib/Classes/DocumentationStatus.swift similarity index 100% rename from Sources/SourceDocs/Classes/DocumentationStatus.swift rename to Sources/SourceDocsLib/Classes/DocumentationStatus.swift diff --git a/Sources/SourceDocs/Classes/DocumentationStatusFile.swift b/Sources/SourceDocsLib/Classes/DocumentationStatusFile.swift similarity index 85% rename from Sources/SourceDocs/Classes/DocumentationStatusFile.swift rename to Sources/SourceDocsLib/Classes/DocumentationStatusFile.swift index 0409218..6f12cff 100644 --- a/Sources/SourceDocs/Classes/DocumentationStatusFile.swift +++ b/Sources/SourceDocsLib/Classes/DocumentationStatusFile.swift @@ -12,7 +12,7 @@ struct DocumentationStatusFile: Writeable { let status: DocumentationStatus var filePath: String { - return basePath + "/" + SourceDocs.defaultStatusFilename + return basePath + "/" + Constants.defaultStatusFilename } func write() throws { diff --git a/Sources/SourceDocs/Classes/MarkdownIndex.swift b/Sources/SourceDocsLib/Classes/MarkdownIndex.swift similarity index 71% rename from Sources/SourceDocs/Classes/MarkdownIndex.swift rename to Sources/SourceDocsLib/Classes/MarkdownIndex.swift index 2153089..2088121 100644 --- a/Sources/SourceDocs/Classes/MarkdownIndex.swift +++ b/Sources/SourceDocsLib/Classes/MarkdownIndex.swift @@ -21,6 +21,7 @@ class MarkdownIndex: Writeable { let filePath: String private let filename: String + private let accessLevel: SwiftAccessLevel private var structs: [MarkdownObject] = [] private var classes: [MarkdownObject] = [] private var extensions: [MarkdownExtension] = [] @@ -28,27 +29,38 @@ class MarkdownIndex: Writeable { private var protocols: [MarkdownProtocol] = [] private var typealiases: [MarkdownTypealias] = [] - init(basePath: String, docs: [SwiftDocs], options: GenerateCommandOptions) { + init?(basePath: String, docs: [SwiftDocs], options: GenerateCommandOptions) { self.basePath = basePath self.filename = options.contentsFileName self.filePath = basePath + "/" + filename - self.process(docs: docs, options: options) + + guard let accessLevel = SwiftAccessLevel(rawValue: options.accessLevelString) else { + return nil + } + + self.accessLevel = accessLevel + + let markdownOptions = MarkdownOptions(collapsibleBlocks: options.collapsibleBlocks, tableOfContents: options.tableOfContents) + let dictionaries = docs.compactMap { $0.docsDictionary.bridge() as? SwiftDocDictionary } + + process(dictionaries: dictionaries, markdownOptions: markdownOptions) } func write() throws { let flattenedExtensions = self.flattenedExtensions() - + let documenatables: [[Documentable]] = [protocols, structs, classes, enums, flattenedExtensions, typealiases] + fputs("Generating Markdown documentation...\n".green, stdout) - let status = [protocols, structs, classes, enums, flattenedExtensions, typealiases].compactMap { - guard let documentables = $0 as? [Documentable] else { - return nil - } - - return documentables.reduce(DocumentationStatus()) { (result, documentable) in - return result + documentable.checkDocumentation() + let status = documenatables.compactMap { + $0.reduce(DocumentationStatus()) { (result, documentable) in + return result + documentable.getDocumentationStatus() } }.reduce(DocumentationStatus(), +) + + guard status.interfaceCount > 0 else { + throw NSError(domain: "No interfaces for the access level: ", code: 9999, userInfo: nil) + } let coverage = Int(status.precentage * 100) @@ -56,9 +68,9 @@ class MarkdownIndex: Writeable { """ # Reference Documentation This Reference Documentation has been generated with - [SourceDocs v\(SourceDocs.version)](https://github.com/jhildensperger/SourceDocs). + [SourceDocs v\(Constants.version)](https://github.com/jhildensperger/SourceDocs). - ![\(coverage)%](\(SourceDocs.defaultCoverageSvgFilename)) + ![\(coverage)%](\(Constants.defaultCoverageSvgFilename)) """ ] @@ -75,21 +87,19 @@ class MarkdownIndex: Writeable { fputs("Done 🎉\n".green, stdout) } - func addItem(from dictionary: SwiftDocDictionary, options: GenerateCommandOptions) { - let markdownOptions = MarkdownOptions(collapsibleBlocks: options.collapsibleBlocks, tableOfContents: options.tableOfContents) - - if let value: String = dictionary.get(.kind), let kind = SwiftDeclarationKind(rawValue: value) { - if kind == .struct, let item = MarkdownObject(dictionary: dictionary, options: markdownOptions) { + func addItem(from dictionary: SwiftDocDictionary, markdownOptions: MarkdownOptions) { + if let value: String = dictionary[.kind], let kind = SwiftDeclarationKind(rawValue: value) { + if kind == .struct, let item = MarkdownObject(dictionary: dictionary, options: markdownOptions, accessLevel: accessLevel) { structs.append(item) - } else if kind == .class, let item = MarkdownObject(dictionary: dictionary, options: markdownOptions) { + } else if kind == .class, let item = MarkdownObject(dictionary: dictionary, options: markdownOptions, accessLevel: accessLevel) { classes.append(item) - } else if let item = MarkdownExtension(dictionary: dictionary, options: markdownOptions) { + } else if let item = MarkdownExtension(dictionary: dictionary, options: markdownOptions, accessLevel: accessLevel) { extensions.append(item) - } else if let item = MarkdownEnum(dictionary: dictionary, options: markdownOptions) { + } else if let item = MarkdownEnum(dictionary: dictionary, options: markdownOptions, accessLevel: accessLevel) { enums.append(item) - } else if let item = MarkdownProtocol(dictionary: dictionary, options: markdownOptions) { + } else if let item = MarkdownProtocol(dictionary: dictionary, options: markdownOptions, accessLevel: accessLevel) { protocols.append(item) - } else if let item = MarkdownTypealias(dictionary: dictionary, options: markdownOptions) { + } else if let item = MarkdownTypealias(dictionary: dictionary, options: markdownOptions, accessLevel: accessLevel) { typealiases.append(item) } } @@ -97,20 +107,15 @@ class MarkdownIndex: Writeable { // MARK: - Private - private func process(docs: [SwiftDocs], options: GenerateCommandOptions) { - let dictionaries = docs.compactMap { $0.docsDictionary.bridge() as? SwiftDocDictionary } - process(dictionaries: dictionaries, options: options) - } - - private func process(dictionaries: [SwiftDocDictionary], options: GenerateCommandOptions) { - dictionaries.forEach { process(dictionary: $0, options: options) } + private func process(dictionaries: [SwiftDocDictionary], markdownOptions: MarkdownOptions) { + dictionaries.forEach { process(dictionary: $0, markdownOptions: markdownOptions) } } - private func process(dictionary: SwiftDocDictionary, options: GenerateCommandOptions) { - addItem(from: dictionary, options: options) + private func process(dictionary: SwiftDocDictionary, markdownOptions: MarkdownOptions) { + addItem(from: dictionary, markdownOptions: markdownOptions) if let substructure = dictionary[SwiftDocKey.substructure.rawValue] as? [SwiftDocDictionary] { - process(dictionaries: substructure, options: options) + process(dictionaries: substructure, markdownOptions: markdownOptions) } } diff --git a/Sources/SourceDocs/Classes/SwiftDocDiscussionKey.swift b/Sources/SourceDocsLib/Classes/SwiftDocDiscussionKey.swift similarity index 100% rename from Sources/SourceDocs/Classes/SwiftDocDiscussionKey.swift rename to Sources/SourceDocsLib/Classes/SwiftDocDiscussionKey.swift diff --git a/Sources/SourceDocs/Commands/Clean.swift b/Sources/SourceDocsLib/Commands/Clean.swift similarity index 96% rename from Sources/SourceDocs/Commands/Clean.swift rename to Sources/SourceDocsLib/Commands/Clean.swift index ed9d5f2..aa5167a 100644 --- a/Sources/SourceDocs/Commands/Clean.swift +++ b/Sources/SourceDocsLib/Commands/Clean.swift @@ -16,8 +16,8 @@ struct CleanCommandOptions: OptionsProtocol { static func evaluate(_ mode: CommandMode) -> Result> { return curry(self.init) - <*> mode <| Option(key: "output-folder", defaultValue: SourceDocs.defaultOutputDirectory, - usage: "Output directory (defaults to \(SourceDocs.defaultOutputDirectory)).") + <*> mode <| Option(key: "output-folder", defaultValue: Constants.defaultOutputDirectory, + usage: "Output directory (defaults to \(Constants.defaultOutputDirectory)).") } } diff --git a/Sources/SourceDocs/Commands/Generate.swift b/Sources/SourceDocsLib/Commands/Generate.swift similarity index 91% rename from Sources/SourceDocs/Commands/Generate.swift rename to Sources/SourceDocsLib/Commands/Generate.swift index 77671b6..a39d05c 100644 --- a/Sources/SourceDocs/Commands/Generate.swift +++ b/Sources/SourceDocsLib/Commands/Generate.swift @@ -18,6 +18,7 @@ struct GenerateCommandOptions: OptionsProtocol { let outputDirectory: String let sourceDirectory: String? let contentsFileName: String + let accessLevelString: String let includeModuleNameInPath: Bool let clean: Bool let collapsibleBlocks: Bool @@ -30,12 +31,14 @@ struct GenerateCommandOptions: OptionsProtocol { usage: "Generate documentation for Swift Package Manager module.") <*> mode <| Option(key: "module-name", defaultValue: nil, usage: "Generate documentation for a Swift module.") - <*> mode <| Option(key: "output", defaultValue: SourceDocs.defaultOutputDirectory, - usage: "Output directory (defaults to \(SourceDocs.defaultOutputDirectory)).") + <*> mode <| Option(key: "output", defaultValue: Constants.defaultOutputDirectory, + usage: "Output directory (defaults to \(Constants.defaultOutputDirectory)).") <*> mode <| Option(key: "source", defaultValue: nil, usage: "Output directory (defaults to the current directory).") - <*> mode <| Option(key: "contents-filename", defaultValue: SourceDocs.defaultContentsFilename, - usage: "Output file (defaults to \(SourceDocs.defaultContentsFilename)).") + <*> mode <| Option(key: "contents-filename", defaultValue: Constants.defaultContentsFilename, + usage: "Output file (defaults to \(Constants.defaultContentsFilename)).") + <*> mode <| Option(key: "access-level", defaultValue: Constants.defaultAccessLevelString, + usage: "Output directory (defaults to the current directory).") <*> mode <| Switch(flag: "m", key: "module-name-path", usage: "Include the module name as part of the output folder path.") <*> mode <| Switch(flag: "c", key: "clean", @@ -51,10 +54,10 @@ struct GenerateCommandOptions: OptionsProtocol { struct GenerateCommand: CommandProtocol { typealias Options = GenerateCommandOptions - private let initialPath = FileManager.default.currentDirectoryPath - let verb = "generate" let function = "Generates the Markdown documentation" + + private let initialPath = FileManager.default.currentDirectoryPath func run(_ options: GenerateCommandOptions) -> Result<(), SourceDocsError> { do { @@ -80,6 +83,8 @@ struct GenerateCommand: CommandProtocol { } } + // MARK:- Private + private func parseSPMModule(moduleName: String) throws -> [SwiftDocs] { guard let docs = Module(spmName: moduleName)?.docs else { let message = "Error: Failed to generate documentation for SPM module '\(moduleName)'." @@ -113,7 +118,7 @@ struct GenerateCommand: CommandProtocol { try CleanCommand.removeReferenceDocs(docsPath: docsPath) } - try MarkdownIndex(basePath: docsPath, docs: docs, options: options).write() + try MarkdownIndex(basePath: docsPath, docs: docs, options: options)?.write() } } diff --git a/Sources/SourceDocs/Commands/Version.swift b/Sources/SourceDocsLib/Commands/Version.swift similarity index 88% rename from Sources/SourceDocs/Commands/Version.swift rename to Sources/SourceDocsLib/Commands/Version.swift index ec294ea..72c8c31 100644 --- a/Sources/SourceDocs/Commands/Version.swift +++ b/Sources/SourceDocsLib/Commands/Version.swift @@ -16,7 +16,7 @@ struct VersionCommand: CommandProtocol { let function = "Display the current version of SourceDocs" func run(_ options: NoOptions) -> Result<(), SourceDocsError> { - fputs("SourceDocs v\(SourceDocs.version)\n".cyan, stdout) + fputs("SourceDocs v\(Constants.version)\n".cyan, stdout) return .success(()) } } diff --git a/Sources/SourceDocs/Elements/MarkdownEnum.swift b/Sources/SourceDocsLib/Elements/MarkdownEnum.swift similarity index 85% rename from Sources/SourceDocs/Elements/MarkdownEnum.swift rename to Sources/SourceDocsLib/Elements/MarkdownEnum.swift index d7daa12..95dc603 100644 --- a/Sources/SourceDocs/Elements/MarkdownEnum.swift +++ b/Sources/SourceDocsLib/Elements/MarkdownEnum.swift @@ -21,23 +21,24 @@ struct MarkdownEnum: SwiftDocDictionaryInitializable, MarkdownConvertible, Docum fatalError("Not supported") } - init?(dictionary: SwiftDocDictionary, options: MarkdownOptions) { - guard dictionary.hasPublicACL && dictionary.isKind([.enum]) else { + init?(dictionary: SwiftDocDictionary, options: MarkdownOptions, accessLevel: SwiftAccessLevel) { + + guard dictionary.isAccessible(for: accessLevel) && dictionary.isKind([.enum]) else { return nil } self.dictionary = dictionary self.options = options - if let structure: [SwiftDocDictionary] = dictionary.get(.substructure) { + if let structure: [SwiftDocDictionary] = dictionary[.substructure] { cases = structure.compactMap { - guard let substructure: [SwiftDocDictionary] = $0.get(.substructure), + guard let substructure: [SwiftDocDictionary] = $0[.substructure], let first = substructure.first else { return nil } return MarkdownEnumCaseElement(dictionary: first) } - properties = structure.compactMap { MarkdownVariable(dictionary: $0, options: options) } - methods = structure.compactMap { MarkdownMethod(dictionary: $0, options: options) } + properties = structure.compactMap { MarkdownVariable(dictionary: $0, options: options, accessLevel: accessLevel) } + methods = structure.compactMap { MarkdownMethod(dictionary: $0, options: options, accessLevel: accessLevel) } } else { cases = [] properties = [] @@ -102,19 +103,19 @@ struct MarkdownEnum: SwiftDocDictionaryInitializable, MarkdownConvertible, Docum """ } - func checkDocumentation() -> DocumentationStatus { + func getDocumentationStatus() -> DocumentationStatus { var status = DocumentationStatus(self) status += cases.reduce(DocumentationStatus(), { (status: DocumentationStatus, documentable) in - return status + documentable.checkDocumentation() + return status + documentable.getDocumentationStatus() }) status += properties.reduce(DocumentationStatus(), { (status: DocumentationStatus, documentable) in - return status + documentable.checkDocumentation() + return status + documentable.getDocumentationStatus() }) status += methods.reduce(DocumentationStatus(), { (status: DocumentationStatus, documentable) in - return status + documentable.checkDocumentation() + return status + documentable.getDocumentationStatus() }) return status diff --git a/Sources/SourceDocs/Elements/MarkdownExtension.swift b/Sources/SourceDocsLib/Elements/MarkdownExtension.swift similarity index 81% rename from Sources/SourceDocs/Elements/MarkdownExtension.swift rename to Sources/SourceDocsLib/Elements/MarkdownExtension.swift index e1b0056..804290a 100644 --- a/Sources/SourceDocs/Elements/MarkdownExtension.swift +++ b/Sources/SourceDocsLib/Elements/MarkdownExtension.swift @@ -20,7 +20,7 @@ struct MarkdownExtension: SwiftDocDictionaryInitializable, MarkdownConvertible, fatalError("Not supported") } - init?(dictionary: SwiftDocDictionary, options: MarkdownOptions) { + init?(dictionary: SwiftDocDictionary, options: MarkdownOptions, accessLevel: SwiftAccessLevel) { let extensions: [SwiftDeclarationKind] = [ .extension, .extensionEnum, .extensionClass, .extensionStruct, .extensionProtocol ] @@ -30,9 +30,9 @@ struct MarkdownExtension: SwiftDocDictionaryInitializable, MarkdownConvertible, self.dictionary = dictionary self.options = options - if let structure: [SwiftDocDictionary] = dictionary.get(.substructure) { - properties = structure.compactMap { MarkdownVariable(dictionary: $0, options: options) } - methods = structure.compactMap { MarkdownMethod(dictionary: $0, options: options) } + if let structure: [SwiftDocDictionary] = dictionary[.substructure] { + properties = structure.compactMap { MarkdownVariable(dictionary: $0, options: options, accessLevel: accessLevel) } + methods = structure.compactMap { MarkdownMethod(dictionary: $0, options: options, accessLevel: accessLevel) } } else { properties = [] methods = [] @@ -58,15 +58,15 @@ struct MarkdownExtension: SwiftDocDictionaryInitializable, MarkdownConvertible, """ } - func checkDocumentation() -> DocumentationStatus { + func getDocumentationStatus() -> DocumentationStatus { var status = DocumentationStatus(self) status = properties.reduce(DocumentationStatus(), { (status: DocumentationStatus, documentable) in - return status + documentable.checkDocumentation() + return status + documentable.getDocumentationStatus() }) status += methods.reduce(DocumentationStatus(), { (status: DocumentationStatus, documentable) in - return status + documentable.checkDocumentation() + return status + documentable.getDocumentationStatus() }) return status diff --git a/Sources/SourceDocs/Elements/MarkdownMethod.swift b/Sources/SourceDocsLib/Elements/MarkdownMethod.swift similarity index 91% rename from Sources/SourceDocs/Elements/MarkdownMethod.swift rename to Sources/SourceDocsLib/Elements/MarkdownMethod.swift index a664df4..8a3816a 100644 --- a/Sources/SourceDocs/Elements/MarkdownMethod.swift +++ b/Sources/SourceDocsLib/Elements/MarkdownMethod.swift @@ -19,17 +19,17 @@ struct MarkdownMethod: SwiftDocDictionaryInitializable, MarkdownConvertible, Doc fatalError("Not supported") } - init?(dictionary: SwiftDocDictionary, options: MarkdownOptions) { + init?(dictionary: SwiftDocDictionary, options: MarkdownOptions, accessLevel: SwiftAccessLevel) { let methods: [SwiftDeclarationKind] = [ .functionMethodInstance, .functionMethodStatic, .functionMethodClass ] - guard dictionary.hasPublicACL && dictionary.isKind(methods) else { + guard dictionary.isAccessible(for: accessLevel) && dictionary.isKind(methods) else { return nil } self.dictionary = dictionary self.options = options - if let params: [SwiftDocDictionary] = dictionary.get(.docParameters) { + if let params: [SwiftDocDictionary] = dictionary[.docParameters] { parameters = params.compactMap { MarkdownMethodParameter(dictionary: $0) } } else { parameters = [] diff --git a/Sources/SourceDocs/Elements/MarkdownObject.swift b/Sources/SourceDocsLib/Elements/MarkdownObject.swift similarity index 83% rename from Sources/SourceDocs/Elements/MarkdownObject.swift rename to Sources/SourceDocsLib/Elements/MarkdownObject.swift index 723941c..5f14ece 100644 --- a/Sources/SourceDocs/Elements/MarkdownObject.swift +++ b/Sources/SourceDocsLib/Elements/MarkdownObject.swift @@ -20,16 +20,16 @@ struct MarkdownObject: SwiftDocDictionaryInitializable, MarkdownConvertible, Doc fatalError("Not supported") } - init?(dictionary: SwiftDocDictionary, options: MarkdownOptions) { - guard dictionary.hasPublicACL && dictionary.isKind([.struct, .class]) else { + init?(dictionary: SwiftDocDictionary, options: MarkdownOptions, accessLevel: SwiftAccessLevel) { + guard dictionary.isAccessible(for: accessLevel) && dictionary.isKind([.struct, .class]) else { return nil } self.dictionary = dictionary self.options = options - if let structure: [SwiftDocDictionary] = dictionary.get(.substructure) { - properties = structure.compactMap { MarkdownVariable(dictionary: $0, options: options) } - methods = structure.compactMap { MarkdownMethod(dictionary: $0, options: options) } + if let structure: [SwiftDocDictionary] = dictionary[.substructure] { + properties = structure.compactMap { MarkdownVariable(dictionary: $0, options: options, accessLevel: accessLevel) } + methods = structure.compactMap { MarkdownMethod(dictionary: $0, options: options, accessLevel: accessLevel) } } else { properties = [] methods = [] @@ -94,15 +94,15 @@ struct MarkdownObject: SwiftDocDictionaryInitializable, MarkdownConvertible, Doc """ } - func checkDocumentation() -> DocumentationStatus { + func getDocumentationStatus() -> DocumentationStatus { var status = DocumentationStatus(self) status += properties.reduce(DocumentationStatus(), { (status: DocumentationStatus, documentable) in - return status + documentable.checkDocumentation() + return status + documentable.getDocumentationStatus() }) status += methods.reduce(DocumentationStatus(), { (status: DocumentationStatus, documentable) in - return status + documentable.checkDocumentation() + return status + documentable.getDocumentationStatus() }) return status diff --git a/Sources/SourceDocs/Elements/MarkdownProtocol.swift b/Sources/SourceDocsLib/Elements/MarkdownProtocol.swift similarity index 75% rename from Sources/SourceDocs/Elements/MarkdownProtocol.swift rename to Sources/SourceDocsLib/Elements/MarkdownProtocol.swift index ee85068..b42f429 100644 --- a/Sources/SourceDocs/Elements/MarkdownProtocol.swift +++ b/Sources/SourceDocsLib/Elements/MarkdownProtocol.swift @@ -20,16 +20,16 @@ struct MarkdownProtocol: SwiftDocDictionaryInitializable, MarkdownConvertible, D fatalError("Not supported") } - init?(dictionary: SwiftDocDictionary, options: MarkdownOptions) { - guard dictionary.hasPublicACL && dictionary.isKind([.protocol]) else { + init?(dictionary: SwiftDocDictionary, options: MarkdownOptions, accessLevel: SwiftAccessLevel) { + guard dictionary.isAccessible(for: accessLevel) && dictionary.isKind([.protocol]) else { return nil } self.dictionary = dictionary self.options = options - if let structure: [SwiftDocDictionary] = dictionary.get(.substructure) { - properties = structure.compactMap { MarkdownVariable(dictionary: $0, options: options) } - methods = structure.compactMap { MarkdownMethod(dictionary: $0, options: options) } + if let structure: [SwiftDocDictionary] = dictionary[.substructure] { + properties = structure.compactMap { MarkdownVariable(dictionary: $0, options: options, accessLevel: accessLevel) } + methods = structure.compactMap { MarkdownMethod(dictionary: $0, options: options, accessLevel: accessLevel) } } else { properties = [] methods = [] @@ -54,15 +54,15 @@ struct MarkdownProtocol: SwiftDocDictionaryInitializable, MarkdownConvertible, D """ } - func checkDocumentation() -> DocumentationStatus { + func getDocumentationStatus() -> DocumentationStatus { var status = DocumentationStatus(self) status += properties.reduce(DocumentationStatus(), { (status: DocumentationStatus, documentable) in - return status + documentable.checkDocumentation() + return status + documentable.getDocumentationStatus() }) status += methods.reduce(DocumentationStatus(), { (status: DocumentationStatus, documentable) in - return status + documentable.checkDocumentation() + return status + documentable.getDocumentationStatus() }) return status diff --git a/Sources/SourceDocs/Elements/MarkdownTypealias.swift b/Sources/SourceDocsLib/Elements/MarkdownTypealias.swift similarity index 81% rename from Sources/SourceDocs/Elements/MarkdownTypealias.swift rename to Sources/SourceDocsLib/Elements/MarkdownTypealias.swift index 6eb955f..21fd8f0 100644 --- a/Sources/SourceDocs/Elements/MarkdownTypealias.swift +++ b/Sources/SourceDocsLib/Elements/MarkdownTypealias.swift @@ -17,8 +17,8 @@ struct MarkdownTypealias: SwiftDocDictionaryInitializable, MarkdownConvertible, fatalError("Not supported") } - init?(dictionary: SwiftDocDictionary, options: MarkdownOptions) { - guard dictionary.hasPublicACL && dictionary.isKind([.protocol, .class, .enum, .struct, .typealias]) else { + init?(dictionary: SwiftDocDictionary, options: MarkdownOptions, accessLevel: SwiftAccessLevel) { + guard dictionary.isAccessible(for: accessLevel) && dictionary.isKind([.protocol, .class, .enum, .struct, .typealias]) else { return nil } self.dictionary = dictionary diff --git a/Sources/SourceDocs/Elements/MarkdownVariable.swift b/Sources/SourceDocsLib/Elements/MarkdownVariable.swift similarity index 84% rename from Sources/SourceDocs/Elements/MarkdownVariable.swift rename to Sources/SourceDocsLib/Elements/MarkdownVariable.swift index 7ded502..00b7b1f 100644 --- a/Sources/SourceDocs/Elements/MarkdownVariable.swift +++ b/Sources/SourceDocsLib/Elements/MarkdownVariable.swift @@ -17,8 +17,8 @@ struct MarkdownVariable: SwiftDocDictionaryInitializable, MarkdownConvertible, D fatalError("Not supported") } - init?(dictionary: SwiftDocDictionary, options: MarkdownOptions) { - guard dictionary.hasPublicACL && dictionary.isKind([.varInstance, .varStatic, .varClass, .varParameter]) else { + init?(dictionary: SwiftDocDictionary, options: MarkdownOptions, accessLevel: SwiftAccessLevel) { + guard dictionary.isAccessible(for: accessLevel) && dictionary.isKind([.varInstance, .varStatic, .varClass, .varParameter]) else { return nil } self.dictionary = dictionary diff --git a/Sources/SourceDocsLib/Extensions/Dictionary+Sourcedocs.swift b/Sources/SourceDocsLib/Extensions/Dictionary+Sourcedocs.swift new file mode 100644 index 0000000..533db96 --- /dev/null +++ b/Sources/SourceDocsLib/Extensions/Dictionary+Sourcedocs.swift @@ -0,0 +1,60 @@ +// +// SwiftDocDictionary.swift +// sourcekitten +// +// Created by Eneko Alonso on 10/3/17. +// + +import Foundation +import SourceKittenFramework +import MarkdownGenerator + +typealias SwiftDocDictionary = [String: Any] + +enum SwiftAccessLevel: String { + case `open` + case `public` + case `internal` + case `fileprivate` + case `private` + + init?(sourceKitAccessibility: String) { + let rawValue = sourceKitAccessibility.replacingOccurrences(of: "source.lang.swift.accessibility.", with: "") + self.init(rawValue: rawValue) + } + + func isGreaterThanOrEqual(to level: SwiftAccessLevel) -> Bool { + switch self { + case .open: return true + case .public: return level != .open + case .internal: return level != .open && level != .public + case .fileprivate: return level != .open && level != .public && level != .internal + case .private: return level != .open && level != .public && level != .internal && level != .fileprivate + } + } +} + +extension Dictionary where Key == String, Value == Any { + func isAccessible(for level: SwiftAccessLevel) -> Bool { + guard let accessLevel = SwiftAccessLevel(sourceKitAccessibility: self[.accessibility] ?? "") else { + return false + } + return accessLevel.isGreaterThanOrEqual(to: level) + } + + subscript(_ key: SwiftDocKey) -> T? { + return self[key.rawValue] as? T + } + + func isKind(_ kind: SwiftDeclarationKind) -> Bool { + return SwiftDeclarationKind(rawValue: self[.kind] ?? "") == kind + } + + func isKind(_ kinds: [SwiftDeclarationKind]) -> Bool { + guard let kind = SwiftDeclarationKind(rawValue: self[.kind] ?? "") else { + return false + } + return kinds.contains(kind) + } +} + diff --git a/Sources/SourceDocs/Extensions/SourceDocsError+Sourcedocs.swift b/Sources/SourceDocsLib/Extensions/SourceDocsError+Sourcedocs.swift similarity index 79% rename from Sources/SourceDocs/Extensions/SourceDocsError+Sourcedocs.swift rename to Sources/SourceDocsLib/Extensions/SourceDocsError+Sourcedocs.swift index 0d2216f..31235f7 100644 --- a/Sources/SourceDocs/Extensions/SourceDocsError+Sourcedocs.swift +++ b/Sources/SourceDocsLib/Extensions/SourceDocsError+Sourcedocs.swift @@ -7,12 +7,12 @@ import Foundation -enum SourceDocsError: Error { +public enum SourceDocsError: Error { case internalError(message: String) } extension SourceDocsError: LocalizedError { - var errorDescription: String? { + public var errorDescription: String? { switch self { case let .internalError(message): return message diff --git a/Sources/SourceDocs/Extensions/String+Sourcedocs.swift b/Sources/SourceDocsLib/Extensions/String+Sourcedocs.swift similarity index 100% rename from Sources/SourceDocs/Extensions/String+Sourcedocs.swift rename to Sources/SourceDocsLib/Extensions/String+Sourcedocs.swift diff --git a/Sources/SourceDocs/Protocols/Documentable.swift b/Sources/SourceDocsLib/Protocols/Documentable.swift similarity index 69% rename from Sources/SourceDocs/Protocols/Documentable.swift rename to Sources/SourceDocsLib/Protocols/Documentable.swift index 7e15870..c5cc58b 100644 --- a/Sources/SourceDocs/Protocols/Documentable.swift +++ b/Sources/SourceDocsLib/Protocols/Documentable.swift @@ -8,11 +8,11 @@ import Foundation protocol Documentable { - func checkDocumentation() -> DocumentationStatus + func getDocumentationStatus() -> DocumentationStatus } extension Documentable where Self: SwiftDocDictionaryInitializable { - func checkDocumentation() -> DocumentationStatus { + func getDocumentationStatus() -> DocumentationStatus { return DocumentationStatus(self) } } diff --git a/Sources/SourceDocs/Protocols/SwiftDocDictionaryInitializable.swift b/Sources/SourceDocsLib/Protocols/SwiftDocDictionaryInitializable.swift similarity index 91% rename from Sources/SourceDocs/Protocols/SwiftDocDictionaryInitializable.swift rename to Sources/SourceDocsLib/Protocols/SwiftDocDictionaryInitializable.swift index 89e1399..8f68efa 100644 --- a/Sources/SourceDocs/Protocols/SwiftDocDictionaryInitializable.swift +++ b/Sources/SourceDocsLib/Protocols/SwiftDocDictionaryInitializable.swift @@ -31,7 +31,7 @@ extension SwiftDocDictionaryInitializable { // MARK: - Public Properties var name: String { - return dictionary.get(.name) ?? "[NO NAME]" + return dictionary[.name] ?? "[NO NAME]" } var comment: String { @@ -39,13 +39,13 @@ extension SwiftDocDictionaryInitializable { } var declaration: String { - let declaration: String = dictionary.get(.docDeclaration) ?? dictionary.get(.parsedDeclaration) ?? "" + let declaration: String = dictionary[.docDeclaration] ?? dictionary[.parsedDeclaration] ?? "" guard declaration.isEmpty else { return MarkdownCodeBlock(code: declaration, style: .backticks(language: "swift")).markdown } - guard let parseDeclaration: String = dictionary.get(.parsedDeclaration) else { + guard let parseDeclaration: String = dictionary[.parsedDeclaration] else { return "" } return MarkdownCodeBlock(code: parseDeclaration, style: .backticks(language: "swift")).markdown @@ -53,11 +53,11 @@ extension SwiftDocDictionaryInitializable { } var debugInfo: [String: Any] { - let file = (dictionary.get(.filePath) ?? "").replacingOccurrences(of: FileManager.default.currentDirectoryPath, with: "") + let file = (dictionary[.filePath] ?? "").replacingOccurrences(of: FileManager.default.currentDirectoryPath, with: "") return [ "name": name, - "declaration": dictionary.get(.parsedDeclaration) ?? "", + "declaration": dictionary[.parsedDeclaration] ?? "", "file": file, "isDocumented": !comment.isEmpty ] @@ -66,14 +66,14 @@ extension SwiftDocDictionaryInitializable { // MARK: - Private Properties private var abstract: String? { - guard let text: String = dictionary.get(.docAbstract) else { + guard let text: String = dictionary[.docAbstract] else { return nil } return text } private var discussion: String? { - guard var xmlString: String = dictionary.get(.fullXMLDocs) else { + guard var xmlString: String = dictionary[.fullXMLDocs] else { return nil } @@ -86,7 +86,7 @@ extension SwiftDocDictionaryInitializable { xmlString.replacingMatches(RegularExpression.link, with: "[$2]($1)") /// Replace all opening header tags with the cooresponding number of #'s - (1...6).forEach { + (1...4).forEach { let headerOpeningTag = "]]>" let replacement = String(repeating: "#", count: $0) + " " xmlString = xmlString.replacingOccurrences(of: headerOpeningTag, with: replacement) @@ -166,7 +166,7 @@ extension SwiftDocDictionaryInitializable { var keyFound = false var string = "" - guard let discussions: [[String: String]] = dictionary.get(.docDiscussion) else { + guard let discussions: [[String: String]] = dictionary[.docDiscussion] else { return string } diff --git a/Sources/SourceDocs/Protocols/Writeable.swift b/Sources/SourceDocsLib/Protocols/Writeable.swift similarity index 100% rename from Sources/SourceDocs/Protocols/Writeable.swift rename to Sources/SourceDocsLib/Protocols/Writeable.swift diff --git a/Sources/SourceDocsLib/SourceDocs.swift b/Sources/SourceDocsLib/SourceDocs.swift new file mode 100644 index 0000000..55816c6 --- /dev/null +++ b/Sources/SourceDocsLib/SourceDocs.swift @@ -0,0 +1,22 @@ +// +// SourceDocs.swift +// SourceDocsLib +// +// Created by Jim Hildensperger on 13/08/2018. +// + +import Foundation +import Commandant +import Rainbow + +public func run() { + let registry = CommandRegistry() + registry.register(CleanCommand()) + registry.register(GenerateCommand()) + registry.register(VersionCommand()) + registry.register(HelpCommand(registry: registry)) + + registry.main(defaultVerb: "help") { error in + fputs("\(error.localizedDescription)\n)".red, stderr) + } +} diff --git a/Tests/SourceDocsTests/SourceDocsTests.swift b/Tests/SourceDocsTests/SourceDocsTests.swift new file mode 100644 index 0000000..6c7bd82 --- /dev/null +++ b/Tests/SourceDocsTests/SourceDocsTests.swift @@ -0,0 +1,15 @@ +// +// SourceDocsTests.swift +// SourceDocsTests +// +// Created by Jim Hildensperger on 13/08/2018. +// + +import XCTest +@testable import SourceDocsLib + +class SourceDocsTests: XCTestCase { + func testRunFunction() { + SourceDocsLib.run() + } +} diff --git a/Tests/SourceDocsTests/SwiftDocDictionaryTests.swift b/Tests/SourceDocsTests/SwiftDocDictionaryTests.swift deleted file mode 100644 index 6829760..0000000 --- a/Tests/SourceDocsTests/SwiftDocDictionaryTests.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// SwiftDocDictionaryTests.swift -// SourceDocs -// -// Created by Eneko Alonso on 10/5/17. -// - -import XCTest - -class SwiftDocDictionaryTests: XCTestCase { - - func testExample() { - XCTAssert(true, "Tests coming soon! I promise :)") - } - - static var allTests = [ - ("testExample", testExample) - ] - -} From cb323531cacd02c345a97c92fc0480e9bc5c43b8 Mon Sep 17 00:00:00 2001 From: Jim Hildensperger Date: Mon, 13 Aug 2018 13:27:36 +0200 Subject: [PATCH 3/6] Move swift access level enum to its own file --- .../Classes/SwiftAccessLevel.swift | 31 +++++++++++++++++++ .../Extensions/Dictionary+Sourcedocs.swift | 23 -------------- 2 files changed, 31 insertions(+), 23 deletions(-) create mode 100644 Sources/SourceDocsLib/Classes/SwiftAccessLevel.swift diff --git a/Sources/SourceDocsLib/Classes/SwiftAccessLevel.swift b/Sources/SourceDocsLib/Classes/SwiftAccessLevel.swift new file mode 100644 index 0000000..1c60244 --- /dev/null +++ b/Sources/SourceDocsLib/Classes/SwiftAccessLevel.swift @@ -0,0 +1,31 @@ +// +// SwiftAccessLevel.swift +// SourceDocs +// +// Created by Jim Hildensperger on 13/08/2018. +// + +import Foundation + +enum SwiftAccessLevel: String { + case `open` + case `public` + case `internal` + case `fileprivate` + case `private` + + init?(sourceKitAccessibility: String) { + let rawValue = sourceKitAccessibility.replacingOccurrences(of: "source.lang.swift.accessibility.", with: "") + self.init(rawValue: rawValue) + } + + func isGreaterThanOrEqual(to level: SwiftAccessLevel) -> Bool { + switch self { + case .open: return true + case .public: return level != .open + case .internal: return level != .open && level != .public + case .fileprivate: return level != .open && level != .public && level != .internal + case .private: return level != .open && level != .public && level != .internal && level != .fileprivate + } + } +} diff --git a/Sources/SourceDocsLib/Extensions/Dictionary+Sourcedocs.swift b/Sources/SourceDocsLib/Extensions/Dictionary+Sourcedocs.swift index 533db96..36bcb61 100644 --- a/Sources/SourceDocsLib/Extensions/Dictionary+Sourcedocs.swift +++ b/Sources/SourceDocsLib/Extensions/Dictionary+Sourcedocs.swift @@ -11,29 +11,6 @@ import MarkdownGenerator typealias SwiftDocDictionary = [String: Any] -enum SwiftAccessLevel: String { - case `open` - case `public` - case `internal` - case `fileprivate` - case `private` - - init?(sourceKitAccessibility: String) { - let rawValue = sourceKitAccessibility.replacingOccurrences(of: "source.lang.swift.accessibility.", with: "") - self.init(rawValue: rawValue) - } - - func isGreaterThanOrEqual(to level: SwiftAccessLevel) -> Bool { - switch self { - case .open: return true - case .public: return level != .open - case .internal: return level != .open && level != .public - case .fileprivate: return level != .open && level != .public && level != .internal - case .private: return level != .open && level != .public && level != .internal && level != .fileprivate - } - } -} - extension Dictionary where Key == String, Value == Any { func isAccessible(for level: SwiftAccessLevel) -> Bool { guard let accessLevel = SwiftAccessLevel(sourceKitAccessibility: self[.accessibility] ?? "") else { From f8aaed829ae319ff30edb90d4d44286b16a69909 Mon Sep 17 00:00:00 2001 From: Jim Hildensperger Date: Tue, 14 Aug 2018 01:01:46 +0200 Subject: [PATCH 4/6] Remove unnecessary public declarations --- .../SourceDocsLib/Extensions/SourceDocsError+Sourcedocs.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/SourceDocsLib/Extensions/SourceDocsError+Sourcedocs.swift b/Sources/SourceDocsLib/Extensions/SourceDocsError+Sourcedocs.swift index 31235f7..0d2216f 100644 --- a/Sources/SourceDocsLib/Extensions/SourceDocsError+Sourcedocs.swift +++ b/Sources/SourceDocsLib/Extensions/SourceDocsError+Sourcedocs.swift @@ -7,12 +7,12 @@ import Foundation -public enum SourceDocsError: Error { +enum SourceDocsError: Error { case internalError(message: String) } extension SourceDocsError: LocalizedError { - public var errorDescription: String? { + var errorDescription: String? { switch self { case let .internalError(message): return message From d1d086c83b451148ff3f623214b2afb508f52562 Mon Sep 17 00:00:00 2001 From: Jim Hildensperger Date: Tue, 14 Aug 2018 01:02:31 +0200 Subject: [PATCH 5/6] Add first functional test --- Tests/SourceDocsTests/SourceDocsTests.swift | 38 +++++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/Tests/SourceDocsTests/SourceDocsTests.swift b/Tests/SourceDocsTests/SourceDocsTests.swift index 6c7bd82..f1065e6 100644 --- a/Tests/SourceDocsTests/SourceDocsTests.swift +++ b/Tests/SourceDocsTests/SourceDocsTests.swift @@ -8,8 +8,40 @@ import XCTest @testable import SourceDocsLib -class SourceDocsTests: XCTestCase { - func testRunFunction() { - SourceDocsLib.run() +let tmpOutputDirectory = "tmp/test/" + +func setCommandLineArguments(_ arguments: String...) { + CommandLine.arguments.removeLast(CommandLine.arguments.count - 1) + CommandLine.arguments.append(contentsOf: arguments) +} + +class GenerateCommandTests: XCTestCase { + override func setUp() { + try? FileManager.default.removeItem(atPath: tmpOutputDirectory) + + XCTAssertFalse(FileManager.default.fileExists(atPath: tmpOutputDirectory)) + } + + func testGenerateOutput() { + let options = GenerateCommandOptions(spmModule: "SourceDocsDemo", + moduleName: nil, + outputDirectory: tmpOutputDirectory, + sourceDirectory: "../../../", + contentsFileName: "README", + accessLevelString: "public", + includeModuleNameInPath: false, + clean: false, + collapsibleBlocks: true, + tableOfContents: true, + xcodeArguments: []) + + let result = GenerateCommand().run(options) + + XCTAssert(FileManager.default.fileExists(atPath: tmpOutputDirectory)) + XCTAssertNil(result.error) } } + +class SourceDocsTests: XCTestCase { + +} From 535b50b01329e06dcc938d6a82e3f15a865293e1 Mon Sep 17 00:00:00 2001 From: Jim Hildensperger Date: Wed, 15 Aug 2018 08:45:14 +0200 Subject: [PATCH 6/6] Add Printer protocol for testability --- .../Classes/ConsolePrinter.swift | 14 +++++++++++ .../SourceDocsLib/Classes/MarkdownIndex.swift | 10 ++++---- Sources/SourceDocsLib/Commands/Clean.swift | 10 ++++---- Sources/SourceDocsLib/Commands/Version.swift | 2 +- Sources/SourceDocsLib/Protocols/Printer.swift | 12 +++++++++ Sources/SourceDocsLib/SourceDocs.swift | 8 +++--- Tests/SourceDocsTests/CommandLine+Utils.swift | 15 +++++++++++ ...ests.swift => GenerateCommanndTests.swift} | 25 ++++++++++--------- Tests/SourceDocsTests/TestPrinter.swift | 17 +++++++++++++ 9 files changed, 87 insertions(+), 26 deletions(-) create mode 100644 Sources/SourceDocsLib/Classes/ConsolePrinter.swift create mode 100644 Sources/SourceDocsLib/Protocols/Printer.swift create mode 100644 Tests/SourceDocsTests/CommandLine+Utils.swift rename Tests/SourceDocsTests/{SourceDocsTests.swift => GenerateCommanndTests.swift} (77%) create mode 100644 Tests/SourceDocsTests/TestPrinter.swift diff --git a/Sources/SourceDocsLib/Classes/ConsolePrinter.swift b/Sources/SourceDocsLib/Classes/ConsolePrinter.swift new file mode 100644 index 0000000..ff8f682 --- /dev/null +++ b/Sources/SourceDocsLib/Classes/ConsolePrinter.swift @@ -0,0 +1,14 @@ +// +// ConsolePrinter.swift +// SourceDocs +// +// Created by Jim Hildensperger on 14/08/2018. +// + +import Foundation + +struct ConsolePrinter: Printer { + func print(_ cstring: UnsafePointer!, _ stream: UnsafeMutablePointer!) { + fputs(cstring, stream) + } +} diff --git a/Sources/SourceDocsLib/Classes/MarkdownIndex.swift b/Sources/SourceDocsLib/Classes/MarkdownIndex.swift index 2088121..cb8a3c3 100644 --- a/Sources/SourceDocsLib/Classes/MarkdownIndex.swift +++ b/Sources/SourceDocsLib/Classes/MarkdownIndex.swift @@ -50,7 +50,7 @@ class MarkdownIndex: Writeable { let flattenedExtensions = self.flattenedExtensions() let documenatables: [[Documentable]] = [protocols, structs, classes, enums, flattenedExtensions, typealiases] - fputs("Generating Markdown documentation...\n".green, stdout) + printer.print("Generating Markdown documentation...\n".green, stdout) let status = documenatables.compactMap { $0.reduce(DocumentationStatus()) { (result, documentable) in @@ -84,7 +84,7 @@ class MarkdownIndex: Writeable { try writeFile(file: CoverageBadge(coverage: coverage, basePath: basePath)) try writeFile(file: MarkdownFile(filename: filename, basePath: basePath, content: content)) try writeFile(file: DocumentationStatusFile(basePath: basePath, status: status)) - fputs("Done 🎉\n".green, stdout) + printer.print("Done 🎉\n".green, stdout) } func addItem(from dictionary: SwiftDocDictionary, markdownOptions: MarkdownOptions) { @@ -141,12 +141,12 @@ class MarkdownIndex: Writeable { } private func writeFile(file: Writeable) throws { - fputs(" Writing documentation file: \(file.filePath)", stdout) + printer.print(" Writing documentation file: \(file.filePath)", stdout) do { try file.write() - fputs(" ✔\n".green, stdout) + printer.print(" ✔\n".green, stdout) } catch let error { - fputs(" ❌\n", stdout) + printer.print(" ❌\n", stdout) throw error } } diff --git a/Sources/SourceDocsLib/Commands/Clean.swift b/Sources/SourceDocsLib/Commands/Clean.swift index aa5167a..b95d563 100644 --- a/Sources/SourceDocsLib/Commands/Clean.swift +++ b/Sources/SourceDocsLib/Commands/Clean.swift @@ -39,10 +39,10 @@ struct CleanCommand: CommandProtocol { static func removeReferenceDocs(docsPath: String) throws { var isDir: ObjCBool = false if FileManager.default.fileExists(atPath: docsPath, isDirectory: &isDir) { - fputs("Removing reference documentation at '\(docsPath)'...".green, stdout) + printer.print("Removing reference documentation at '\(docsPath)'...".green, stdout) guard let paths = try? FileManager.default.contentsOfDirectory(atPath: docsPath) else { - fputs(" ❌\n", stdout) + printer.print(" ❌\n", stdout) throw NSError(domain: "file", code: 9999, userInfo: nil) } @@ -51,14 +51,14 @@ struct CleanCommand: CommandProtocol { guard path.first != "." else { continue } try FileManager.default.removeItem(atPath: docsPath.appending("/\(path)")) } catch let error { - fputs(" ❌\n", stdout) + printer.print(" ❌\n", stdout) throw error } } - fputs(" ✔".green + "\n", stdout) + printer.print(" ✔".green + "\n", stdout) } else { - fputs("Did not find any reference docs at '\(docsPath)'.\n", stdout) + printer.print("Did not find any reference docs at '\(docsPath)'.\n", stdout) } } diff --git a/Sources/SourceDocsLib/Commands/Version.swift b/Sources/SourceDocsLib/Commands/Version.swift index 72c8c31..c095f65 100644 --- a/Sources/SourceDocsLib/Commands/Version.swift +++ b/Sources/SourceDocsLib/Commands/Version.swift @@ -16,7 +16,7 @@ struct VersionCommand: CommandProtocol { let function = "Display the current version of SourceDocs" func run(_ options: NoOptions) -> Result<(), SourceDocsError> { - fputs("SourceDocs v\(Constants.version)\n".cyan, stdout) + printer.print("SourceDocs v\(Constants.version)\n".cyan, stdout) return .success(()) } } diff --git a/Sources/SourceDocsLib/Protocols/Printer.swift b/Sources/SourceDocsLib/Protocols/Printer.swift new file mode 100644 index 0000000..df9a49d --- /dev/null +++ b/Sources/SourceDocsLib/Protocols/Printer.swift @@ -0,0 +1,12 @@ +// +// Printer.swift +// SourceDocs +// +// Created by Jim Hildensperger on 14/08/2018. +// + +import Foundation + +public protocol Printer { + func print(_ cstring: UnsafePointer!, _ stream: UnsafeMutablePointer!) +} diff --git a/Sources/SourceDocsLib/SourceDocs.swift b/Sources/SourceDocsLib/SourceDocs.swift index 55816c6..3333937 100644 --- a/Sources/SourceDocsLib/SourceDocs.swift +++ b/Sources/SourceDocsLib/SourceDocs.swift @@ -9,14 +9,16 @@ import Foundation import Commandant import Rainbow -public func run() { +var printer: Printer = ConsolePrinter() + +public func run() -> Never { let registry = CommandRegistry() registry.register(CleanCommand()) registry.register(GenerateCommand()) registry.register(VersionCommand()) registry.register(HelpCommand(registry: registry)) - + registry.main(defaultVerb: "help") { error in - fputs("\(error.localizedDescription)\n)".red, stderr) + printer.print("\(error.localizedDescription)\n)".red, stderr) } } diff --git a/Tests/SourceDocsTests/CommandLine+Utils.swift b/Tests/SourceDocsTests/CommandLine+Utils.swift new file mode 100644 index 0000000..72acbf4 --- /dev/null +++ b/Tests/SourceDocsTests/CommandLine+Utils.swift @@ -0,0 +1,15 @@ +// +// CommandLineUtils.swift +// SourceDocsTests +// +// Created by Jim Hildensperger on 14/08/2018. +// + +import Foundation + +extension CommandLine { + func setArguments(_ arguments: String...) { + CommandLine.arguments.removeLast(CommandLine.arguments.count - 1) + CommandLine.arguments.append(contentsOf: arguments) + } +} diff --git a/Tests/SourceDocsTests/SourceDocsTests.swift b/Tests/SourceDocsTests/GenerateCommanndTests.swift similarity index 77% rename from Tests/SourceDocsTests/SourceDocsTests.swift rename to Tests/SourceDocsTests/GenerateCommanndTests.swift index f1065e6..555e930 100644 --- a/Tests/SourceDocsTests/SourceDocsTests.swift +++ b/Tests/SourceDocsTests/GenerateCommanndTests.swift @@ -1,20 +1,16 @@ // -// SourceDocsTests.swift +// GenerateCommanndTests.swift // SourceDocsTests // -// Created by Jim Hildensperger on 13/08/2018. +// Created by Jim Hildensperger on 14/08/2018. // +import Foundation import XCTest @testable import SourceDocsLib let tmpOutputDirectory = "tmp/test/" -func setCommandLineArguments(_ arguments: String...) { - CommandLine.arguments.removeLast(CommandLine.arguments.count - 1) - CommandLine.arguments.append(contentsOf: arguments) -} - class GenerateCommandTests: XCTestCase { override func setUp() { try? FileManager.default.removeItem(atPath: tmpOutputDirectory) @@ -23,6 +19,8 @@ class GenerateCommandTests: XCTestCase { } func testGenerateOutput() { + printer = TestPrinter() + let options = GenerateCommandOptions(spmModule: "SourceDocsDemo", moduleName: nil, outputDirectory: tmpOutputDirectory, @@ -37,11 +35,14 @@ class GenerateCommandTests: XCTestCase { let result = GenerateCommand().run(options) + switch result { + case .success: XCTAssert(true) + case .failure: XCTAssert(false) + } + + let testPrinter = printer as! TestPrinter + + XCTAssertEqual(testPrinter.outputs.count, 26) XCTAssert(FileManager.default.fileExists(atPath: tmpOutputDirectory)) - XCTAssertNil(result.error) } } - -class SourceDocsTests: XCTestCase { - -} diff --git a/Tests/SourceDocsTests/TestPrinter.swift b/Tests/SourceDocsTests/TestPrinter.swift new file mode 100644 index 0000000..57d1129 --- /dev/null +++ b/Tests/SourceDocsTests/TestPrinter.swift @@ -0,0 +1,17 @@ +// +// SourceDocsTests.swift +// SourceDocsTests +// +// Created by Jim Hildensperger on 13/08/2018. +// + +import AppKit +@testable import SourceDocsLib + +class TestPrinter: Printer { + var outputs = [String]() + + func print(_ cstring: UnsafePointer!, _: UnsafeMutablePointer!) { + outputs.append(String(cString: cstring)) + } +}