forked from apple/swift-argument-parser
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit f6ac7b8
Showing
79 changed files
with
11,333 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
.DS_Store | ||
/.build | ||
/Packages | ||
/*.xcodeproj | ||
.swiftpm |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
# Code of Conduct | ||
To be a truly great community, Swift.org needs to welcome developers from all walks of life, | ||
with different backgrounds, and with a wide range of experience. A diverse and friendly | ||
community will have more great ideas, more unique perspectives, and produce more great | ||
code. We will work diligently to make the Swift community welcoming to everyone. | ||
|
||
To give clarity of what is expected of our members, Swift.org has adopted the code of conduct | ||
defined by [contributor-covenant.org](https://www.contributor-covenant.org). This document is used across many open source | ||
communities, and we think it articulates our values well. The full text is copied below: | ||
|
||
### Contributor Code of Conduct v1.3 | ||
As contributors and maintainers of this project, and in the interest of fostering an open and | ||
welcoming community, we pledge to respect all people who contribute through reporting | ||
issues, posting feature requests, updating documentation, submitting pull requests or patches, | ||
and other activities. | ||
|
||
We are committed to making participation in this project a harassment-free experience for | ||
everyone, regardless of level of experience, gender, gender identity and expression, sexual | ||
orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or | ||
nationality. | ||
|
||
Examples of unacceptable behavior by participants include: | ||
- The use of sexualized language or imagery | ||
- Personal attacks | ||
- Trolling or insulting/derogatory comments | ||
- Public or private harassment | ||
- Publishing other’s private information, such as physical or electronic addresses, without explicit permission | ||
- Other unethical or unprofessional conduct | ||
|
||
Project maintainers have the right and responsibility to remove, edit, or reject comments, | ||
commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of | ||
Conduct, or to ban temporarily or permanently any contributor for other behaviors that they | ||
deem inappropriate, threatening, offensive, or harmful. | ||
|
||
By adopting this Code of Conduct, project maintainers commit themselves to fairly and | ||
consistently applying these principles to every aspect of managing this project. Project | ||
maintainers who do not follow or enforce the Code of Conduct may be permanently removed | ||
from the project team. | ||
|
||
This code of conduct applies both within project spaces and in public spaces when an | ||
individual is representing the project or its community. | ||
|
||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by | ||
contacting a project maintainer at [[email protected]](mailto:[email protected]). All complaints will be reviewed and | ||
investigated and will result in a response that is deemed necessary and appropriate to the | ||
circumstances. Maintainers are obligated to maintain confidentiality with regard to the reporter | ||
of an incident. | ||
|
||
*This policy is adapted from the Contributor Code of Conduct [version 1.3.0](http://contributor-covenant.org/version/1/3/0/).* | ||
|
||
### Reporting | ||
A working group of community members is committed to promptly addressing any [reported | ||
issues](mailto:[email protected]). Working group members are volunteers appointed by the project lead, with a | ||
preference for individuals with varied backgrounds and perspectives. Membership is expected | ||
to change regularly, and may grow or shrink. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
By submitting a pull request, you represent that you have the right to license | ||
your contribution to Apple and the community, and agree by submitting the patch | ||
that your contributions are licensed under the [Swift | ||
license](https://swift.org/LICENSE.txt). | ||
|
||
--- | ||
|
||
Before submitting the pull request, please make sure you have tested your | ||
changes and that they follow the Swift project [guidelines for contributing | ||
code](https://swift.org/contributing/#contributing-code). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,292 @@ | ||
# Getting Started with `ArgumentParser` | ||
|
||
Learn to set up and customize a simple command-line tool. | ||
|
||
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. | ||
|
||
## Adding `ArgumentParser` as a Dependency | ||
|
||
First, we need to add `swift-argument-parser` as a dependency to our package, | ||
and then include `"ArgumentParser"` as a dependency for our executable target. | ||
Our "Package.swift" file ends up looking like this: | ||
|
||
```swift | ||
// swift-tools-version:5.2 | ||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "random", | ||
dependencies: [ | ||
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "0.0.1"), | ||
], | ||
targets: [ | ||
.target( | ||
name: "count", | ||
dependencies: [.product(name: "ArgumentParser", package: "swift-argument-parser")]), | ||
] | ||
) | ||
``` | ||
|
||
## Building Our First Command | ||
|
||
Let's write a tool called `count` that reads an input file, counts the words, and writes the result to an output file. | ||
|
||
We can run our `count` tool like this: | ||
|
||
``` | ||
% count readme.md readme.counts | ||
Counting words in 'readme.md' and writing the result into 'readme.counts'. | ||
``` | ||
|
||
We'll define the initial version of the command as a type that conforms to the `ParsableCommand` protocol: | ||
|
||
```swift | ||
import ArgumentParser | ||
|
||
struct Count: ParsableCommand { | ||
@Argument() | ||
var inputFile: String | ||
|
||
@Argument() | ||
var outputFile: String | ||
|
||
func run() throws { | ||
print(""" | ||
Counting words in '\(inputFile)' \ | ||
and writing the result into '\(outputFile)'. | ||
""") | ||
|
||
// Read 'inputFile', count the words, and save to 'outputFile'. | ||
} | ||
} | ||
|
||
Count.main() | ||
``` | ||
|
||
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. | ||
|
||
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.) | ||
|
||
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. | ||
|
||
|
||
## Working with Named Options | ||
|
||
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: | ||
|
||
``` | ||
% count --input-file readme.md --output-file readme.counts | ||
Counting words in 'readme.md' and writing the result into 'readme.counts'. | ||
``` | ||
|
||
We do this by using the `@Option` property wrapper instead of `@Argument`: | ||
|
||
```swift | ||
struct Count: ParsableCommand { | ||
@Option() | ||
var inputFile: String | ||
|
||
@Option() | ||
var outputFile: String | ||
|
||
func run() throws { | ||
print(""" | ||
Counting words in '\(inputFile)' \ | ||
and writing the result into '\(outputFile)'. | ||
""") | ||
|
||
// Read 'inputFile', count the words, and save to 'outputFile'. | ||
} | ||
} | ||
``` | ||
|
||
The `@Option` property wrapper denotes a command-line input that looks like `--name value`, deriving its name from the name of your property. | ||
|
||
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: | ||
|
||
``` | ||
% count --output-file readme.counts --input-file readme.md | ||
Counting words in 'readme.md' and writing the result into 'readme.counts'. | ||
``` | ||
|
||
## Adding a Flag | ||
|
||
Next, we want to add a `--verbose` flag to our tool, and only print the message if the user specifies that option: | ||
|
||
``` | ||
% count --input-file readme.md --output-file readme.counts | ||
(no output) | ||
% count --verbose --input-file readme.md --output-file readme.counts | ||
Counting words in 'readme.md' and writing the result into 'readme.counts'. | ||
``` | ||
|
||
Let's change our `Count` type to look like this: | ||
|
||
```swift | ||
struct Count: ParsableCommand { | ||
@Option() | ||
var inputFile: String | ||
|
||
@Option() | ||
var outputFile: String | ||
|
||
@Flag() | ||
var verbose: Bool | ||
|
||
func run() throws { | ||
if verbose { | ||
print(""" | ||
Counting words in '\(inputFile)' \ | ||
and writing the result into '\(outputFile)'. | ||
""") | ||
} | ||
|
||
// Read 'inputFile', count the words, and save to 'outputFile'. | ||
} | ||
} | ||
``` | ||
|
||
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. | ||
|
||
|
||
## Using Custom Names | ||
|
||
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: | ||
|
||
``` | ||
% count -v -i readme.md -o readme.counts | ||
Counting words in 'readme.md' and writing the result into 'readme.counts'. | ||
% count --input readme.md --output readme.counts -v | ||
Counting words in 'readme.md' and writing the result into 'readme.counts'. | ||
% count -o readme.counts -i readme.md --verbose | ||
Counting words in 'readme.md' and writing the result into 'readme.counts'. | ||
``` | ||
|
||
Customize the input names by passing `name` parameters to the `@Option` and `@Flag` initializers: | ||
|
||
```swift | ||
struct Count: ParsableCommand { | ||
@Option(name: [.short, .customLong("input")]) | ||
var inputFile: String | ||
|
||
@Option(name: [.short, .customLong("output")]) | ||
var outputFile: String | ||
|
||
@Flag(name: .shortAndLong) | ||
var verbose: Bool | ||
|
||
func run() throws { ... } | ||
} | ||
``` | ||
|
||
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. | ||
|
||
## Providing Help | ||
|
||
`ArgumentParser` automatically generates help for any command when a user provides the `-h` or `--help` flags: | ||
|
||
``` | ||
% count --help | ||
USAGE: count --input <input> --output <output> [--verbose] | ||
OPTIONS: | ||
-i, --input <input> | ||
-o, --output <output> | ||
-v, --verbose | ||
-h, --help Show help information. | ||
``` | ||
|
||
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: | ||
|
||
```swift | ||
struct Count: ParsableCommand { | ||
@Option(name: [.short, .customLong("input")], help: "A file to read.") | ||
var inputFile: String | ||
|
||
@Option(name: [.short, .customLong("output")], help: "A file to save word counts to.") | ||
var outputFile: String | ||
|
||
@Flag(name: .shortAndLong, help: "Print status updates while counting.") | ||
var verbose: Bool | ||
|
||
func run() throws { ... } | ||
} | ||
``` | ||
|
||
The help screen now includes descriptions for each parameter: | ||
|
||
``` | ||
% count -h | ||
USAGE: count --input <input> --output <output> [--verbose] | ||
OPTIONS: | ||
-i, --input <input> A file to read. | ||
-o, --output <output> A file to save word counts to. | ||
-v, --verbose Print status updates while counting. | ||
-h, --help Show help information. | ||
``` | ||
|
||
## The Complete Utility | ||
|
||
As promised, here's the complete `count` command, for your experimentation: | ||
|
||
```swift | ||
struct Count: ParsableCommand { | ||
static let configuration = CommandConfiguration(abstract: "Word counter.") | ||
|
||
@Option(name: [.short, .customLong("input")], help: "A file to read.") | ||
var inputFile: String | ||
|
||
@Option(name: [.short, .customLong("output")], help: "A file to save word counts to.") | ||
var outputFile: String | ||
|
||
@Flag(name: .shortAndLong, help: "Print status updates while counting.") | ||
var verbose: Bool | ||
|
||
func run() throws { | ||
if verbose { | ||
print(""" | ||
Counting words in '\(inputFile)' \ | ||
and writing the result into '\(outputFile)'. | ||
""") | ||
} | ||
|
||
guard let input = try? String(contentsOfFile: inputFile) else { | ||
throw RuntimeError("Couldn't read from '\(inputFile)'!") | ||
} | ||
|
||
let words = input.components(separatedBy: .whitespacesAndNewlines) | ||
.map { word in | ||
word.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) | ||
.lowercased() | ||
} | ||
.compactMap { word in word.isEmpty ? nil : word } | ||
|
||
let counts = Dictionary(grouping: words, by: { $0 }) | ||
.mapValues { $0.count } | ||
.sorted(by: { $0.value > $1.value }) | ||
|
||
if verbose { | ||
print("Found \(counts.count) words.") | ||
} | ||
|
||
let output = counts.map { word, count in "\(word): \(count)" } | ||
.joined(separator: "\n") | ||
|
||
guard let _ = try? output.write(toFile: outputFile, atomically: true, encoding: .utf8) else { | ||
throw RuntimeError("Couldn't write to '\(outputFile)'!") | ||
} | ||
} | ||
} | ||
|
||
struct RuntimeError: Error, CustomStringConvertible { | ||
var description: String | ||
|
||
init(_ description: String) { | ||
self.description = description | ||
} | ||
} | ||
|
||
Count.main() | ||
``` |
Oops, something went wrong.