Skip to content

Commit

Permalink
Initial import of ArgumentParser
Browse files Browse the repository at this point in the history
  • Loading branch information
natecook1000 committed Feb 27, 2020
0 parents commit f6ac7b8
Show file tree
Hide file tree
Showing 79 changed files with 11,333 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
.swiftpm
55 changes: 55 additions & 0 deletions CODE_OF_CONDUCT.md
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.
10 changes: 10 additions & 0 deletions CONTRIBUTING.md
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).
292 changes: 292 additions & 0 deletions Documentation/01 Getting Started.md
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()
```
Loading

0 comments on commit f6ac7b8

Please sign in to comment.