-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCLI.swift
134 lines (117 loc) · 3.95 KB
/
CLI.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import ArgumentParser
import DotEnvy
import Foundation
@main
struct Tool: ParsableCommand {
static var configuration = CommandConfiguration(
commandName: "dotenvy-tool",
abstract: "Tool for working with dotenv files",
subcommands: [Check.self, JSON.self]
)
}
struct Check: ParsableCommand {
static var configuration
= CommandConfiguration(
abstract: "Check syntax of input.",
discussion: """
In case of a syntax error, the error is printed to standard error
and the command exits with failure code \(ExitCode.failure.rawValue).
If there are no problems reading the input, nothing is printed
and the command exits with \(ExitCode.success.rawValue).
"""
)
@Option(
name: [.customShort("i"), .long],
help: "Input. Standard input is used with -. If omitted, try to use .env in cwd"
)
var input: Input?
func run() throws {
_ = try loadInput(self.input)
}
}
struct JSON: ParsableCommand {
static var configuration
= CommandConfiguration(
abstract: "Convert input to JSON.",
discussion: """
The input is converted to a JSON object.
In case of a syntax error, the error is printed to standard error and the
command exits with failure code \(ExitCode.failure.rawValue).
If there are no problems reading the input, the JSON value is printed to
standard output and the command exits with \(ExitCode.success.rawValue).
"""
)
@Option(
name: [.customShort("i"), .long],
help: "Input. Standard input is used with -. If omitted, try to use .env in cwd"
)
var input: Input?
@Flag(help: "Pretty print JSON")
var pretty: Bool = false
func run() throws {
let values = try loadInput(self.input)
let json = try JSONSerialization.data(
withJSONObject: values,
options: self.pretty ? [.prettyPrinted, .sortedKeys] : []
)
FileHandle.standardOutput.write(json)
FileHandle.standardOutput.write(Data("\n".utf8))
}
}
enum Input: ExpressibleByArgument {
case stdin
case fileURL(FileURL)
init?(argument: String) {
if argument == "-" {
self = .stdin
} else if let fileURL = FileURL(argument: argument) {
self = .fileURL(fileURL)
} else {
return nil
}
}
}
struct FileURL: ExpressibleByArgument {
var url: URL
init?(argument: String) {
// the new URL(filePath:directoryHint:) is not available on Linux
let url = URL(fileURLWithPath: argument, isDirectory: false)
guard url.isFileURL else {
return nil
}
self.url = url
}
}
private func loadInput(_ input: Input?) throws -> [String: String] {
if let input = input {
let string = try readInput(input)
do {
return try DotEnvironment.parse(string: string)
} catch let error as ParseErrorWithLocation {
FileHandle.standardError.write(Data(error.formatError(source: string).utf8))
FileHandle.standardError.write(Data("\n".utf8))
throw ExitCode.failure
}
} else {
do {
return try DotEnvironment.loadValues()
} catch let error as LoadError {
FileHandle.standardError.write(Data(error.description.utf8))
FileHandle.standardError.write(Data("\n".utf8))
throw ExitCode.failure
}
}
}
private func readInput(_ input: Input) throws -> String {
let data: Data
switch input {
case .stdin:
data = FileHandle.standardInput.readDataToEndOfFile()
case let .fileURL(fileURL):
data = try Data(contentsOf: fileURL.url)
}
guard let string = String(data: data, encoding: .utf8) else {
throw ValidationError("Input could not be decoded as UTF-8")
}
return string
}