Skip to content

Commit f6ac7b8

Browse files
committed
Initial import of ArgumentParser
0 parents  commit f6ac7b8

File tree

79 files changed

+11333
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+11333
-0
lines changed

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
/*.xcodeproj
5+
.swiftpm

CODE_OF_CONDUCT.md

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Code of Conduct
2+
To be a truly great community, Swift.org needs to welcome developers from all walks of life,
3+
with different backgrounds, and with a wide range of experience. A diverse and friendly
4+
community will have more great ideas, more unique perspectives, and produce more great
5+
code. We will work diligently to make the Swift community welcoming to everyone.
6+
7+
To give clarity of what is expected of our members, Swift.org has adopted the code of conduct
8+
defined by [contributor-covenant.org](https://www.contributor-covenant.org). This document is used across many open source
9+
communities, and we think it articulates our values well. The full text is copied below:
10+
11+
### Contributor Code of Conduct v1.3
12+
As contributors and maintainers of this project, and in the interest of fostering an open and
13+
welcoming community, we pledge to respect all people who contribute through reporting
14+
issues, posting feature requests, updating documentation, submitting pull requests or patches,
15+
and other activities.
16+
17+
We are committed to making participation in this project a harassment-free experience for
18+
everyone, regardless of level of experience, gender, gender identity and expression, sexual
19+
orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or
20+
nationality.
21+
22+
Examples of unacceptable behavior by participants include:
23+
- The use of sexualized language or imagery
24+
- Personal attacks
25+
- Trolling or insulting/derogatory comments
26+
- Public or private harassment
27+
- Publishing other’s private information, such as physical or electronic addresses, without explicit permission
28+
- Other unethical or unprofessional conduct
29+
30+
Project maintainers have the right and responsibility to remove, edit, or reject comments,
31+
commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of
32+
Conduct, or to ban temporarily or permanently any contributor for other behaviors that they
33+
deem inappropriate, threatening, offensive, or harmful.
34+
35+
By adopting this Code of Conduct, project maintainers commit themselves to fairly and
36+
consistently applying these principles to every aspect of managing this project. Project
37+
maintainers who do not follow or enforce the Code of Conduct may be permanently removed
38+
from the project team.
39+
40+
This code of conduct applies both within project spaces and in public spaces when an
41+
individual is representing the project or its community.
42+
43+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by
44+
contacting a project maintainer at [conduct@swift.org](mailto:conduct@swift.org). All complaints will be reviewed and
45+
investigated and will result in a response that is deemed necessary and appropriate to the
46+
circumstances. Maintainers are obligated to maintain confidentiality with regard to the reporter
47+
of an incident.
48+
49+
*This policy is adapted from the Contributor Code of Conduct [version 1.3.0](http://contributor-covenant.org/version/1/3/0/).*
50+
51+
### Reporting
52+
A working group of community members is committed to promptly addressing any [reported
53+
issues](mailto:conduct@swift.org). Working group members are volunteers appointed by the project lead, with a
54+
preference for individuals with varied backgrounds and perspectives. Membership is expected
55+
to change regularly, and may grow or shrink.

CONTRIBUTING.md

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
By submitting a pull request, you represent that you have the right to license
2+
your contribution to Apple and the community, and agree by submitting the patch
3+
that your contributions are licensed under the [Swift
4+
license](https://swift.org/LICENSE.txt).
5+
6+
---
7+
8+
Before submitting the pull request, please make sure you have tested your
9+
changes and that they follow the Swift project [guidelines for contributing
10+
code](https://swift.org/contributing/#contributing-code).

Documentation/01 Getting Started.md

+292
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
# Getting Started with `ArgumentParser`
2+
3+
Learn to set up and customize a simple command-line tool.
4+
5+
This guide walks through building an example command. You'll learn about the different tools that `ArgumentParser` provides for defining a command's options, customizing the interface, and providing help text for your user.
6+
7+
## Adding `ArgumentParser` as a Dependency
8+
9+
First, we need to add `swift-argument-parser` as a dependency to our package,
10+
and then include `"ArgumentParser"` as a dependency for our executable target.
11+
Our "Package.swift" file ends up looking like this:
12+
13+
```swift
14+
// swift-tools-version:5.2
15+
import PackageDescription
16+
17+
let package = Package(
18+
name: "random",
19+
dependencies: [
20+
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "0.0.1"),
21+
],
22+
targets: [
23+
.target(
24+
name: "count",
25+
dependencies: [.product(name: "ArgumentParser", package: "swift-argument-parser")]),
26+
]
27+
)
28+
```
29+
30+
## Building Our First Command
31+
32+
Let's write a tool called `count` that reads an input file, counts the words, and writes the result to an output file.
33+
34+
We can run our `count` tool like this:
35+
36+
```
37+
% count readme.md readme.counts
38+
Counting words in 'readme.md' and writing the result into 'readme.counts'.
39+
```
40+
41+
We'll define the initial version of the command as a type that conforms to the `ParsableCommand` protocol:
42+
43+
```swift
44+
import ArgumentParser
45+
46+
struct Count: ParsableCommand {
47+
@Argument()
48+
var inputFile: String
49+
50+
@Argument()
51+
var outputFile: String
52+
53+
func run() throws {
54+
print("""
55+
Counting words in '\(inputFile)' \
56+
and writing the result into '\(outputFile)'.
57+
""")
58+
59+
// Read 'inputFile', count the words, and save to 'outputFile'.
60+
}
61+
}
62+
63+
Count.main()
64+
```
65+
66+
In the code above, the `inputFile` and `outputFile` properties use the `@Argument` property wrapper. `ArgumentParser` uses this wrapper to denote a positional command-line input — because `inputFile` is specified first in the `Count` type, it's the first value read from the command-line, and `outputFile` is read second.
67+
68+
We've implemented the command's logic in its `run()` method. Here, we're printing out a message confirming the names of the files the user gave. (You can find a full implementation of the completed command at the end of this guide.)
69+
70+
Finally, you tell the parser to execute the `Count` command by calling its static `main()` method. This method parses the command-line arguments, verifies that they match up with what we've defined in `Count`, and either calls the `run()` method or exits with a helpful message.
71+
72+
73+
## Working with Named Options
74+
75+
Our `count` tool may have a usability problem — it's not immediately clear whether a user should provide the input file first, or the output file. Instead of using positional arguments for our two inputs, let's specify that they should be labeled options:
76+
77+
```
78+
% count --input-file readme.md --output-file readme.counts
79+
Counting words in 'readme.md' and writing the result into 'readme.counts'.
80+
```
81+
82+
We do this by using the `@Option` property wrapper instead of `@Argument`:
83+
84+
```swift
85+
struct Count: ParsableCommand {
86+
@Option()
87+
var inputFile: String
88+
89+
@Option()
90+
var outputFile: String
91+
92+
func run() throws {
93+
print("""
94+
Counting words in '\(inputFile)' \
95+
and writing the result into '\(outputFile)'.
96+
""")
97+
98+
// Read 'inputFile', count the words, and save to 'outputFile'.
99+
}
100+
}
101+
```
102+
103+
The `@Option` property wrapper denotes a command-line input that looks like `--name value`, deriving its name from the name of your property.
104+
105+
This interface has a trade-off for the users of our `count` tool: With `@Argument`, users don't need to type as much, but have to remember whether the input file or the output file needs to be given first. Using `@Option` makes the user type a little more, but the distinction between values is explicit. Options are order-independent, as well, so the user can name the input and output files in either order:
106+
107+
```
108+
% count --output-file readme.counts --input-file readme.md
109+
Counting words in 'readme.md' and writing the result into 'readme.counts'.
110+
```
111+
112+
## Adding a Flag
113+
114+
Next, we want to add a `--verbose` flag to our tool, and only print the message if the user specifies that option:
115+
116+
```
117+
% count --input-file readme.md --output-file readme.counts
118+
(no output)
119+
% count --verbose --input-file readme.md --output-file readme.counts
120+
Counting words in 'readme.md' and writing the result into 'readme.counts'.
121+
```
122+
123+
Let's change our `Count` type to look like this:
124+
125+
```swift
126+
struct Count: ParsableCommand {
127+
@Option()
128+
var inputFile: String
129+
130+
@Option()
131+
var outputFile: String
132+
133+
@Flag()
134+
var verbose: Bool
135+
136+
func run() throws {
137+
if verbose {
138+
print("""
139+
Counting words in '\(inputFile)' \
140+
and writing the result into '\(outputFile)'.
141+
""")
142+
}
143+
144+
// Read 'inputFile', count the words, and save to 'outputFile'.
145+
}
146+
}
147+
```
148+
149+
The `@Flag` property wrapper denotes a command-line input that looks like `--name`, deriving its name from the name of your property. Flags are most frequently used for Boolean values, like the `verbose` property here.
150+
151+
152+
## Using Custom Names
153+
154+
We can customize the names of our options and add an alternative to the `verbose` flag, so that users can specify `-v` instead of `--verbose`. The new interface will look like this:
155+
156+
```
157+
% count -v -i readme.md -o readme.counts
158+
Counting words in 'readme.md' and writing the result into 'readme.counts'.
159+
% count --input readme.md --output readme.counts -v
160+
Counting words in 'readme.md' and writing the result into 'readme.counts'.
161+
% count -o readme.counts -i readme.md --verbose
162+
Counting words in 'readme.md' and writing the result into 'readme.counts'.
163+
```
164+
165+
Customize the input names by passing `name` parameters to the `@Option` and `@Flag` initializers:
166+
167+
```swift
168+
struct Count: ParsableCommand {
169+
@Option(name: [.short, .customLong("input")])
170+
var inputFile: String
171+
172+
@Option(name: [.short, .customLong("output")])
173+
var outputFile: String
174+
175+
@Flag(name: .shortAndLong)
176+
var verbose: Bool
177+
178+
func run() throws { ... }
179+
}
180+
```
181+
182+
The default name specification is `.long`, which uses a property's with a two-dash prefix. `.short` uses only the first letter of a property's name with a single-dash prefix, and allows combining groups of short options. You can specify custom short and long names with the `.customShort(_:)` and `.customLong(_:)` methods, respectively, or use the combined `.shortAndLong` property to specify the common case of both the short and long derived names.
183+
184+
## Providing Help
185+
186+
`ArgumentParser` automatically generates help for any command when a user provides the `-h` or `--help` flags:
187+
188+
```
189+
% count --help
190+
USAGE: count --input <input> --output <output> [--verbose]
191+
192+
OPTIONS:
193+
-i, --input <input>
194+
-o, --output <output>
195+
-v, --verbose
196+
-h, --help Show help information.
197+
```
198+
199+
This is a great start — you can see that all the custom names are visible, and the help shows that values are expected for the `--input` and `--output` options. However, our custom options and flag don't have any descriptive text. Let's add that now, by passing string literals as the `help` parameter:
200+
201+
```swift
202+
struct Count: ParsableCommand {
203+
@Option(name: [.short, .customLong("input")], help: "A file to read.")
204+
var inputFile: String
205+
206+
@Option(name: [.short, .customLong("output")], help: "A file to save word counts to.")
207+
var outputFile: String
208+
209+
@Flag(name: .shortAndLong, help: "Print status updates while counting.")
210+
var verbose: Bool
211+
212+
func run() throws { ... }
213+
}
214+
```
215+
216+
The help screen now includes descriptions for each parameter:
217+
218+
```
219+
% count -h
220+
USAGE: count --input <input> --output <output> [--verbose]
221+
222+
OPTIONS:
223+
-i, --input <input> A file to read.
224+
-o, --output <output> A file to save word counts to.
225+
-v, --verbose Print status updates while counting.
226+
-h, --help Show help information.
227+
228+
```
229+
230+
## The Complete Utility
231+
232+
As promised, here's the complete `count` command, for your experimentation:
233+
234+
```swift
235+
struct Count: ParsableCommand {
236+
static let configuration = CommandConfiguration(abstract: "Word counter.")
237+
238+
@Option(name: [.short, .customLong("input")], help: "A file to read.")
239+
var inputFile: String
240+
241+
@Option(name: [.short, .customLong("output")], help: "A file to save word counts to.")
242+
var outputFile: String
243+
244+
@Flag(name: .shortAndLong, help: "Print status updates while counting.")
245+
var verbose: Bool
246+
247+
func run() throws {
248+
if verbose {
249+
print("""
250+
Counting words in '\(inputFile)' \
251+
and writing the result into '\(outputFile)'.
252+
""")
253+
}
254+
255+
guard let input = try? String(contentsOfFile: inputFile) else {
256+
throw RuntimeError("Couldn't read from '\(inputFile)'!")
257+
}
258+
259+
let words = input.components(separatedBy: .whitespacesAndNewlines)
260+
.map { word in
261+
word.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
262+
.lowercased()
263+
}
264+
.compactMap { word in word.isEmpty ? nil : word }
265+
266+
let counts = Dictionary(grouping: words, by: { $0 })
267+
.mapValues { $0.count }
268+
.sorted(by: { $0.value > $1.value })
269+
270+
if verbose {
271+
print("Found \(counts.count) words.")
272+
}
273+
274+
let output = counts.map { word, count in "\(word): \(count)" }
275+
.joined(separator: "\n")
276+
277+
guard let _ = try? output.write(toFile: outputFile, atomically: true, encoding: .utf8) else {
278+
throw RuntimeError("Couldn't write to '\(outputFile)'!")
279+
}
280+
}
281+
}
282+
283+
struct RuntimeError: Error, CustomStringConvertible {
284+
var description: String
285+
286+
init(_ description: String) {
287+
self.description = description
288+
}
289+
}
290+
291+
Count.main()
292+
```

0 commit comments

Comments
 (0)