├── .gitignore ├── LICENSE ├── Makefile ├── Package.resolved ├── Package.resources ├── Package.swift ├── Podfile ├── README.md ├── Sources ├── Scripts │ └── xquick.sh └── hackscode │ ├── Commands │ ├── CreateNewFile.swift │ ├── DumpBuildFiles.swift │ ├── RemoveBuildFiles.swift │ └── XQuick.swift │ ├── Utilities.swift │ ├── main.swift │ └── zzz.Sourcery.out.swift └── scripts ├── bump-version.sh └── run-sourcery /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | 6 | # Workaround for CocoaPods 7 | *.xcworkspace 8 | Pods/ 9 | Podfile.lock 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Toshihiro Suzuki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY = clean update build bootstrap 2 | SOURCERY ?= sourcery # Please install appropriate version on your own. 3 | cmdshelf ?= cmdshelf 4 | 5 | clean: 6 | rm -rf .build 7 | rm Package.resolved 8 | 9 | update: 10 | swift package update 11 | 12 | build: 13 | swift build 14 | 15 | bootstrap: 16 | rm -rf Pods Podfile.lock *.xcodeproj # Cleaning up to avoid cocoapods failing to bootstrap from Podfile.lock 17 | swift package generate-xcodeproj # Creating one for CocoaPods to work. 18 | pod install # Installing sourcery for swifttemplate support. 19 | swift package generate-xcodeproj # Discarding cocoapods side effects, gracefully. 20 | 21 | sourcery: 22 | ./scripts/run-sourcery 23 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "AEXML", 6 | "repositoryURL": "https://github.com/tadija/AEXML", 7 | "state": { 8 | "branch": null, 9 | "revision": "e4d517844dd03dac557e35d77a8e9ab438de91a6", 10 | "version": "4.4.0" 11 | } 12 | }, 13 | { 14 | "package": "CoreCLI", 15 | "repositoryURL": "https://github.com/toshi0383/CoreCLI", 16 | "state": { 17 | "branch": null, 18 | "revision": "104c93ffb603b892e2665cac68fe9b4c5b5a7376", 19 | "version": "0.1.10" 20 | } 21 | }, 22 | { 23 | "package": "PathKit", 24 | "repositoryURL": "https://github.com/kylef/PathKit", 25 | "state": { 26 | "branch": null, 27 | "revision": "73f8e9dca9b7a3078cb79128217dc8f2e585a511", 28 | "version": "1.0.0" 29 | } 30 | }, 31 | { 32 | "package": "ShellOut", 33 | "repositoryURL": "https://github.com/JohnSundell/ShellOut", 34 | "state": { 35 | "branch": null, 36 | "revision": "d3d54ce662dfee7fef619330b71d251b8d4869f9", 37 | "version": "2.2.0" 38 | } 39 | }, 40 | { 41 | "package": "Spectre", 42 | "repositoryURL": "https://github.com/kylef/Spectre.git", 43 | "state": { 44 | "branch": null, 45 | "revision": "f14ff47f45642aa5703900980b014c2e9394b6e5", 46 | "version": "0.9.0" 47 | } 48 | }, 49 | { 50 | "package": "XcodeProj", 51 | "repositoryURL": "https://github.com/tuist/XcodeProj", 52 | "state": { 53 | "branch": null, 54 | "revision": "23f7e12a7e0db29b4f16052692d99f9fbe41fa15", 55 | "version": "7.5.0" 56 | } 57 | } 58 | ] 59 | }, 60 | "version": 1 61 | } 62 | -------------------------------------------------------------------------------- /Package.resources: -------------------------------------------------------------------------------- 1 | Sources/Scripts/xquick.sh 2 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "hackscode", 7 | products: [ 8 | .executable(name: "hackscode", targets: ["hackscode"]), 9 | ], 10 | dependencies: [ 11 | .package(url: "https://github.com/toshi0383/CoreCLI", .upToNextMajor(from: "0.1.10")), 12 | .package(url: "https://github.com/tuist/XcodeProj", .upToNextMajor(from: "7.1.0")), 13 | .package(url: "https://github.com/JohnSundell/ShellOut", .upToNextMajor(from: "2.2.0")), 14 | ], 15 | targets: [ 16 | .target(name: "hackscode", dependencies: ["CoreCLI", "XcodeProj", "ShellOut"]), 17 | ] 18 | ) 19 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | target 'hackscode' do 2 | platform :osx, '10.12' 3 | pod 'Sourcery' 4 | end 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hackscode 2 | 3 | A hacky partner for my life with Xcode. Maybe for you too? 4 | 5 | # Actions 6 | ## remove-build-files 7 | 8 | > Swift compile time is so expensive. Let's ignore all files but one which I'm working on right now.💡 9 | 10 | Install and call it from AppleScript so it's also callable from Xcode. 11 | 12 | ``` 13 | #!/usr/bin/osascript 14 | 15 | use AppleScript version "2.4" # Yosemite or later 16 | use scripting additions 17 | use framework "Foundation" 18 | 19 | on run 20 | tell application "Xcode" 21 | set projectPath to path of active workspace document 22 | set projectFolder to characters 1 thru -((offset of "/" in (reverse of items of projectPath as string)) + 1) of projectPath as string 23 | set sourceName1 to (get name of window 1) 24 | 25 | try 26 | do shell script "hackscode remove-build-files --from-target AbemaTVTests --matching UI.*Spec.swift --excluding " & sourceName1 & " --project-root " & projectFolder 27 | display notification "👌 Ignored all specs except " & sourceName1 with title "Hackscode" 28 | on error errStr number errorNumber 29 | display notification "🛑 " & errStr & " (" & errorNumber & ")" 30 | end try 31 | end tell 32 | end run 33 | ``` 34 | 35 | Set the script trigger to Xcode Behavior. 36 | 37 | ![Xcode Behavior](https://camo.qiitausercontent.com/02b1e04f2d663055e427dcfad0aa754b065bf058/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f33353030382f62343838383034662d636133392d656132662d303132612d6433656262326330306636622e706e67) 38 | 39 | ## dump-build-files 40 | 41 | ``` 42 | $ hackscode dump-build-files --targets hackscode 43 | ## hackscode 44 | CreateNewFile.swift 45 | DumpBuildFiles.swift 46 | RemoveBuildFiles.swift 47 | XQuick.swift 48 | Utilities.swift 49 | main.swift 50 | zzz.Sourcery.out.swift 51 | ``` 52 | 53 | # Install 54 | Mint 55 | ``` 56 | mint install toshi0383/hackscode 57 | ``` 58 | 59 | # Development 60 | Run following to start development. 61 | ``` 62 | make bootstrap 63 | ``` 64 | 65 | You will need to run this after you changed command interface. 66 | ``` 67 | make sourcery 68 | ``` 69 | See: https://github.com/toshi0383/CoreCLI 70 | 71 | # LICENSE 72 | MIT 73 | -------------------------------------------------------------------------------- /Sources/Scripts/xquick.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | FILE=$(find . -name ${1:?} | head -1) 3 | if [ $# -eq 2 ]; then 4 | # Revert 5 | COMMAND='s, xdescribe\(.*\) // xquick, describe\1,;s, xcontext\(.*\) // xquick, context\1,;s, xit\(.*\) // xquick, it\1,;s, // xquick,,' 6 | else 7 | # Apply 8 | COMMAND='s, describe\(.*\), xdescribe\1 // xquick,;s, context\(.*\), xcontext\1 // xquick,;s, it\(.*\), xit\1 // xquick,' 9 | fi 10 | sed -i "" -e "$COMMAND" "$FILE" 11 | -------------------------------------------------------------------------------- /Sources/hackscode/Commands/CreateNewFile.swift: -------------------------------------------------------------------------------- 1 | import CoreCLI 2 | import Foundation 3 | import XcodeProj 4 | 5 | struct CreateNewFile: CommandType { 6 | 7 | let argument: Argument 8 | 9 | struct Argument: AutoArgumentsDecodable { 10 | let toTarget: String 11 | let filepath: String 12 | let projectRoot: String? 13 | 14 | // infer group from filepath by default 15 | let underGroup: String? 16 | 17 | static var shortHandOptions: [PartialKeyPath: Character] { 18 | return [\Argument.toTarget: "t", \Argument.filepath: "f", \Argument.underGroup: "g"] 19 | } 20 | } 21 | 22 | // MARK: CommandType 23 | 24 | func run() throws { 25 | let filepath = argument.filepath 26 | let fm = FileManager.default 27 | 28 | // Error out if the file already exists. 29 | if fm.fileExists(atPath: filepath) { 30 | throw CommandError("file already exists: \(filepath)") 31 | } 32 | 33 | // Create new file 34 | if !fm.createFile(atPath: filepath, contents: nil, attributes: nil) { 35 | throw CommandError("Creating file returned with error status.") 36 | } 37 | 38 | // add FileReference and BuildFile if needed. 39 | let groupPath: String = { 40 | if let underGroup = argument.underGroup { 41 | // look for existing group 42 | // error out if not found 43 | return underGroup 44 | } else { 45 | // infer group from filepath 46 | return filepath.split(separator: "/").dropLast().joined(separator: "/") 47 | } 48 | }() 49 | 50 | let xcodeprojPath = try getXcodeprojPath(projectRoot: argument.projectRoot) 51 | 52 | try! editPbxproj(xcodeprojPath: xcodeprojPath) { pbxproj in 53 | } 54 | } 55 | } 56 | 57 | -------------------------------------------------------------------------------- /Sources/hackscode/Commands/DumpBuildFiles.swift: -------------------------------------------------------------------------------- 1 | import CoreCLI 2 | import Foundation 3 | import XcodeProj 4 | 5 | struct DumpBuildFiles: CommandType { 6 | 7 | let argument: Argument 8 | 9 | struct Argument: AutoArgumentsDecodable { 10 | let targets: String? 11 | let projectRoot: String? 12 | 13 | static var shortHandOptions: [PartialKeyPath: Character] { 14 | return [\Argument.targets: "t", \Argument.projectRoot: "p"] 15 | } 16 | } 17 | 18 | // MARK: CommandType 19 | 20 | func run() throws { 21 | let xcodeprojPath = try getXcodeprojPath(projectRoot: argument.projectRoot) 22 | let project = try XcodeProj(pathString: xcodeprojPath) 23 | let pbxproj = project.pbxproj 24 | 25 | let targetsToDump: [PBXNativeTarget] = { 26 | if let targetsArg = argument.targets { 27 | let targets = targetsArg.split(separator: ",").map(String.init) 28 | return pbxproj.nativeTargets.filter { targets.contains($0.name) } 29 | } else { 30 | return pbxproj.nativeTargets 31 | } 32 | }() 33 | 34 | let sorted = targetsToDump.sorted { $0.name < $1.name } 35 | 36 | for (i, target) in sorted.enumerated() { 37 | print("## \(i) \(target.name)") 38 | let sourcesBuildPhase = try! target.sourcesBuildPhase()! 39 | let sourcefiles = sourcesBuildPhase.files! 40 | if sourcefiles.isEmpty { 41 | print("no source files found") 42 | continue 43 | } 44 | 45 | for file in sourcefiles { 46 | if let fileElement = file.file { 47 | let nameOrPath = fileElement.name ?? fileElement.path! 48 | print(" \(nameOrPath)") 49 | } 50 | } 51 | 52 | if i < targetsToDump.count - 1 { 53 | print() 54 | } 55 | } 56 | } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /Sources/hackscode/Commands/RemoveBuildFiles.swift: -------------------------------------------------------------------------------- 1 | import CoreCLI 2 | import Foundation 3 | import XcodeProj 4 | 5 | struct RemoveBuildFiles: CommandType { 6 | 7 | let argument: Argument 8 | 9 | struct Argument: AutoArgumentsDecodable { 10 | let fromTarget: String 11 | let matching: String 12 | let excluding: String? 13 | let projectRoot: String? 14 | let verbose: Bool 15 | 16 | static var shortHandOptions: [PartialKeyPath: Character] { 17 | return [\Argument.fromTarget: "t", \Argument.excluding: "e", \Argument.matching: "m"] 18 | } 19 | 20 | static var shortHandFlags: [KeyPath: Character] { 21 | return [\.verbose: "V"] 22 | } 23 | } 24 | 25 | // MARK: CommandType 26 | 27 | func run() throws { 28 | let xcodeprojPath = try getXcodeprojPath(projectRoot: argument.projectRoot) 29 | try editPbxproj(xcodeprojPath: xcodeprojPath) { pbxproj in 30 | 31 | let fromTarget = argument.fromTarget 32 | let keepNames = argument.excluding?.split(separator: ",").map(String.init) 33 | let matching = argument.matching 34 | 35 | let target = pbxproj.nativeTargets 36 | .first { $0.name == fromTarget }! 37 | 38 | let sourcesBuildPhase = try! target.sourcesBuildPhase()! 39 | 40 | let shouldDelete: (PBXFileElement) -> Bool = { fileElement in 41 | let nameOrPath = fileElement.name ?? fileElement.path! 42 | if let keepNames = keepNames, keepNames.contains(where: { nameOrPath.contains($0) }) { 43 | return false 44 | } 45 | 46 | if nameOrPath.range(of: "(.*)\(matching)(.*)", options: .regularExpression) != nil { 47 | return true 48 | } else { 49 | return false 50 | } 51 | } 52 | 53 | let buildFilesToDelete: [PBXBuildFile] = sourcesBuildPhase 54 | .files! 55 | .compactMap { 56 | 57 | if let fileElement = $0.file, shouldDelete(fileElement) { 58 | return $0 59 | } else { 60 | return nil 61 | } 62 | } 63 | 64 | // Delete from buildFiles 65 | for (i, buildFile) in sourcesBuildPhase.files!.enumerated().reversed() { 66 | if buildFilesToDelete.contains(buildFile) { 67 | sourcesBuildPhase.files!.remove(at: i) 68 | } 69 | } 70 | } 71 | } 72 | } 73 | 74 | -------------------------------------------------------------------------------- /Sources/hackscode/Commands/XQuick.swift: -------------------------------------------------------------------------------- 1 | import CoreCLI 2 | import Foundation 3 | import ShellOut 4 | 5 | struct Xquick: CommandType { 6 | 7 | let argument: Argument 8 | 9 | struct Argument: AutoArgumentsDecodable { 10 | 11 | // sourcery: shift 12 | let filename: String 13 | let revert: Bool 14 | static var shortHandFlags: [KeyPath: Character] { 15 | return [\Argument.revert: "r"] 16 | } 17 | } 18 | 19 | // MARK: CommandType 20 | 21 | func run() throws { 22 | let filename = argument.filename 23 | try shellOut(to: pathForShellScript(named: "xquick.sh"), 24 | arguments: argument.revert ? [filename, "r"] : [filename]) 25 | exit(0) 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /Sources/hackscode/Utilities.swift: -------------------------------------------------------------------------------- 1 | import CoreCLI 2 | import Foundation 3 | import XcodeProj 4 | 5 | func getXcodeprojPath(projectRoot: String? = nil) throws -> String { 6 | let curdir = projectRoot ?? FileManager.default.currentDirectoryPath 7 | guard let xcodeprojName = try FileManager.default.contentsOfDirectory(atPath: curdir) 8 | .first(where: { $0.contains("xcodeproj")}) else { 9 | throw CommandError("Could not find xcodeproj in current directory: \(curdir)") 10 | } 11 | return "\(curdir)/\(xcodeprojName)" 12 | } 13 | 14 | func editPbxproj(xcodeprojPath: String, _ edit: (PBXProj) throws -> ()) throws { 15 | let project = try XcodeProj(pathString: xcodeprojPath) 16 | let pbxproj = project.pbxproj 17 | try edit(pbxproj) 18 | try! pbxproj.write(pathString: "\(xcodeprojPath)/project.pbxproj", override: true) 19 | } 20 | -------------------------------------------------------------------------------- /Sources/hackscode/main.swift: -------------------------------------------------------------------------------- 1 | import CoreCLI 2 | import Foundation 3 | 4 | struct Hackscode: CommandType { 5 | let arguments: Arguments 6 | 7 | private let version = "0.5.1" 8 | 9 | struct Arguments: AutoArgumentsDecodable { 10 | let version: Bool 11 | let help: Bool 12 | let subCommand: CommandType? 13 | static let subCommands: [CommandType.Type] = [RemoveBuildFiles.self, 14 | DumpBuildFiles.self, 15 | CreateNewFile.self, 16 | Xquick.self] 17 | 18 | static var shortHandCommands: [String: CommandType.Type] { 19 | return ["remove": RemoveBuildFiles.self, "dump": DumpBuildFiles.self] 20 | } 21 | } 22 | 23 | init() throws { 24 | let parser = ArgumentParser(arguments: ProcessInfo.processInfo.arguments) 25 | self.arguments = try Arguments(parser: parser) 26 | } 27 | 28 | // MARK: CommandType 29 | 30 | func run() throws { 31 | if arguments.version { 32 | print(version) 33 | exit(0) 34 | } 35 | 36 | if arguments.help { 37 | print("Help me!") 38 | exit(0) 39 | } 40 | 41 | guard let subCommand = arguments.subCommand else { 42 | print("Usage!") 43 | exit(1) 44 | } 45 | 46 | do { 47 | try subCommand.run() 48 | } catch { 49 | print("\(type(of: subCommand).name): \(error)") 50 | exit(1) 51 | } 52 | } 53 | } 54 | 55 | do { 56 | try Hackscode().run() 57 | } catch { 58 | print(error) 59 | exit(1) 60 | } 61 | -------------------------------------------------------------------------------- /Sources/hackscode/zzz.Sourcery.out.swift: -------------------------------------------------------------------------------- 1 | // Generated using Sourcery 0.17.0 — https://github.com/krzysztofzablocki/Sourcery 2 | // DO NOT EDIT 3 | 4 | 5 | // Generated by using CoreCLI version 0.1.9 6 | 7 | import CoreCLI 8 | 9 | // - MARK: CreateNewFile.Argument 10 | 11 | extension CreateNewFile.Argument { 12 | private typealias Base = CreateNewFile.Argument 13 | 14 | private static var autoMappedOptions: [PartialKeyPath: String] { 15 | return [ 16 | \Base.toTarget: "--to-target", 17 | \Base.filepath: "--filepath", 18 | \Base.projectRoot: "--project-root", 19 | \Base.underGroup: "--under-group", 20 | ] 21 | } 22 | 23 | private static var autoMappedFlags: [KeyPath: String] { 24 | return [ 25 | : ] 26 | } 27 | 28 | init(parser: ArgumentParserType) throws { 29 | 30 | func getOptionValue(keyPath: PartialKeyPath) throws -> String { 31 | if let short = Base.shortHandOptions[keyPath], 32 | let value = try? parser.getValue(forOption: "-\(short)") { 33 | return value 34 | } 35 | let long = Base.autoMappedOptions[keyPath]! 36 | return try parser.getValue(forOption: long) 37 | } 38 | 39 | func getFlag(keyPath: KeyPath) -> Bool { 40 | if let short = Base.shortHandFlags[keyPath] { 41 | let value = parser.getFlag("-\(short)") 42 | if value { 43 | return true 44 | } 45 | } 46 | let long = Base.autoMappedFlags[keyPath]! 47 | return parser.getFlag(long) 48 | } 49 | 50 | func getCommandType() -> CommandType.Type? { 51 | for (i, arg) in parser.remainder.enumerated() { 52 | if let subCommand = Base.shortHandCommands[arg] { 53 | parser.shift(at: i) 54 | return subCommand 55 | } 56 | if let subCommand = Base.subCommands.first(where: { $0.name == arg }) { 57 | parser.shift(at: i) 58 | return subCommand 59 | } 60 | } 61 | 62 | return Base.defaultSubCommand 63 | } 64 | 65 | self.toTarget = try getOptionValue(keyPath: \Base.toTarget) 66 | self.filepath = try getOptionValue(keyPath: \Base.filepath) 67 | self.projectRoot = try? getOptionValue(keyPath: \Base.projectRoot) 68 | self.underGroup = try? getOptionValue(keyPath: \Base.underGroup) 69 | } 70 | } 71 | 72 | // - MARK: DumpBuildFiles.Argument 73 | 74 | extension DumpBuildFiles.Argument { 75 | private typealias Base = DumpBuildFiles.Argument 76 | 77 | private static var autoMappedOptions: [PartialKeyPath: String] { 78 | return [ 79 | \Base.targets: "--targets", 80 | \Base.projectRoot: "--project-root", 81 | ] 82 | } 83 | 84 | private static var autoMappedFlags: [KeyPath: String] { 85 | return [ 86 | : ] 87 | } 88 | 89 | init(parser: ArgumentParserType) throws { 90 | 91 | func getOptionValue(keyPath: PartialKeyPath) throws -> String { 92 | if let short = Base.shortHandOptions[keyPath], 93 | let value = try? parser.getValue(forOption: "-\(short)") { 94 | return value 95 | } 96 | let long = Base.autoMappedOptions[keyPath]! 97 | return try parser.getValue(forOption: long) 98 | } 99 | 100 | func getFlag(keyPath: KeyPath) -> Bool { 101 | if let short = Base.shortHandFlags[keyPath] { 102 | let value = parser.getFlag("-\(short)") 103 | if value { 104 | return true 105 | } 106 | } 107 | let long = Base.autoMappedFlags[keyPath]! 108 | return parser.getFlag(long) 109 | } 110 | 111 | func getCommandType() -> CommandType.Type? { 112 | for (i, arg) in parser.remainder.enumerated() { 113 | if let subCommand = Base.shortHandCommands[arg] { 114 | parser.shift(at: i) 115 | return subCommand 116 | } 117 | if let subCommand = Base.subCommands.first(where: { $0.name == arg }) { 118 | parser.shift(at: i) 119 | return subCommand 120 | } 121 | } 122 | 123 | return Base.defaultSubCommand 124 | } 125 | 126 | self.targets = try? getOptionValue(keyPath: \Base.targets) 127 | self.projectRoot = try? getOptionValue(keyPath: \Base.projectRoot) 128 | } 129 | } 130 | 131 | // - MARK: Hackscode.Arguments 132 | 133 | extension Hackscode.Arguments { 134 | private typealias Base = Hackscode.Arguments 135 | 136 | private static var autoMappedOptions: [PartialKeyPath: String] { 137 | return [ 138 | : ] 139 | } 140 | 141 | private static var autoMappedFlags: [KeyPath: String] { 142 | return [ 143 | \Base.version: "--version", 144 | \Base.help: "--help", 145 | ] 146 | } 147 | 148 | init(parser: ArgumentParserType) throws { 149 | 150 | func getOptionValue(keyPath: PartialKeyPath) throws -> String { 151 | if let short = Base.shortHandOptions[keyPath], 152 | let value = try? parser.getValue(forOption: "-\(short)") { 153 | return value 154 | } 155 | let long = Base.autoMappedOptions[keyPath]! 156 | return try parser.getValue(forOption: long) 157 | } 158 | 159 | func getFlag(keyPath: KeyPath) -> Bool { 160 | if let short = Base.shortHandFlags[keyPath] { 161 | let value = parser.getFlag("-\(short)") 162 | if value { 163 | return true 164 | } 165 | } 166 | let long = Base.autoMappedFlags[keyPath]! 167 | return parser.getFlag(long) 168 | } 169 | 170 | func getCommandType() -> CommandType.Type? { 171 | for (i, arg) in parser.remainder.enumerated() { 172 | if let subCommand = Base.shortHandCommands[arg] { 173 | parser.shift(at: i) 174 | return subCommand 175 | } 176 | if let subCommand = Base.subCommands.first(where: { $0.name == arg }) { 177 | parser.shift(at: i) 178 | return subCommand 179 | } 180 | } 181 | 182 | return Base.defaultSubCommand 183 | } 184 | 185 | self.version = getFlag(keyPath: \Base.version) 186 | self.help = getFlag(keyPath: \Base.help) 187 | if let type = getCommandType() { 188 | self.subCommand = try type.init(parser: parser) 189 | } else { 190 | self.subCommand = nil 191 | } 192 | } 193 | } 194 | 195 | // - MARK: RemoveBuildFiles.Argument 196 | 197 | extension RemoveBuildFiles.Argument { 198 | private typealias Base = RemoveBuildFiles.Argument 199 | 200 | private static var autoMappedOptions: [PartialKeyPath: String] { 201 | return [ 202 | \Base.fromTarget: "--from-target", 203 | \Base.matching: "--matching", 204 | \Base.excluding: "--excluding", 205 | \Base.projectRoot: "--project-root", 206 | ] 207 | } 208 | 209 | private static var autoMappedFlags: [KeyPath: String] { 210 | return [ 211 | \Base.verbose: "--verbose", 212 | ] 213 | } 214 | 215 | init(parser: ArgumentParserType) throws { 216 | 217 | func getOptionValue(keyPath: PartialKeyPath) throws -> String { 218 | if let short = Base.shortHandOptions[keyPath], 219 | let value = try? parser.getValue(forOption: "-\(short)") { 220 | return value 221 | } 222 | let long = Base.autoMappedOptions[keyPath]! 223 | return try parser.getValue(forOption: long) 224 | } 225 | 226 | func getFlag(keyPath: KeyPath) -> Bool { 227 | if let short = Base.shortHandFlags[keyPath] { 228 | let value = parser.getFlag("-\(short)") 229 | if value { 230 | return true 231 | } 232 | } 233 | let long = Base.autoMappedFlags[keyPath]! 234 | return parser.getFlag(long) 235 | } 236 | 237 | func getCommandType() -> CommandType.Type? { 238 | for (i, arg) in parser.remainder.enumerated() { 239 | if let subCommand = Base.shortHandCommands[arg] { 240 | parser.shift(at: i) 241 | return subCommand 242 | } 243 | if let subCommand = Base.subCommands.first(where: { $0.name == arg }) { 244 | parser.shift(at: i) 245 | return subCommand 246 | } 247 | } 248 | 249 | return Base.defaultSubCommand 250 | } 251 | 252 | self.fromTarget = try getOptionValue(keyPath: \Base.fromTarget) 253 | self.matching = try getOptionValue(keyPath: \Base.matching) 254 | self.excluding = try? getOptionValue(keyPath: \Base.excluding) 255 | self.projectRoot = try? getOptionValue(keyPath: \Base.projectRoot) 256 | self.verbose = getFlag(keyPath: \Base.verbose) 257 | } 258 | } 259 | 260 | // - MARK: Xquick.Argument 261 | 262 | extension Xquick.Argument { 263 | private typealias Base = Xquick.Argument 264 | 265 | private static var autoMappedOptions: [PartialKeyPath: String] { 266 | return [ 267 | \Base.filename: "--filename", 268 | ] 269 | } 270 | 271 | private static var autoMappedFlags: [KeyPath: String] { 272 | return [ 273 | \Base.revert: "--revert", 274 | ] 275 | } 276 | 277 | init(parser: ArgumentParserType) throws { 278 | 279 | func getOptionValue(keyPath: PartialKeyPath) throws -> String { 280 | if let short = Base.shortHandOptions[keyPath], 281 | let value = try? parser.getValue(forOption: "-\(short)") { 282 | return value 283 | } 284 | let long = Base.autoMappedOptions[keyPath]! 285 | return try parser.getValue(forOption: long) 286 | } 287 | 288 | func getFlag(keyPath: KeyPath) -> Bool { 289 | if let short = Base.shortHandFlags[keyPath] { 290 | let value = parser.getFlag("-\(short)") 291 | if value { 292 | return true 293 | } 294 | } 295 | let long = Base.autoMappedFlags[keyPath]! 296 | return parser.getFlag(long) 297 | } 298 | 299 | func getCommandType() -> CommandType.Type? { 300 | for (i, arg) in parser.remainder.enumerated() { 301 | if let subCommand = Base.shortHandCommands[arg] { 302 | parser.shift(at: i) 303 | return subCommand 304 | } 305 | if let subCommand = Base.subCommands.first(where: { $0.name == arg }) { 306 | parser.shift(at: i) 307 | return subCommand 308 | } 309 | } 310 | 311 | return Base.defaultSubCommand 312 | } 313 | 314 | self.revert = getFlag(keyPath: \Base.revert) 315 | guard let shifted = parser.shift() else { 316 | throw CommandError("Missing filename parameter.") 317 | } 318 | self.filename = shifted 319 | } 320 | } 321 | 322 | // - MARK: CreateNewFile 323 | 324 | extension CreateNewFile { 325 | private typealias Base = CreateNewFile 326 | 327 | init(parser: ArgumentParserType) throws { 328 | self.argument = try CreateNewFile.Argument(parser: parser) 329 | } 330 | 331 | static var name: String { 332 | return "create-new-file" 333 | } 334 | } 335 | 336 | // - MARK: DumpBuildFiles 337 | 338 | extension DumpBuildFiles { 339 | private typealias Base = DumpBuildFiles 340 | 341 | init(parser: ArgumentParserType) throws { 342 | self.argument = try DumpBuildFiles.Argument(parser: parser) 343 | } 344 | 345 | static var name: String { 346 | return "dump-build-files" 347 | } 348 | } 349 | 350 | // - MARK: Hackscode 351 | 352 | extension Hackscode { 353 | private typealias Base = Hackscode 354 | 355 | init(parser: ArgumentParserType) throws { 356 | self.arguments = try Hackscode.Arguments(parser: parser) 357 | } 358 | 359 | static var name: String { 360 | return "hackscode" 361 | } 362 | } 363 | 364 | // - MARK: RemoveBuildFiles 365 | 366 | extension RemoveBuildFiles { 367 | private typealias Base = RemoveBuildFiles 368 | 369 | init(parser: ArgumentParserType) throws { 370 | self.argument = try RemoveBuildFiles.Argument(parser: parser) 371 | } 372 | 373 | static var name: String { 374 | return "remove-build-files" 375 | } 376 | } 377 | 378 | // - MARK: Xquick 379 | 380 | extension Xquick { 381 | private typealias Base = Xquick 382 | 383 | init(parser: ArgumentParserType) throws { 384 | self.argument = try Xquick.Argument(parser: parser) 385 | } 386 | 387 | static var name: String { 388 | return "xquick" 389 | } 390 | } 391 | 392 | -------------------------------------------------------------------------------- /scripts/bump-version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | VERSION=${1:?} 3 | CURRENT=${2:-`hackscode --version`} 4 | #sed -i "" -e "s/master/${VERSION}/" CHANGELOG.md 5 | git grep -l $CURRENT | grep -v CHANGELOG.md | xargs sed -i "" -e "s/${CURRENT}/${VERSION}/g" 6 | -------------------------------------------------------------------------------- /scripts/run-sourcery: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | TEMPLATE=$(find . -name AutoArgumentsDecodable.swifttemplate | head -1) 3 | CORECLI_SOURCES=$(echo ${TEMPLATE:?} | sed 's/AutoArgumentsDecodable.swifttemplate//') 4 | YOUR_SOURCE=Sources/hackscode 5 | OUT=Sources/hackscode/zzz.Sourcery.out.swift 6 | ./Pods/Sourcery/bin/sourcery --sources "$YOUR_SOURCE" --sources ${CORECLI_SOURCES} --templates "$TEMPLATE" --output "$OUT" 7 | --------------------------------------------------------------------------------