From 82dcb2f48d83d58cb35006e5251af930355c50d4 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Mon, 6 Jul 2020 09:52:31 +0100 Subject: [PATCH] Add --stdinpath option --- README.md | 4 +- Sources/Arguments.swift | 1 + Sources/CommandLine.swift | 39 ++++++++++++++--- Sources/SwiftFormat.swift | 90 +++++++++++++++++++-------------------- 4 files changed, 82 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 9e5ca98b..ecac1e72 100644 --- a/README.md +++ b/README.md @@ -326,11 +326,11 @@ Git pre-commit hook 3. Edit or create a `.git/hooks/pre-commit` file in your project folder. The .git folder is hidden but should already exist if you are using Git with your project, so open it with the terminal, or the Finder's `Go > Go to Folder...` menu. -4. Add the following line in the pre-commit file: +4. Add the following line in the pre-commit file. The `{}` will be replaced automatically by the path to the Swift file being formatted: ```bash #!/bin/bash - git-format-staged --formatter "swiftformat --config .swiftformat stdin" "*.swift" + git-format-staged --formatter "swiftformat stdin --stdinpath {}" "*.swift" ``` (Note that this example uses your locally installed version of SwiftFormat, not a separate copy in your project repository. You can replace `swiftformat` with the path to a copy inside your project if you prefer.) diff --git a/Sources/Arguments.swift b/Sources/Arguments.swift index e6bcbaaa..a3e5cbad 100644 --- a/Sources/Arguments.swift +++ b/Sources/Arguments.swift @@ -514,6 +514,7 @@ let optionsArguments = fileArguments + rulesArguments + formattingArguments + in let commandLineArguments = [ // Input options "filelist", + "stdinpath", "config", "inferoptions", "output", diff --git a/Sources/CommandLine.swift b/Sources/CommandLine.swift index 7eb5b98b..4bb0d8c1 100644 --- a/Sources/CommandLine.swift +++ b/Sources/CommandLine.swift @@ -176,6 +176,7 @@ func printHelp(as type: CLI.OutputType) { ... Swift files or directories to be processed, or "stdin" --filelist Path to a file with names of files to process, one per line + --stdinpath Path to stdin source file (used for generating header) --config Path to a configuration file containing rules and options --inferoptions Instead of formatting input, use it to infer format options --output Output path for formatted file(s) (defaults to input path) @@ -358,7 +359,7 @@ func processArguments(_ args: [String], in directory: String) -> ExitCode { } // Options - let options = try Options(args, in: directory) + var options = try Options(args, in: directory) // Show rules if showRules { @@ -386,7 +387,6 @@ func processArguments(_ args: [String], in directory: String) -> ExitCode { } // Input path(s) - var useStdin = false var inputURLs = [URL]() if let fileListPath = args["filelist"] { let fileListURL = try parsePath(fileListPath, for: "filelist", in: directory) @@ -397,13 +397,38 @@ func processArguments(_ args: [String], in directory: String) -> ExitCode { throw FormatError.options("Failed to read file list at \(fileListPath)") } } - while !useStdin, let inputPath = args[String(inputURLs.count + 1)] { + var useStdin = false + while let inputPath = args[String(inputURLs.count + 1)] { + inputURLs += try parsePaths(inputPath, for: "input", in: directory) if inputPath.lowercased() == "stdin" { useStdin = true - } else { - inputURLs += try parsePaths(inputPath, for: "input", in: directory) } } + if useStdin { + if inputURLs.count > 1 { + if args["filelist"] != nil { + throw FormatError.options("--filelist option cannot be combined with stdin input") + } + throw FormatError.options("Cannot combine stdin with other file inputs") + } + inputURLs = [] + } + if let stdinPath = args["stdinpath"] { + if !useStdin { + print("warning: --stdinpath option only applies when using stdin", as: .warning) + } + let stdinURL = try parsePath(stdinPath, for: "stdinpath", in: directory) + let resourceValues = try getResourceValues( + for: stdinURL.standardizedFileURL, + keys: [.creationDateKey, .pathKey] + ) + var formatOptions = options.formatOptions ?? .default + formatOptions.fileInfo = FileInfo( + filePath: resourceValues.path, + creationDate: resourceValues.creationDate + ) + options.formatOptions = formatOptions + } // Treat values for arguments that do not take a value as input paths func addInputPaths(for argName: String) throws { @@ -720,6 +745,10 @@ func applyRules(_ source: String, options: Options, verbose: Bool, lint: Bool) t let ruleNames = Array(options.rules ?? allRules.subtracting(FormatRules.disabledByDefault)).sorted() let rules = ruleNames.compactMap { rulesByName[$0] } + if verbose, let path = options.formatOptions?.fileInfo.filePath { + print("\(lint ? "Linting" : "Formatting") \(path)", as: .info) + } + // Apply rules let formatOptions = options.formatOptions ?? .default var changes = [Formatter.Change]() diff --git a/Sources/SwiftFormat.swift b/Sources/SwiftFormat.swift index 7b3a1e74..b114d87b 100644 --- a/Sources/SwiftFormat.swift +++ b/Sources/SwiftFormat.swift @@ -100,47 +100,6 @@ public func enumerateFiles(withInputURL inputURL: URL, .creationDateKey, .pathKey, ] - struct ResourceValues { - let isRegularFile: Bool? - let isDirectory: Bool? - let isAliasFile: Bool? - let isSymbolicLink: Bool? - let creationDate: Date? - let path: String? - } - - func getResourceValues(for url: URL) throws -> ResourceValues { - #if os(macOS) - if let resourceValues = try? url.resourceValues(forKeys: Set(keys)) { - return ResourceValues( - isRegularFile: resourceValues.isRegularFile, - isDirectory: resourceValues.isDirectory, - isAliasFile: resourceValues.isAliasFile, - isSymbolicLink: resourceValues.isSymbolicLink, - creationDate: resourceValues.creationDate, - path: resourceValues.path - ) - } - if manager.fileExists(atPath: url.path) { - throw FormatError.reading("Failed to read attributes for \(url.path)") - } - throw FormatError.options("File not found at \(url.path)") - #else - var isDirectory: ObjCBool = false - if manager.fileExists(atPath: url.path, isDirectory: &isDirectory) { - return ResourceValues( - isRegularFile: !isDirectory.boolValue, - isDirectory: isDirectory.boolValue, - isAliasFile: false, - isSymbolicLink: false, - creationDate: nil, - path: url.path - ) - } - throw FormatError.options("File not found at \(url.path)") - #endif - } - let group = DispatchGroup() var completionBlocks = [() throws -> Void]() let completionQueue = DispatchQueue(label: "swiftformat.enumeration") @@ -156,12 +115,12 @@ public func enumerateFiles(withInputURL inputURL: URL, let fileOptions = options.fileOptions ?? .default let inputURL = inputURL.standardizedFileURL do { - let resourceValues = try getResourceValues(for: inputURL) + let resourceValues = try getResourceValues(for: inputURL, keys: keys) if resourceValues.isAliasFile == true { #if os(macOS) if fileOptions.followSymlinks { let resolvedURL = try URL(resolvingAliasFileAt: inputURL) - return (resolvedURL, try getResourceValues(for: resolvedURL), baseOptions) + return (resolvedURL, try getResourceValues(for: resolvedURL, keys: keys), baseOptions) } else if let handler = skipped { onComplete(try handler(inputURL, inputURL, options)) return nil @@ -170,7 +129,7 @@ public func enumerateFiles(withInputURL inputURL: URL, } else if resourceValues.isSymbolicLink == true { if fileOptions.followSymlinks { let resolvedURL = inputURL.resolvingSymlinksInPath() - return (resolvedURL, try getResourceValues(for: resolvedURL), baseOptions) + return (resolvedURL, try getResourceValues(for: resolvedURL, keys: keys), baseOptions) } else if let handler = skipped { onComplete(try handler(inputURL, inputURL, options)) return nil @@ -185,7 +144,7 @@ public func enumerateFiles(withInputURL inputURL: URL, let fileOptions = baseOptions.fileOptions ?? .default do { - let resourceValues = try getResourceValues(for: inputURL.standardizedFileURL) + let resourceValues = try getResourceValues(for: inputURL.standardizedFileURL, keys: keys) if !fileOptions.followSymlinks, resourceValues.isAliasFile == true || resourceValues.isSymbolicLink == true { return [FormatError.options("Symbolic link or alias was skipped: \(inputURL.path)")] @@ -612,6 +571,47 @@ public func expandPath(_ path: String, in directory: String) -> URL { return URL(fileURLWithPath: directory).appendingPathComponent(path) } +struct ResourceValues { + let isRegularFile: Bool? + let isDirectory: Bool? + let isAliasFile: Bool? + let isSymbolicLink: Bool? + let creationDate: Date? + let path: String? +} + +func getResourceValues(for url: URL, keys: [URLResourceKey]) throws -> ResourceValues { + let manager = FileManager.default + #if os(macOS) + if let resourceValues = try? url.resourceValues(forKeys: Set(keys)) { + return ResourceValues( + isRegularFile: resourceValues.isRegularFile, + isDirectory: resourceValues.isDirectory, + isAliasFile: resourceValues.isAliasFile, + isSymbolicLink: resourceValues.isSymbolicLink, + creationDate: resourceValues.creationDate, + path: resourceValues.path + ) + } + if manager.fileExists(atPath: url.path) { + throw FormatError.reading("Failed to read attributes for \(url.path)") + } + #else + var isDirectory: ObjCBool = false + if manager.fileExists(atPath: url.path, isDirectory: &isDirectory) { + return ResourceValues( + isRegularFile: !isDirectory.boolValue, + isDirectory: isDirectory.boolValue, + isAliasFile: false, + isSymbolicLink: false, + creationDate: nil, + path: url.path + ) + } + #endif + throw FormatError.options("File not found at \(url.path)") +} + // MARK: Documentation utilities // Strip markdown code-formatting