Merge documentation and changelog generation
Merge documentation generation into a single binary, `bin/gen`. This includes: The changelog, man pages, the readme, and the book. type: reform
This commit is contained in:
parent
1f8023d13a
commit
04338e3501
30
.github/workflows/build.yaml
vendored
30
.github/workflows/build.yaml
vendored
|
@ -35,6 +35,15 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Cache cargo registry
|
- name: Cache cargo registry
|
||||||
uses: actions/cache@v1
|
uses: actions/cache@v1
|
||||||
|
@ -63,7 +72,7 @@ jobs:
|
||||||
components: clippy, rustfmt
|
components: clippy, rustfmt
|
||||||
override: true
|
override: true
|
||||||
|
|
||||||
- name: Version
|
- name: Info
|
||||||
run: |
|
run: |
|
||||||
rustup --version
|
rustup --version
|
||||||
cargo --version
|
cargo --version
|
||||||
|
@ -79,8 +88,10 @@ jobs:
|
||||||
run: cargo clippy --all
|
run: cargo clippy --all
|
||||||
|
|
||||||
- name: Lint
|
- name: Lint
|
||||||
if: matrix.os != 'windows-latest'
|
if: matrix.os == 'macos-latest'
|
||||||
run: ./bin/lint
|
run: |
|
||||||
|
brew install ripgrep
|
||||||
|
./bin/lint
|
||||||
|
|
||||||
- name: Install Nightly
|
- name: Install Nightly
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
|
@ -93,16 +104,11 @@ jobs:
|
||||||
- name: Check Formatting
|
- name: Check Formatting
|
||||||
run: cargo +nightly fmt --all -- --check
|
run: cargo +nightly fmt --all -- --check
|
||||||
|
|
||||||
- name: Check Completion Scripts
|
- name: Check Generated
|
||||||
if: matrix.os != 'windows-latest'
|
if: matrix.os == 'macos-latest'
|
||||||
run: |
|
run: |
|
||||||
./bin/generate-completions
|
brew install help2man
|
||||||
git diff --no-ext-diff --exit-code
|
cargo run --package gen all
|
||||||
|
|
||||||
- name: Check Readme Table of Contents
|
|
||||||
if: matrix.os != 'windows-latest'
|
|
||||||
run: |
|
|
||||||
cargo run --package update-readme toc
|
|
||||||
git diff --no-ext-diff --exit-code
|
git diff --no-ext-diff --exit-code
|
||||||
|
|
||||||
- name: Install `mdbook`
|
- name: Install `mdbook`
|
||||||
|
|
7
.ignore
Normal file
7
.ignore
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
/CHANGELOG.md
|
||||||
|
/README.md
|
||||||
|
/completions
|
||||||
|
/man
|
||||||
|
/book/src/commands
|
||||||
|
/book/src/SUMMARY.md
|
||||||
|
/book/src/introduction.md
|
|
@ -2,9 +2,11 @@ Changelog
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
|
||||||
UNRELEASED - 2020-04-13
|
UNRELEASED - 2020-04-18
|
||||||
-----------------------
|
-----------------------
|
||||||
- :sparkles: [`xxxxxxxxxxxx`](https://github.com/casey/intermodal/commits/master) Partially implement BEP 53 - Fixes [#245](https://github.com/casey/intermodal/issues/245) - _strickinato <aaronstrick@gmail.com>_
|
- :art: [`xxxxxxxxxxxx`](https://github.com/casey/intermodal/commits/master) Merge documentation and changelog generation - _Casey Rodarmor <casey@rodarmor.com>_
|
||||||
|
- :books: [`1f8023d13a39`](https://github.com/casey/intermodal/commit/1f8023d13a399e381176c20bbb6a71763b7c352a) Fix directory link in README - _Matt Boulanger <Celeo@users.noreply.github.com>_
|
||||||
|
- :sparkles: [`cb8b5a691945`](https://github.com/casey/intermodal/commit/cb8b5a691945b8108676f95d2888774263be8cc8) Partially implement BEP 53 - Fixes [#245](https://github.com/casey/intermodal/issues/245) - _strickinato <aaronstrick@gmail.com>_
|
||||||
- :books: [`6185d6c8a27c`](https://github.com/casey/intermodal/commit/6185d6c8a27c0d603f0434e98000c8e4a868dcc8) Add table of packages to readme ([#372](https://github.com/casey/intermodal/pull/372)) - Fixes [#369](https://github.com/casey/intermodal/issues/369) - _Casey Rodarmor <casey@rodarmor.com>_
|
- :books: [`6185d6c8a27c`](https://github.com/casey/intermodal/commit/6185d6c8a27c0d603f0434e98000c8e4a868dcc8) Add table of packages to readme ([#372](https://github.com/casey/intermodal/pull/372)) - Fixes [#369](https://github.com/casey/intermodal/issues/369) - _Casey Rodarmor <casey@rodarmor.com>_
|
||||||
- :wrench: [`ddf097c83690`](https://github.com/casey/intermodal/commit/ddf097c8369002748992165f81e9a1bdbe6eff98) Fix `publish` recipe ([#368](https://github.com/casey/intermodal/pull/368)) - _Casey Rodarmor <casey@rodarmor.com>_
|
- :wrench: [`ddf097c83690`](https://github.com/casey/intermodal/commit/ddf097c8369002748992165f81e9a1bdbe6eff98) Fix `publish` recipe ([#368](https://github.com/casey/intermodal/pull/368)) - _Casey Rodarmor <casey@rodarmor.com>_
|
||||||
|
|
||||||
|
|
124
Cargo.lock
generated
124
Cargo.lock
generated
|
@ -42,6 +42,49 @@ dependencies = [
|
||||||
"nodrop",
|
"nodrop",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "askama"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "10a1fb9e41eb366cbcd267da2094be5b7e62fdbca9f82091e7503e80f885050d"
|
||||||
|
dependencies = [
|
||||||
|
"askama_derive",
|
||||||
|
"askama_escape",
|
||||||
|
"askama_shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "askama_derive"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e1012c270085fa35ece6a48a569544fde85b6d9ee41074c7b706cc912a03f939"
|
||||||
|
dependencies = [
|
||||||
|
"askama_shared",
|
||||||
|
"nom",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "askama_escape"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a577aeba5fec1aafb9f195d98cfcc38a78b588e4ebf9b15f62ca1c7aa33795a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "askama_shared"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ee517f4e33c27b129928e71d8a044d54c513e72e0b72ec5c4f5f1823e9de353"
|
||||||
|
dependencies = [
|
||||||
|
"askama_escape",
|
||||||
|
"humansize",
|
||||||
|
"num-traits",
|
||||||
|
"serde",
|
||||||
|
"toml",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atty"
|
name = "atty"
|
||||||
version = "0.2.14"
|
version = "0.2.14"
|
||||||
|
@ -133,23 +176,6 @@ version = "0.1.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "changelog"
|
|
||||||
version = "0.0.0"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"cargo_toml",
|
|
||||||
"chrono",
|
|
||||||
"fehler",
|
|
||||||
"git2",
|
|
||||||
"serde",
|
|
||||||
"serde_yaml",
|
|
||||||
"structopt",
|
|
||||||
"strum",
|
|
||||||
"strum_macros",
|
|
||||||
"url",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "chrono"
|
||||||
version = "0.4.11"
|
version = "0.4.11"
|
||||||
|
@ -313,6 +339,29 @@ version = "1.0.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
|
checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gen"
|
||||||
|
version = "0.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"askama",
|
||||||
|
"cargo_toml",
|
||||||
|
"chrono",
|
||||||
|
"fehler",
|
||||||
|
"git2",
|
||||||
|
"globset",
|
||||||
|
"log",
|
||||||
|
"pretty_env_logger",
|
||||||
|
"regex",
|
||||||
|
"serde",
|
||||||
|
"serde_yaml",
|
||||||
|
"structopt",
|
||||||
|
"strum",
|
||||||
|
"strum_macros",
|
||||||
|
"tempfile",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.1.14"
|
version = "0.1.14"
|
||||||
|
@ -339,12 +388,6 @@ dependencies = [
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "glob"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "globset"
|
name = "globset"
|
||||||
version = "0.4.5"
|
version = "0.4.5"
|
||||||
|
@ -376,6 +419,12 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "humansize"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6cab2627acfc432780848602f3f558f7e9dd427352224b0d9324025796d2a5e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "humantime"
|
name = "humantime"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
|
@ -532,16 +581,6 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "man"
|
|
||||||
version = "0.0.0"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"fehler",
|
|
||||||
"regex",
|
|
||||||
"tempfile",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "matches"
|
name = "matches"
|
||||||
version = "0.1.8"
|
version = "0.1.8"
|
||||||
|
@ -572,6 +611,16 @@ version = "0.1.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
|
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nom"
|
||||||
|
version = "5.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b471253da97532da4b61552249c521e01e736071f71c1a4f7ebbfbf0a06aad6"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-integer"
|
name = "num-integer"
|
||||||
version = "0.1.42"
|
version = "0.1.42"
|
||||||
|
@ -1143,15 +1192,6 @@ version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
|
checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "update-readme"
|
|
||||||
version = "0.0.0"
|
|
||||||
dependencies = [
|
|
||||||
"glob",
|
|
||||||
"regex",
|
|
||||||
"structopt",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.1.1"
|
version = "2.1.1"
|
||||||
|
|
12
Cargo.toml
12
Cargo.toml
|
@ -62,15 +62,9 @@ temptree = "0.0.0"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
|
# generate documentation
|
||||||
|
"bin/gen",
|
||||||
|
|
||||||
# run commands for demo animation
|
# run commands for demo animation
|
||||||
"bin/demo",
|
"bin/demo",
|
||||||
|
|
||||||
# generate table of contents and table of supported BEPs in README.md
|
|
||||||
"bin/update-readme",
|
|
||||||
|
|
||||||
# generate the changelog from git commits
|
|
||||||
"bin/changelog",
|
|
||||||
|
|
||||||
# generate man page
|
|
||||||
"bin/man"
|
|
||||||
]
|
]
|
||||||
|
|
67
README.md
67
README.md
|
@ -1,4 +1,4 @@
|
||||||
# intermodal: a 40' shipping container for the Internet
|
# Intermodal: A 40' shipping container for the Internet
|
||||||
|
|
||||||
[![Crate](https://img.shields.io/crates/v/imdl.svg?logo=rust)](https://crates.io/crates/imdl)
|
[![Crate](https://img.shields.io/crates/v/imdl.svg?logo=rust)](https://crates.io/crates/imdl)
|
||||||
[![Build](https://github.com/casey/intermodal/workflows/Build/badge.svg)](https://github.com/casey/intermodal/actions)
|
[![Build](https://github.com/casey/intermodal/workflows/Build/badge.svg)](https://github.com/casey/intermodal/actions)
|
||||||
|
@ -13,37 +13,34 @@ For more about the project and its goals, check out [this post](https://rodarmor
|
||||||
|
|
||||||
![demonstration animation](https://raw.githubusercontent.com/casey/intermodal/master/www/demo.gif)
|
![demonstration animation](https://raw.githubusercontent.com/casey/intermodal/master/www/demo.gif)
|
||||||
|
|
||||||
## Manual
|
## Table of Contents
|
||||||
|
|
||||||
- [General](#general)
|
- [Installation](#installation)
|
||||||
- [Installation](#installation)
|
- [Supported Operating Systems](#supported-operating-systems)
|
||||||
- [Supported Operating Systems](#supported-operating-systems)
|
- [Packages](#packages)
|
||||||
- [Packages](#packages)
|
- [Pre-built binaries](#pre-built-binaries)
|
||||||
- [Pre-built binaries](#pre-built-binaries)
|
- [Cargo](#cargo)
|
||||||
- [Cargo](#cargo)
|
- [Shell Completion Scripts](#shell-completion-scripts)
|
||||||
- [Shell Completion Scripts](#shell-completion-scripts)
|
- [Semantic Versioning](#semantic-versioning)
|
||||||
- [Semantic Versioning](#semantic-versioning)
|
- [Unstable Features](#unstable-features)
|
||||||
- [Unstable Features](#unstable-features)
|
- [Source Signatures](#source-signatures)
|
||||||
- [Source Signatures](#source-signatures)
|
|
||||||
- [Acknowledgments](#acknowledgments)
|
- [Acknowledgments](#acknowledgments)
|
||||||
|
|
||||||
## General
|
## Installation
|
||||||
|
|
||||||
### Installation
|
### Supported Operating Systems
|
||||||
|
|
||||||
#### Supported Operating Systems
|
|
||||||
|
|
||||||
`imdl` supports Linux, MacOS, and Windows, and should work on other unix OSes.
|
`imdl` supports Linux, MacOS, and Windows, and should work on other unix OSes.
|
||||||
If it does not, please open an issue!
|
If it does not, please open an issue!
|
||||||
|
|
||||||
#### Packages
|
### Packages
|
||||||
|
|
||||||
| Operating System | Package Manager | Package | Command |
|
| Operating System | Package Manager | Package | Command |
|
||||||
|:--------------------------------------------------------------------:|:-----------------------------------:|:-------------------------------------------------------------------------:|---------------------:|
|
|:--------------------------------------------------------------------:|:-----------------------------------:|:-------------------------------------------------------------------------:|---------------------:|
|
||||||
| [Various](https://forge.rust-lang.org/release/platform-support.html) | [Cargo](https://www.rust-lang.org) | [imdl](https://crates.io/crates/imdl) | `cargo install imdl` |
|
| [Various](https://forge.rust-lang.org/release/platform-support.html) | [Cargo](https://www.rust-lang.org) | [imdl](https://crates.io/crates/imdl) | `cargo install imdl` |
|
||||||
| [Arch Linux](https://www.archlinux.org) | [Yay](https://github.com/Jguer/yay) | [intermodal](https://aur.archlinux.org/packages/intermodal)<sup>AUR</sup> | `yay -S intermodal` |
|
| [Arch Linux](https://www.archlinux.org) | [Yay](https://github.com/Jguer/yay) | [intermodal](https://aur.archlinux.org/packages/intermodal)<sup>AUR</sup> | `yay -S intermodal` |
|
||||||
|
|
||||||
#### Pre-built binaries
|
### Pre-built binaries
|
||||||
|
|
||||||
Pre-built binaries for Linux, macOS, and Windows can be found on
|
Pre-built binaries for Linux, macOS, and Windows can be found on
|
||||||
[the releases page](https://github.com/casey/intermodal/releases).
|
[the releases page](https://github.com/casey/intermodal/releases).
|
||||||
|
@ -79,17 +76,29 @@ For `fish`, it should be done in `~/.config/fish/config.fish`:
|
||||||
echo 'set -gx PATH ~/bin $PATH' >> ~/.config/fish/config.fish
|
echo 'set -gx PATH ~/bin $PATH' >> ~/.config/fish/config.fish
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Cargo
|
### Cargo
|
||||||
|
|
||||||
`imdl` is written in [Rust](https://www.rust-lang.org/) and can be built from
|
`imdl` is written in [Rust](https://www.rust-lang.org/) and can be built from
|
||||||
source and installed with `cargo install imdl`. To get Rust, use the
|
source and installed with `cargo install imdl`. To get Rust, use the
|
||||||
[rustup installer](https://rustup.rs/).
|
[rustup installer](https://rustup.rs/).
|
||||||
|
|
||||||
### Shell Completion Scripts
|
## Shell Completion Scripts
|
||||||
|
|
||||||
Shell completion scripts for Bash, Zsh, Fish, PowerShell, and Elvish are
|
Shell completion scripts for Bash, Zsh, Fish, PowerShell, and Elvish are
|
||||||
available in the [completions directory](./completions). Please refer to your
|
available in the [completions directory](./completions), included in all
|
||||||
shell's documentation for how to install them.
|
[binary releases](https://github.com/casey/imdl/releases).
|
||||||
|
|
||||||
|
For Bash, move `imdl.bash` to `$XDG_CONFIG_HOME/bash_completion` or
|
||||||
|
`/etc/bash_completion.d/`.
|
||||||
|
|
||||||
|
For Fish, move `imdl.fish` to `$HOME/.config/fish/completions/`.
|
||||||
|
|
||||||
|
For the Z shell, move `_imdl` to one of your `$fpath` directories.
|
||||||
|
|
||||||
|
For PowerShell, add `. _imdl.ps1` to your PowerShell
|
||||||
|
[profile](https://technet.microsoft.com/en-us/library/bb613488(v=vs.85).aspx)
|
||||||
|
(note the leading period). If the `_imdl.ps1` file is not on your `PATH`, do
|
||||||
|
`. /path/to/_imdl.ps1` instead.
|
||||||
|
|
||||||
The `imdl` binary can also generate the same completion scripts at runtime,
|
The `imdl` binary can also generate the same completion scripts at runtime,
|
||||||
using the `completions` command:
|
using the `completions` command:
|
||||||
|
@ -98,7 +107,15 @@ using the `completions` command:
|
||||||
$ imdl completions --shell bash > imdl.bash
|
$ imdl completions --shell bash > imdl.bash
|
||||||
```
|
```
|
||||||
|
|
||||||
### Semantic Versioning
|
The `--dir` argument can be used to write a completion script into a directory
|
||||||
|
with a filename that's appropriate for the shell. For example, the following
|
||||||
|
command will write the Z shell completion script to `$fpath[0]/_imdl`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ imdl completions --shell zsh --dir $fpath[0]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Semantic Versioning
|
||||||
|
|
||||||
Intermodal follows [semantic versioning](https://semver.org/).
|
Intermodal follows [semantic versioning](https://semver.org/).
|
||||||
|
|
||||||
|
@ -110,14 +127,14 @@ In particular:
|
||||||
- vX.Y.Z: Breaking changes may only be introduced with a major version number
|
- vX.Y.Z: Breaking changes may only be introduced with a major version number
|
||||||
bump
|
bump
|
||||||
|
|
||||||
### Unstable Features
|
## Unstable Features
|
||||||
|
|
||||||
To avoid premature stabilization and excessive version churn, unstable features
|
To avoid premature stabilization and excessive version churn, unstable features
|
||||||
are unavailable unless the `--unstable` / `-u` flag is passed, for example
|
are unavailable unless the `--unstable` / `-u` flag is passed, for example
|
||||||
`imdl --unstable torrent create .`. Unstable features may be changed or removed
|
`imdl --unstable torrent create .`. Unstable features may be changed or removed
|
||||||
at any time.
|
at any time.
|
||||||
|
|
||||||
### Source Signatures
|
## Source Signatures
|
||||||
|
|
||||||
All commits to the intermodal master branch signed with Casey Rodarmor's PGP
|
All commits to the intermodal master branch signed with Casey Rodarmor's PGP
|
||||||
key with fingerprint `3259DAEDB29636B0E2025A70556186B153EC6FE0`, which can be
|
key with fingerprint `3259DAEDB29636B0E2025A70556186B153EC6FE0`, which can be
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "changelog"
|
|
||||||
version = "0.0.0"
|
|
||||||
authors = ["Casey Rodarmor <casey@rodarmor.com>"]
|
|
||||||
edition = "2018"
|
|
||||||
publish = false
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
anyhow = "1.0.28"
|
|
||||||
cargo_toml = "0.8.0"
|
|
||||||
chrono = "0.4.11"
|
|
||||||
fehler = "1.0.0"
|
|
||||||
git2 = "0.13.1"
|
|
||||||
serde_yaml = "0.8.11"
|
|
||||||
structopt = "0.3.12"
|
|
||||||
strum = "0.18.0"
|
|
||||||
strum_macros = "0.18.0"
|
|
||||||
|
|
||||||
[dependencies.serde]
|
|
||||||
version = "1.0.106"
|
|
||||||
features = ["derive"]
|
|
||||||
|
|
||||||
[dependencies.url]
|
|
||||||
version = "2.1.1"
|
|
||||||
features = ["serde"]
|
|
|
@ -1,14 +0,0 @@
|
||||||
use crate::common::*;
|
|
||||||
|
|
||||||
mod changelog;
|
|
||||||
mod common;
|
|
||||||
mod entry;
|
|
||||||
mod kind;
|
|
||||||
mod metadata;
|
|
||||||
mod opt;
|
|
||||||
mod release;
|
|
||||||
|
|
||||||
#[throws]
|
|
||||||
fn main() {
|
|
||||||
Opt::from_args().run()?;
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
use crate::common::*;
|
|
||||||
|
|
||||||
#[derive(StructOpt)]
|
|
||||||
pub(crate) enum Opt {
|
|
||||||
IssueTemplate,
|
|
||||||
Types,
|
|
||||||
Update,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Opt {
|
|
||||||
#[throws]
|
|
||||||
pub(crate) fn run(self) {
|
|
||||||
match self {
|
|
||||||
Self::Types => {
|
|
||||||
for kind in Kind::VARIANTS {
|
|
||||||
println!("{}", kind)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self::IssueTemplate => {
|
|
||||||
println!("{}", Metadata::default().to_string());
|
|
||||||
}
|
|
||||||
Self::Update => {
|
|
||||||
let cwd = env::current_dir()?;
|
|
||||||
|
|
||||||
let repo = Repository::discover(cwd)?;
|
|
||||||
|
|
||||||
let changelog = Changelog::new(&repo)?;
|
|
||||||
|
|
||||||
let dst = repo.workdir().unwrap().join("CHANGELOG.md");
|
|
||||||
|
|
||||||
fs::write(dst, changelog.to_string())?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
31
bin/gen/Cargo.toml
Normal file
31
bin/gen/Cargo.toml
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
[package]
|
||||||
|
name = "gen"
|
||||||
|
version = "0.0.0"
|
||||||
|
authors = ["Casey Rodarmor <casey@rodarmor.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.28"
|
||||||
|
askama = "0.9.0"
|
||||||
|
cargo_toml = "0.8.0"
|
||||||
|
chrono = "0.4.11"
|
||||||
|
fehler = "1.0.0"
|
||||||
|
git2 = "0.13.1"
|
||||||
|
globset = "0.4.5"
|
||||||
|
log = "0.4.8"
|
||||||
|
pretty_env_logger = "0.4.0"
|
||||||
|
regex = "1.3.6"
|
||||||
|
serde_yaml = "0.8.11"
|
||||||
|
structopt = "0.3.12"
|
||||||
|
strum = "0.18.0"
|
||||||
|
strum_macros = "0.18.0"
|
||||||
|
tempfile = "3.1.0"
|
||||||
|
|
||||||
|
[dependencies.serde]
|
||||||
|
version = "1.0.106"
|
||||||
|
features = ["derive"]
|
||||||
|
|
||||||
|
[dependencies.url]
|
||||||
|
version = "2.1.1"
|
||||||
|
features = ["serde"]
|
41
bin/gen/config.yaml
Normal file
41
bin/gen/config.yaml
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
changelog:
|
||||||
|
1f8023d13a399e381176c20bbb6a71763b7c352a:
|
||||||
|
type: documentation
|
||||||
|
|
||||||
|
examples:
|
||||||
|
- command: imdl
|
||||||
|
text: "The binary is called `imdl`:"
|
||||||
|
code: "imdl --help"
|
||||||
|
|
||||||
|
- command: imdl torrent
|
||||||
|
text: "BitTorrent metainfo related functionality is under the `torrent` subcommand:"
|
||||||
|
code: "imdl torrent --help"
|
||||||
|
|
||||||
|
- command: imdl torrent create
|
||||||
|
text: "Intermodal can be used to create `.torrent` files:"
|
||||||
|
code: "imdl torrent create --input foo"
|
||||||
|
|
||||||
|
- command: imdl torrent show
|
||||||
|
text: "Print information about existing `.torrent` files:"
|
||||||
|
code: "imdl torrent show --input foo.torrent"
|
||||||
|
|
||||||
|
- command: imdl torrent verify
|
||||||
|
text: "Verify downloaded torrents:"
|
||||||
|
code: "imdl torrent verify --input foo.torrent --content foo"
|
||||||
|
|
||||||
|
- command: imdl torrent link
|
||||||
|
text: "Generate magnet links from `.torrent` files:"
|
||||||
|
code: "imdl torrent link --input foo.torrent"
|
||||||
|
|
||||||
|
- command: imdl torrent piece-length
|
||||||
|
text: "Show infromation about the piece length picker:"
|
||||||
|
code: "imdl torrent piece-length"
|
||||||
|
|
||||||
|
- command: imdl completions
|
||||||
|
text: "Print completion scripts for the `imdl` binary:"
|
||||||
|
code: "imdl completions --shell zsh"
|
||||||
|
|
||||||
|
- command: imdl torrent stats
|
||||||
|
unstable: true
|
||||||
|
text: "Print information about a collection of torrents:"
|
||||||
|
code: "imdl --unstable torrent stats --input dir"
|
|
@ -1,16 +1,15 @@
|
||||||
use crate::common::*;
|
use crate::common::*;
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct Bin {
|
pub(crate) struct Bin {
|
||||||
pub(crate) bin: String,
|
path: PathBuf,
|
||||||
pub(crate) subcommands: Vec<Subcommand>,
|
pub(crate) subcommands: Vec<Subcommand>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Bin {
|
impl Bin {
|
||||||
#[throws]
|
#[throws]
|
||||||
pub(crate) fn new(bin: &str) -> Self {
|
pub(crate) fn new(path: &Path) -> Bin {
|
||||||
let mut bin = Bin {
|
let mut bin = Bin {
|
||||||
bin: bin.into(),
|
path: path.into(),
|
||||||
subcommands: Vec::new(),
|
subcommands: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -23,7 +22,7 @@ impl Bin {
|
||||||
|
|
||||||
#[throws]
|
#[throws]
|
||||||
fn add_subcommands(&mut self, command: &mut Vec<String>) {
|
fn add_subcommands(&mut self, command: &mut Vec<String>) {
|
||||||
let subcommand = Subcommand::new(&self.bin, command.clone())?;
|
let subcommand = Subcommand::new(&self.path, command.clone())?;
|
||||||
|
|
||||||
for name in &subcommand.subcommands {
|
for name in &subcommand.subcommands {
|
||||||
command.push(name.into());
|
command.push(name.into());
|
|
@ -6,8 +6,8 @@ pub(crate) struct Changelog {
|
||||||
|
|
||||||
impl Changelog {
|
impl Changelog {
|
||||||
#[throws]
|
#[throws]
|
||||||
pub(crate) fn new(repo: &Repository) -> Self {
|
pub(crate) fn new(project: &Project) -> Self {
|
||||||
let mut current = repo.head()?.peel_to_commit()?;
|
let mut current = project.repo.head()?.peel_to_commit()?;
|
||||||
|
|
||||||
let mut entries = Vec::new();
|
let mut entries = Vec::new();
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ impl Changelog {
|
||||||
let manifest_bytes = current
|
let manifest_bytes = current
|
||||||
.tree()?
|
.tree()?
|
||||||
.get_path("Cargo.toml".as_ref())?
|
.get_path("Cargo.toml".as_ref())?
|
||||||
.to_object(&repo)?
|
.to_object(&project.repo)?
|
||||||
.as_blob()
|
.as_blob()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.content()
|
.content()
|
||||||
|
@ -32,7 +32,12 @@ impl Changelog {
|
||||||
|
|
||||||
let manifest = Manifest::from_slice(&manifest_bytes)?;
|
let manifest = Manifest::from_slice(&manifest_bytes)?;
|
||||||
|
|
||||||
let entry = Entry::new(¤t, manifest.package.unwrap().version.as_ref(), head)?;
|
let entry = Entry::new(
|
||||||
|
¤t,
|
||||||
|
manifest.package.unwrap().version.as_ref(),
|
||||||
|
head,
|
||||||
|
&project.config,
|
||||||
|
)?;
|
||||||
|
|
||||||
entries.push(entry);
|
entries.push(entry);
|
||||||
}
|
}
|
17
bin/gen/src/cmd.rs
Normal file
17
bin/gen/src/cmd.rs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
macro_rules! cmd {
|
||||||
|
{
|
||||||
|
$bin:expr,
|
||||||
|
$($arg:expr),*
|
||||||
|
$(,)?
|
||||||
|
} => {
|
||||||
|
{
|
||||||
|
let mut command = Command::new($bin);
|
||||||
|
|
||||||
|
$(
|
||||||
|
command.arg($arg);
|
||||||
|
)*
|
||||||
|
|
||||||
|
command
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
bin/gen/src/command_ext.rs
Normal file
22
bin/gen/src/command_ext.rs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
use crate::common::*;
|
||||||
|
|
||||||
|
pub(crate) trait CommandExt {
|
||||||
|
#[throws]
|
||||||
|
fn out(&mut self) -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommandExt for Command {
|
||||||
|
#[throws]
|
||||||
|
fn out(&mut self) -> String {
|
||||||
|
let output = self
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::inherit())
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
output.status.into_result()?;
|
||||||
|
|
||||||
|
let text = String::from_utf8(output.stdout)?;
|
||||||
|
|
||||||
|
text
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,15 +1,21 @@
|
||||||
pub(crate) use std::{
|
pub(crate) use std::{
|
||||||
cmp::{Ord, PartialOrd},
|
cmp::{Ord, PartialOrd},
|
||||||
|
collections::{BTreeMap, BTreeSet},
|
||||||
env,
|
env,
|
||||||
fmt::{self, Display, Formatter},
|
fmt::{self, Display, Formatter},
|
||||||
fs, str,
|
fs::{self, File},
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
process::{Command, ExitStatus, Stdio},
|
||||||
|
str,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) use anyhow::{anyhow, Error};
|
pub(crate) use anyhow::{anyhow, Error};
|
||||||
|
pub(crate) use askama::Template;
|
||||||
pub(crate) use cargo_toml::Manifest;
|
pub(crate) use cargo_toml::Manifest;
|
||||||
pub(crate) use chrono::{DateTime, NaiveDateTime, Utc};
|
pub(crate) use chrono::{DateTime, NaiveDateTime, Utc};
|
||||||
pub(crate) use fehler::{throw, throws};
|
pub(crate) use fehler::{throw, throws};
|
||||||
pub(crate) use git2::{Commit, Repository};
|
pub(crate) use git2::{Commit, Repository};
|
||||||
|
pub(crate) use regex::Regex;
|
||||||
pub(crate) use serde::{Deserialize, Serialize};
|
pub(crate) use serde::{Deserialize, Serialize};
|
||||||
pub(crate) use structopt::StructOpt;
|
pub(crate) use structopt::StructOpt;
|
||||||
pub(crate) use strum::VariantNames;
|
pub(crate) use strum::VariantNames;
|
||||||
|
@ -17,5 +23,8 @@ pub(crate) use strum_macros::{EnumVariantNames, IntoStaticStr};
|
||||||
pub(crate) use url::Url;
|
pub(crate) use url::Url;
|
||||||
|
|
||||||
pub(crate) use crate::{
|
pub(crate) use crate::{
|
||||||
changelog::Changelog, entry::Entry, kind::Kind, metadata::Metadata, opt::Opt, release::Release,
|
bin::Bin, changelog::Changelog, command_ext::CommandExt, config::Config, entry::Entry,
|
||||||
|
example::Example, exit_status_ext::ExitStatusExt, introduction::Introduction, kind::Kind,
|
||||||
|
metadata::Metadata, opt::Opt, project::Project, readme::Readme, release::Release,
|
||||||
|
subcommand::Subcommand, summary::Summary,
|
||||||
};
|
};
|
17
bin/gen/src/config.rs
Normal file
17
bin/gen/src/config.rs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
use crate::common::*;
|
||||||
|
|
||||||
|
const PATH: &str = "bin/gen/config.yaml";
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub(crate) struct Config {
|
||||||
|
pub(crate) changelog: BTreeMap<String, Metadata>,
|
||||||
|
pub(crate) examples: Vec<Example>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
#[throws]
|
||||||
|
pub(crate) fn load(root: &Path) -> Config {
|
||||||
|
let file = File::open(root.join(PATH))?;
|
||||||
|
serde_yaml::from_reader(file)?
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,13 +12,17 @@ pub(crate) struct Entry {
|
||||||
|
|
||||||
impl Entry {
|
impl Entry {
|
||||||
#[throws]
|
#[throws]
|
||||||
pub(crate) fn new(commit: &Commit, version: &str, head: bool) -> Self {
|
pub(crate) fn new(commit: &Commit, version: &str, head: bool, config: &Config) -> Self {
|
||||||
let time = DateTime::<Utc>::from_utc(
|
let time = DateTime::<Utc>::from_utc(
|
||||||
NaiveDateTime::from_timestamp(commit.time().seconds(), 0),
|
NaiveDateTime::from_timestamp(commit.time().seconds(), 0),
|
||||||
Utc,
|
Utc,
|
||||||
);
|
);
|
||||||
|
|
||||||
let metadata = Metadata::from_commit(commit)?;
|
let metadata = if let Some(metadata) = config.changelog.get(&commit.id().to_string()) {
|
||||||
|
metadata.clone()
|
||||||
|
} else {
|
||||||
|
Metadata::from_commit(commit)?
|
||||||
|
};
|
||||||
|
|
||||||
Entry {
|
Entry {
|
||||||
version: version.into(),
|
version: version.into(),
|
10
bin/gen/src/example.rs
Normal file
10
bin/gen/src/example.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
use crate::common::*;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Clone)]
|
||||||
|
pub(crate) struct Example {
|
||||||
|
pub(crate) command: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub(crate) unstable: bool,
|
||||||
|
pub(crate) text: String,
|
||||||
|
pub(crate) code: String,
|
||||||
|
}
|
14
bin/gen/src/exit_status_ext.rs
Normal file
14
bin/gen/src/exit_status_ext.rs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
use crate::common::*;
|
||||||
|
|
||||||
|
pub(crate) trait ExitStatusExt {
|
||||||
|
fn into_result(self) -> anyhow::Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExitStatusExt for ExitStatus {
|
||||||
|
#[throws]
|
||||||
|
fn into_result(self) {
|
||||||
|
if !self.success() {
|
||||||
|
throw!(anyhow!(self));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
bin/gen/src/introduction.rs
Normal file
15
bin/gen/src/introduction.rs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
use crate::common::*;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "introduction.md")]
|
||||||
|
pub(crate) struct Introduction {
|
||||||
|
pub(crate) examples: Vec<Example>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Introduction {
|
||||||
|
pub(crate) fn new(config: &Config) -> Self {
|
||||||
|
Self {
|
||||||
|
examples: config.examples.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
bin/gen/src/main.rs
Normal file
31
bin/gen/src/main.rs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
use crate::common::*;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod cmd;
|
||||||
|
|
||||||
|
mod bin;
|
||||||
|
mod changelog;
|
||||||
|
mod command_ext;
|
||||||
|
mod common;
|
||||||
|
mod config;
|
||||||
|
mod entry;
|
||||||
|
mod example;
|
||||||
|
mod exit_status_ext;
|
||||||
|
mod introduction;
|
||||||
|
mod kind;
|
||||||
|
mod metadata;
|
||||||
|
mod opt;
|
||||||
|
mod project;
|
||||||
|
mod readme;
|
||||||
|
mod release;
|
||||||
|
mod subcommand;
|
||||||
|
mod summary;
|
||||||
|
|
||||||
|
#[throws]
|
||||||
|
fn main() {
|
||||||
|
pretty_env_logger::init();
|
||||||
|
|
||||||
|
let project = Project::load()?;
|
||||||
|
|
||||||
|
Opt::from_args().run(&project)?;
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::common::*;
|
use crate::common::*;
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize, Clone)]
|
||||||
pub(crate) struct Metadata {
|
pub(crate) struct Metadata {
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub(crate) kind: Kind,
|
pub(crate) kind: Kind,
|
153
bin/gen/src/opt.rs
Normal file
153
bin/gen/src/opt.rs
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
use crate::common::*;
|
||||||
|
|
||||||
|
#[derive(StructOpt)]
|
||||||
|
pub(crate) enum Opt {
|
||||||
|
#[structopt(about("Update all generated docs"))]
|
||||||
|
All,
|
||||||
|
#[structopt(about("Generate the changelog"))]
|
||||||
|
Changelog,
|
||||||
|
#[structopt(about("Print a commit template to standard output"))]
|
||||||
|
CommitTemplate,
|
||||||
|
#[structopt(about("Print possible values for `type` field of commit metadata"))]
|
||||||
|
CommitTypes,
|
||||||
|
#[structopt(about("Generate completion scripts"))]
|
||||||
|
CompletionScripts,
|
||||||
|
#[structopt(about("Generate readme"))]
|
||||||
|
Readme,
|
||||||
|
#[structopt(about("Generate book"))]
|
||||||
|
Book,
|
||||||
|
#[structopt(about("Generate man pages"))]
|
||||||
|
Man,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[throws]
|
||||||
|
fn clean_dir(dir: impl AsRef<Path>) {
|
||||||
|
let dir = dir.as_ref();
|
||||||
|
|
||||||
|
eprintln!("Cleaning `{}`…", dir.display());
|
||||||
|
|
||||||
|
if dir.is_dir() {
|
||||||
|
fs::remove_dir_all(dir)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::create_dir_all(dir)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Opt {
|
||||||
|
#[throws]
|
||||||
|
pub(crate) fn run(self, project: &Project) {
|
||||||
|
match self {
|
||||||
|
Self::Changelog => Self::changelog(project)?,
|
||||||
|
Self::CommitTemplate => {
|
||||||
|
println!("{}", Metadata::default().to_string());
|
||||||
|
}
|
||||||
|
Self::CommitTypes => {
|
||||||
|
for kind in Kind::VARIANTS {
|
||||||
|
println!("{}", kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::CompletionScripts => Self::completion_scripts(project)?,
|
||||||
|
Self::Readme => Self::readme(project)?,
|
||||||
|
Self::Book => Self::book(project)?,
|
||||||
|
Self::Man => Self::man(project)?,
|
||||||
|
Self::All => {
|
||||||
|
Self::changelog(project)?;
|
||||||
|
Self::completion_scripts(project)?;
|
||||||
|
Self::readme(project)?;
|
||||||
|
Self::book(project)?;
|
||||||
|
Self::man(project)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[throws]
|
||||||
|
pub(crate) fn changelog(project: &Project) {
|
||||||
|
eprintln!("Generating changelog…");
|
||||||
|
let changelog = Changelog::new(&project)?;
|
||||||
|
|
||||||
|
let dst = project.root.join("CHANGELOG.md");
|
||||||
|
|
||||||
|
fs::write(dst, changelog.to_string())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[throws]
|
||||||
|
pub(crate) fn completion_scripts(project: &Project) {
|
||||||
|
eprintln!("Generating completion scripts…");
|
||||||
|
let completions = project.root.join("completions");
|
||||||
|
|
||||||
|
clean_dir(&completions)?;
|
||||||
|
|
||||||
|
cmd!(
|
||||||
|
"cargo",
|
||||||
|
"run",
|
||||||
|
"--package",
|
||||||
|
"imdl",
|
||||||
|
"completions",
|
||||||
|
"--dir",
|
||||||
|
completions
|
||||||
|
)
|
||||||
|
.status()?
|
||||||
|
.into_result()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[throws]
|
||||||
|
pub(crate) fn readme(project: &Project) {
|
||||||
|
eprintln!("Generating readme…");
|
||||||
|
let template = project.root.join("bin/gen/templates/README.md");
|
||||||
|
|
||||||
|
let readme = Readme::load(&template)?;
|
||||||
|
|
||||||
|
let mut text = readme.render()?;
|
||||||
|
text.push('\n');
|
||||||
|
|
||||||
|
fs::write(project.root.join("README.md"), text)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[throws]
|
||||||
|
pub(crate) fn book(project: &Project) {
|
||||||
|
eprintln!("Generating book…");
|
||||||
|
let commands = project.root.join("book/src/commands/");
|
||||||
|
|
||||||
|
clean_dir(&commands)?;
|
||||||
|
|
||||||
|
for subcommand in &project.bin.subcommands {
|
||||||
|
let page = subcommand.page()?;
|
||||||
|
|
||||||
|
let dst = commands.join(format!("{}.md", subcommand.slug()));
|
||||||
|
|
||||||
|
fs::write(dst, page)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let summary = Summary::new(&project.bin);
|
||||||
|
|
||||||
|
let mut text = summary.render()?;
|
||||||
|
text.push('\n');
|
||||||
|
|
||||||
|
fs::write(project.root.join("book/src/SUMMARY.md"), text)?;
|
||||||
|
|
||||||
|
let introduction = Introduction::new(&project.config);
|
||||||
|
|
||||||
|
let mut text = introduction.render()?;
|
||||||
|
text.push('\n');
|
||||||
|
|
||||||
|
fs::write(project.root.join("book/src/introduction.md"), text)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[throws]
|
||||||
|
pub(crate) fn man(project: &Project) {
|
||||||
|
eprintln!("Generating man pages…");
|
||||||
|
let mans = project.root.join("man");
|
||||||
|
|
||||||
|
clean_dir(&mans)?;
|
||||||
|
|
||||||
|
for subcommand in &project.bin.subcommands {
|
||||||
|
let man = subcommand.man()?;
|
||||||
|
|
||||||
|
let dst = mans.join(format!("{}.1", subcommand.slug()));
|
||||||
|
|
||||||
|
eprintln!("Writing man page to `{}`", dst.display());
|
||||||
|
|
||||||
|
fs::write(dst, man)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
57
bin/gen/src/project.rs
Normal file
57
bin/gen/src/project.rs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
use crate::common::*;
|
||||||
|
|
||||||
|
pub(crate) struct Project {
|
||||||
|
pub(crate) repo: Repository,
|
||||||
|
pub(crate) root: PathBuf,
|
||||||
|
pub(crate) config: Config,
|
||||||
|
pub(crate) bin: Bin,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Project {
|
||||||
|
#[throws]
|
||||||
|
pub(crate) fn load() -> Self {
|
||||||
|
let repo = Repository::discover(env::current_dir()?)?;
|
||||||
|
|
||||||
|
let root = repo
|
||||||
|
.workdir()
|
||||||
|
.ok_or_else(|| anyhow!("Repository at `{}` had no workdir", repo.path().display()))?
|
||||||
|
.to_owned();
|
||||||
|
|
||||||
|
let config = Config::load(&root)?;
|
||||||
|
|
||||||
|
let bin = Bin::new(&root.join("target/debug/imdl"))?;
|
||||||
|
|
||||||
|
let example_commands = config
|
||||||
|
.examples
|
||||||
|
.iter()
|
||||||
|
.map(|example| example.command.clone())
|
||||||
|
.collect::<BTreeSet<String>>();
|
||||||
|
|
||||||
|
let bin_commands = bin
|
||||||
|
.subcommands
|
||||||
|
.iter()
|
||||||
|
.map(|subcommand| subcommand.command_line())
|
||||||
|
.collect::<BTreeSet<String>>();
|
||||||
|
|
||||||
|
if example_commands != bin_commands {
|
||||||
|
println!("Example commands:");
|
||||||
|
for command in example_commands {
|
||||||
|
println!("{}", command);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("…don't match bin commands:");
|
||||||
|
for command in bin_commands {
|
||||||
|
println!("{}", command);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw!(anyhow!(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
Project {
|
||||||
|
repo,
|
||||||
|
root,
|
||||||
|
config,
|
||||||
|
bin,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
bin/gen/src/readme.rs
Normal file
37
bin/gen/src/readme.rs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
use crate::common::*;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "README.md")]
|
||||||
|
pub(crate) struct Readme {
|
||||||
|
pub(crate) table_of_contents: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
const HEADING_PATTERN: &str = "(?m)^(?P<MARKER>#+) (?P<TEXT>.*)$";
|
||||||
|
|
||||||
|
impl Readme {
|
||||||
|
#[throws]
|
||||||
|
pub(crate) fn load(template: &Path) -> Readme {
|
||||||
|
let text = fs::read_to_string(template)?;
|
||||||
|
|
||||||
|
let header_re = Regex::new(HEADING_PATTERN)?;
|
||||||
|
|
||||||
|
let mut lines = Vec::new();
|
||||||
|
|
||||||
|
for captures in header_re.captures_iter(&text).skip(2) {
|
||||||
|
let marker = captures.name("MARKER").unwrap().as_str();
|
||||||
|
let text = captures.name("TEXT").unwrap().as_str();
|
||||||
|
let level = marker.len();
|
||||||
|
let indentation = " ".repeat((level - 2) * 2);
|
||||||
|
let slug = text
|
||||||
|
.to_lowercase()
|
||||||
|
.replace(' ', "-")
|
||||||
|
.replace('.', "")
|
||||||
|
.replace('&', "");
|
||||||
|
lines.push(format!("{}- [{}](#{})", indentation, text, slug));
|
||||||
|
}
|
||||||
|
|
||||||
|
Readme {
|
||||||
|
table_of_contents: lines.join("\n"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
162
bin/gen/src/subcommand.rs
Normal file
162
bin/gen/src/subcommand.rs
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
use crate::common::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq)]
|
||||||
|
pub(crate) struct Subcommand {
|
||||||
|
pub(crate) bin: PathBuf,
|
||||||
|
pub(crate) command: Vec<String>,
|
||||||
|
pub(crate) subcommands: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Subcommand {
|
||||||
|
#[throws]
|
||||||
|
pub(crate) fn new(bin: &Path, command: Vec<String>) -> Self {
|
||||||
|
let wide_help = Command::new(bin)
|
||||||
|
.args(command.as_slice())
|
||||||
|
.env("IMDL_TERM_WIDTH", "200")
|
||||||
|
.arg("--help")
|
||||||
|
.out()?;
|
||||||
|
|
||||||
|
const MARKER: &str = "\nSUBCOMMANDS:\n";
|
||||||
|
|
||||||
|
let mut subcommands = Vec::new();
|
||||||
|
|
||||||
|
if let Some(marker) = wide_help.find(MARKER) {
|
||||||
|
let block = &wide_help[marker + MARKER.len()..];
|
||||||
|
|
||||||
|
for line in block.lines() {
|
||||||
|
let name = line.trim().split_whitespace().next().unwrap();
|
||||||
|
subcommands.push(name.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
bin: bin.into(),
|
||||||
|
command,
|
||||||
|
subcommands,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[throws]
|
||||||
|
fn help(&self) -> String {
|
||||||
|
eprintln!("Getting help for `{}`", self.command_line());
|
||||||
|
|
||||||
|
Command::new(&self.bin)
|
||||||
|
.args(self.command.as_slice())
|
||||||
|
.env("IMDL_TERM_WIDTH", "80")
|
||||||
|
.arg("--help")
|
||||||
|
.out()?
|
||||||
|
}
|
||||||
|
|
||||||
|
#[throws]
|
||||||
|
pub(crate) fn man(&self) -> String {
|
||||||
|
let command_line = self.command_line();
|
||||||
|
|
||||||
|
eprintln!("Generating man page for `{}`", command_line);
|
||||||
|
|
||||||
|
let name = command_line.replace(" ", "\\ ");
|
||||||
|
|
||||||
|
let help = self.help()?;
|
||||||
|
|
||||||
|
let description = if self.command.is_empty() {
|
||||||
|
"A 40' shipping container for the Internet".to_string()
|
||||||
|
} else {
|
||||||
|
help.lines().nth(1).unwrap().into()
|
||||||
|
};
|
||||||
|
|
||||||
|
let include = format!(
|
||||||
|
"\
|
||||||
|
[NAME]
|
||||||
|
\\fB{}\\fR
|
||||||
|
- {}
|
||||||
|
",
|
||||||
|
name, description
|
||||||
|
);
|
||||||
|
|
||||||
|
let tmp = tempfile::tempdir()?;
|
||||||
|
|
||||||
|
let include_path = tmp.path().join("include");
|
||||||
|
|
||||||
|
fs::write(&include_path, include)?;
|
||||||
|
|
||||||
|
let version = cmd!(&self.bin, "--version")
|
||||||
|
.out()?
|
||||||
|
.split_whitespace()
|
||||||
|
.nth(1)
|
||||||
|
.unwrap()
|
||||||
|
.to_owned();
|
||||||
|
|
||||||
|
eprintln!("Running help2man for `{}`", command_line);
|
||||||
|
|
||||||
|
let mut command = self.bin.as_os_str().to_owned();
|
||||||
|
for arg in &self.command {
|
||||||
|
command.push(" ");
|
||||||
|
command.push(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = cmd!(
|
||||||
|
"help2man",
|
||||||
|
"--include",
|
||||||
|
&include_path,
|
||||||
|
"--manual",
|
||||||
|
"Intermodal Manual",
|
||||||
|
"--no-info",
|
||||||
|
"--source",
|
||||||
|
&format!("Intermodal {}", version),
|
||||||
|
command
|
||||||
|
)
|
||||||
|
.out()?;
|
||||||
|
|
||||||
|
let man = output
|
||||||
|
.replace("📦 ", "\n")
|
||||||
|
.replace("\n.SS ", "\n.SH ")
|
||||||
|
.replace("\"USAGE:\"", "\"SYNOPSIS:\"");
|
||||||
|
|
||||||
|
let re = Regex::new(r"(?ms).SH DESCRIPTION.*?.SH").unwrap();
|
||||||
|
|
||||||
|
let man = re.replace(&man, ".SH").into_owned();
|
||||||
|
|
||||||
|
man
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn slug(&self) -> String {
|
||||||
|
let mut slug = self
|
||||||
|
.bin
|
||||||
|
.display()
|
||||||
|
.to_string()
|
||||||
|
.split('/')
|
||||||
|
.last()
|
||||||
|
.unwrap()
|
||||||
|
.to_owned();
|
||||||
|
|
||||||
|
for name in &self.command {
|
||||||
|
slug.push('-');
|
||||||
|
slug.push_str(&name);
|
||||||
|
}
|
||||||
|
|
||||||
|
slug
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn command_line(&self) -> String {
|
||||||
|
let mut line = self
|
||||||
|
.bin
|
||||||
|
.display()
|
||||||
|
.to_string()
|
||||||
|
.split('/')
|
||||||
|
.last()
|
||||||
|
.unwrap()
|
||||||
|
.to_owned();
|
||||||
|
|
||||||
|
for name in &self.command {
|
||||||
|
line.push(' ');
|
||||||
|
line.push_str(&name);
|
||||||
|
}
|
||||||
|
|
||||||
|
line
|
||||||
|
}
|
||||||
|
|
||||||
|
#[throws]
|
||||||
|
pub(crate) fn page(&self) -> String {
|
||||||
|
let help = self.help()?;
|
||||||
|
format!("# `{}`\n```\n{}\n```", self.command_line(), help)
|
||||||
|
}
|
||||||
|
}
|
29
bin/gen/src/summary.rs
Normal file
29
bin/gen/src/summary.rs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
use crate::common::*;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "SUMMARY.md")]
|
||||||
|
pub(crate) struct Summary {
|
||||||
|
pub(crate) commands: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Summary {
|
||||||
|
pub(crate) fn new(bin: &Bin) -> Summary {
|
||||||
|
let mut lines = Vec::new();
|
||||||
|
|
||||||
|
lines.push("- [Commands](./commands.md)".to_string());
|
||||||
|
|
||||||
|
for subcommand in &bin.subcommands {
|
||||||
|
let slug = subcommand.slug();
|
||||||
|
|
||||||
|
lines.push(format!(
|
||||||
|
" - [`{}`](./commands/{}.md)",
|
||||||
|
subcommand.command_line(),
|
||||||
|
slug
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
Summary {
|
||||||
|
commands: lines.join("\n"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
140
bin/gen/templates/README.md
Normal file
140
bin/gen/templates/README.md
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
# Intermodal: A 40' shipping container for the Internet
|
||||||
|
|
||||||
|
[![Crate](https://img.shields.io/crates/v/imdl.svg?logo=rust)](https://crates.io/crates/imdl)
|
||||||
|
[![Build](https://github.com/casey/intermodal/workflows/Build/badge.svg)](https://github.com/casey/intermodal/actions)
|
||||||
|
[![Book](https://img.shields.io/static/v1?logo=read-the-docs&label=book&message=imdl.io&color=informational)](https://imdl.io/book/)
|
||||||
|
[![Chat](https://img.shields.io/discord/679283456261226516.svg?logo=discord&color=7289da)](https://discord.gg/HaaT5Qz)
|
||||||
|
|
||||||
|
Intermodal is a user-friendly and featureful command-line BitTorrent metainfo utility. The binary is called `imdl` and runs on Linux, Windows, and macOS.
|
||||||
|
|
||||||
|
At the moment, creation, viewing, and verification of `.torrent` files is supported.
|
||||||
|
|
||||||
|
For more about the project and its goals, check out [this post](https://rodarmor.com/blog/intermodal).
|
||||||
|
|
||||||
|
![demonstration animation](https://raw.githubusercontent.com/casey/intermodal/master/www/demo.gif)
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
{{table_of_contents}}
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Supported Operating Systems
|
||||||
|
|
||||||
|
`imdl` supports Linux, MacOS, and Windows, and should work on other unix OSes.
|
||||||
|
If it does not, please open an issue!
|
||||||
|
|
||||||
|
### Packages
|
||||||
|
|
||||||
|
| Operating System | Package Manager | Package | Command |
|
||||||
|
|:--------------------------------------------------------------------:|:-----------------------------------:|:-------------------------------------------------------------------------:|---------------------:|
|
||||||
|
| [Various](https://forge.rust-lang.org/release/platform-support.html) | [Cargo](https://www.rust-lang.org) | [imdl](https://crates.io/crates/imdl) | `cargo install imdl` |
|
||||||
|
| [Arch Linux](https://www.archlinux.org) | [Yay](https://github.com/Jguer/yay) | [intermodal](https://aur.archlinux.org/packages/intermodal)<sup>AUR</sup> | `yay -S intermodal` |
|
||||||
|
|
||||||
|
### Pre-built binaries
|
||||||
|
|
||||||
|
Pre-built binaries for Linux, macOS, and Windows can be found on
|
||||||
|
[the releases page](https://github.com/casey/intermodal/releases).
|
||||||
|
|
||||||
|
You can use the following command to download the latest binary for Linux,
|
||||||
|
MacOS, or Windows, just replace `DEST` with the directory where you'd like to
|
||||||
|
install the `imdl` binary:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl --proto '=https' --tlsv1.2 -sSf https://imdl.io/install.sh | bash -s -- --to DEST
|
||||||
|
```
|
||||||
|
|
||||||
|
A good place to install personal binaries is `~/bin`, which `install.sh` uses
|
||||||
|
when `--to` is not supplied. To create the `~/bin` directory and install `imdl`
|
||||||
|
there, do:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl --proto '=https' --tlsv1.2 -sSf https://imdl.io/install.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
Additionally, you'll have to add `~/bin` to the `PATH` environment variable,
|
||||||
|
which the system uses to find executables. How to do this depends on the shell.
|
||||||
|
|
||||||
|
For `sh`, `bash`, and `zsh`, it should be done in `~/.profile`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
echo 'export PATH=$HOME/bin:$PATH' >> ~/.profile
|
||||||
|
```
|
||||||
|
|
||||||
|
For `fish`, it should be done in `~/.config/fish/config.fish`:
|
||||||
|
|
||||||
|
```fish
|
||||||
|
echo 'set -gx PATH ~/bin $PATH' >> ~/.config/fish/config.fish
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cargo
|
||||||
|
|
||||||
|
`imdl` is written in [Rust](https://www.rust-lang.org/) and can be built from
|
||||||
|
source and installed with `cargo install imdl`. To get Rust, use the
|
||||||
|
[rustup installer](https://rustup.rs/).
|
||||||
|
|
||||||
|
## Shell Completion Scripts
|
||||||
|
|
||||||
|
Shell completion scripts for Bash, Zsh, Fish, PowerShell, and Elvish are
|
||||||
|
available in the [completions directory](./completions), included in all
|
||||||
|
[binary releases](https://github.com/casey/imdl/releases).
|
||||||
|
|
||||||
|
For Bash, move `imdl.bash` to `$XDG_CONFIG_HOME/bash_completion` or
|
||||||
|
`/etc/bash_completion.d/`.
|
||||||
|
|
||||||
|
For Fish, move `imdl.fish` to `$HOME/.config/fish/completions/`.
|
||||||
|
|
||||||
|
For the Z shell, move `_imdl` to one of your `$fpath` directories.
|
||||||
|
|
||||||
|
For PowerShell, add `. _imdl.ps1` to your PowerShell
|
||||||
|
[profile](https://technet.microsoft.com/en-us/library/bb613488(v=vs.85).aspx)
|
||||||
|
(note the leading period). If the `_imdl.ps1` file is not on your `PATH`, do
|
||||||
|
`. /path/to/_imdl.ps1` instead.
|
||||||
|
|
||||||
|
The `imdl` binary can also generate the same completion scripts at runtime,
|
||||||
|
using the `completions` command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ imdl completions --shell bash > imdl.bash
|
||||||
|
```
|
||||||
|
|
||||||
|
The `--dir` argument can be used to write a completion script into a directory
|
||||||
|
with a filename that's appropriate for the shell. For example, the following
|
||||||
|
command will write the Z shell completion script to `$fpath[0]/_imdl`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ imdl completions --shell zsh --dir $fpath[0]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Semantic Versioning
|
||||||
|
|
||||||
|
Intermodal follows [semantic versioning](https://semver.org/).
|
||||||
|
|
||||||
|
In particular:
|
||||||
|
|
||||||
|
- v0.0.X: Breaking changes may be introduced at any time.
|
||||||
|
- v0.X.Y: Breaking changes may only be introduced with a minor version number
|
||||||
|
bump.
|
||||||
|
- vX.Y.Z: Breaking changes may only be introduced with a major version number
|
||||||
|
bump
|
||||||
|
|
||||||
|
## Unstable Features
|
||||||
|
|
||||||
|
To avoid premature stabilization and excessive version churn, unstable features
|
||||||
|
are unavailable unless the `--unstable` / `-u` flag is passed, for example
|
||||||
|
`imdl --unstable torrent create .`. Unstable features may be changed or removed
|
||||||
|
at any time.
|
||||||
|
|
||||||
|
## Source Signatures
|
||||||
|
|
||||||
|
All commits to the intermodal master branch signed with Casey Rodarmor's PGP
|
||||||
|
key with fingerprint `3259DAEDB29636B0E2025A70556186B153EC6FE0`, which can be
|
||||||
|
found
|
||||||
|
[on keybase](https://keybase.io/rodarmor/pgp_keys.asc?fingerprint=3259daedb29636b0e2025a70556186b153ec6fe0) and on
|
||||||
|
[his homepage](https://rodarmor.com/static/rodarmor.asc).
|
||||||
|
|
||||||
|
## Acknowledgments
|
||||||
|
|
||||||
|
The formatting of `imdl torrent show` is entirely copied from
|
||||||
|
[torf](https://github.com/rndusr/torf-cli), an excellent command-line torrent
|
||||||
|
creator, editor, and viewer.
|
16
bin/gen/templates/SUMMARY.md
Normal file
16
bin/gen/templates/SUMMARY.md
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
Summary
|
||||||
|
=======
|
||||||
|
|
||||||
|
[Intermodal](./introduction.md)
|
||||||
|
|
||||||
|
{{commands}}
|
||||||
|
|
||||||
|
- [Bittorrent](./bittorrent.md)
|
||||||
|
- [Distributing Large Data Sets](./bittorrent/distributing-large-data-sets.md)
|
||||||
|
- [BEP Support](./bittorrent/bep-support.md)
|
||||||
|
- [Alternatives & Prior Art](./bittorrent/prior-art.md)
|
||||||
|
- [UDP Tracker Protocol](./bittorrent/udp-tracker-protocol.md)
|
||||||
|
- [References](./bittorrent/references.md)
|
||||||
|
|
||||||
|
- [Metadata](./metadata.md)
|
||||||
|
- [Prior Art](./metadata/prior-art.md)
|
29
bin/gen/templates/introduction.md
Normal file
29
bin/gen/templates/introduction.md
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
# Intermodal: A 40' shipping container for the Internet
|
||||||
|
|
||||||
|
Intermodal is a user-friendly and featureful command-line BitTorrent metainfo utility for Linux, Windows, and macOS.
|
||||||
|
|
||||||
|
Project development is hosted on [GitHub](https://github.com/casey/intermodal).
|
||||||
|
{%- for example in examples -%}
|
||||||
|
{%- if !example.unstable %}
|
||||||
|
|
||||||
|
{{example.text}}
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ {{example.code}}
|
||||||
|
```
|
||||||
|
{%- endif %}
|
||||||
|
{%- endfor %}
|
||||||
|
|
||||||
|
Functionality that is not yet finalized, but still available for preview, can be accessed with the `--unstable` flag:
|
||||||
|
|
||||||
|
{%- for example in examples -%}
|
||||||
|
{%- if example.unstable %}
|
||||||
|
|
||||||
|
{{example.text}}
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ {{example.code}}
|
||||||
|
```
|
||||||
|
{%- endif %}
|
||||||
|
{%- endfor %}
|
||||||
|
Happy sharing!
|
|
@ -1,10 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -euxo pipefail
|
|
||||||
|
|
||||||
cargo build
|
|
||||||
|
|
||||||
for script in completions/*; do
|
|
||||||
shell=${script##*.}
|
|
||||||
./target/debug/imdl completions --shell $shell > $script
|
|
||||||
done
|
|
5
bin/lint
5
bin/lint
|
@ -2,4 +2,7 @@
|
||||||
|
|
||||||
set -euxo pipefail
|
set -euxo pipefail
|
||||||
|
|
||||||
! grep --color -REni 'FIXME|TODO|XXX|todo!|#\[ignore]' src book/src
|
! rg \
|
||||||
|
--glob !bin/lint \
|
||||||
|
--ignore-case \
|
||||||
|
'FIXME|TODO|XXX|todo!|#\[ignore\]'
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "man"
|
|
||||||
version = "0.0.0"
|
|
||||||
authors = ["Casey Rodarmor <casey@rodarmor.com>"]
|
|
||||||
edition = "2018"
|
|
||||||
publish = false
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
anyhow = "1.0.28"
|
|
||||||
fehler = "1.0.0"
|
|
||||||
tempfile = "3.1.0"
|
|
||||||
regex = "1.3.6"
|
|
|
@ -1,12 +0,0 @@
|
||||||
pub(crate) use std::{
|
|
||||||
fs,
|
|
||||||
path::Path,
|
|
||||||
process::{Command, Stdio},
|
|
||||||
str,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub(crate) use anyhow::{anyhow, Error};
|
|
||||||
pub(crate) use fehler::{throw, throws};
|
|
||||||
pub(crate) use regex::Regex;
|
|
||||||
|
|
||||||
pub(crate) use crate::{bin::Bin, subcommand::Subcommand};
|
|
|
@ -1,57 +0,0 @@
|
||||||
use crate::common::*;
|
|
||||||
|
|
||||||
mod bin;
|
|
||||||
mod common;
|
|
||||||
mod subcommand;
|
|
||||||
|
|
||||||
#[throws]
|
|
||||||
fn clean(dir: impl AsRef<Path>) {
|
|
||||||
let dir = dir.as_ref();
|
|
||||||
fs::remove_dir_all(dir)?;
|
|
||||||
fs::create_dir_all(dir)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[throws]
|
|
||||||
fn write(dst: impl AsRef<Path>, contents: &str) {
|
|
||||||
let dst = dst.as_ref();
|
|
||||||
println!("Writing `{}`…", dst.display());
|
|
||||||
fs::write(dst, contents)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[throws]
|
|
||||||
fn main() {
|
|
||||||
let bin = Bin::new("target/debug/imdl")?;
|
|
||||||
|
|
||||||
clean("man")?;
|
|
||||||
clean("book/src/commands")?;
|
|
||||||
|
|
||||||
let mut pages = "- [Commands](./commands.md)\n".to_string();
|
|
||||||
|
|
||||||
for subcommand in bin.subcommands {
|
|
||||||
let slug = subcommand.slug();
|
|
||||||
|
|
||||||
let dst = format!("man/{}.1", slug);
|
|
||||||
write(dst, &subcommand.man)?;
|
|
||||||
|
|
||||||
let dst = format!("book/src/commands/{}.md", slug);
|
|
||||||
write(dst, &subcommand.page())?;
|
|
||||||
|
|
||||||
pages.push_str(&format!(
|
|
||||||
" - [`{}`](./commands/{}.md)\n",
|
|
||||||
subcommand.command_line(),
|
|
||||||
slug
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pages.push('\n');
|
|
||||||
|
|
||||||
let path = "book/src/SUMMARY.md";
|
|
||||||
|
|
||||||
let original = fs::read_to_string(path)?;
|
|
||||||
|
|
||||||
let re = Regex::new(r"(?ms)^- \[Commands\]\(./commands.md\).*?\n\n").unwrap();
|
|
||||||
|
|
||||||
let text = re.replace(&original, pages.as_str()).into_owned();
|
|
||||||
|
|
||||||
fs::write(path, text)?;
|
|
||||||
}
|
|
|
@ -1,156 +0,0 @@
|
||||||
use crate::common::*;
|
|
||||||
|
|
||||||
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq)]
|
|
||||||
pub(crate) struct Subcommand {
|
|
||||||
pub(crate) bin: String,
|
|
||||||
pub(crate) command: Vec<String>,
|
|
||||||
pub(crate) help: String,
|
|
||||||
pub(crate) man: String,
|
|
||||||
pub(crate) subcommands: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
trait CommandExt {
|
|
||||||
#[throws]
|
|
||||||
fn out(&mut self) -> String;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CommandExt for Command {
|
|
||||||
#[throws]
|
|
||||||
fn out(&mut self) -> String {
|
|
||||||
let output = self.stdout(Stdio::piped()).output()?;
|
|
||||||
|
|
||||||
if !output.status.success() {
|
|
||||||
throw!(anyhow!("Command `{:?}` failed: {}", self, output.status));
|
|
||||||
}
|
|
||||||
|
|
||||||
let text = String::from_utf8(output.stdout)?;
|
|
||||||
|
|
||||||
text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Subcommand {
|
|
||||||
#[throws]
|
|
||||||
pub(crate) fn new(bin: &str, command: Vec<String>) -> Self {
|
|
||||||
let wide_help = Command::new(bin)
|
|
||||||
.args(command.as_slice())
|
|
||||||
.env("IMDL_TERM_WIDTH", "200")
|
|
||||||
.arg("--help")
|
|
||||||
.out()?;
|
|
||||||
|
|
||||||
const MARKER: &str = "\nSUBCOMMANDS:\n";
|
|
||||||
|
|
||||||
let mut subcommands = Vec::new();
|
|
||||||
|
|
||||||
if let Some(marker) = wide_help.find(MARKER) {
|
|
||||||
let block = &wide_help[marker + MARKER.len()..];
|
|
||||||
|
|
||||||
for line in block.lines() {
|
|
||||||
let name = line.trim().split_whitespace().next().unwrap();
|
|
||||||
subcommands.push(name.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let command_line = format!("{} {}", bin, command.join(" "));
|
|
||||||
|
|
||||||
let name = command_line
|
|
||||||
.split('/')
|
|
||||||
.last()
|
|
||||||
.unwrap()
|
|
||||||
.trim()
|
|
||||||
.replace(" ", "\\ ");
|
|
||||||
|
|
||||||
let description = if command.is_empty() {
|
|
||||||
"A 40' shipping container for the Internet".to_string()
|
|
||||||
} else {
|
|
||||||
wide_help.lines().nth(1).unwrap().into()
|
|
||||||
};
|
|
||||||
|
|
||||||
let include = format!(
|
|
||||||
"\
|
|
||||||
[NAME]
|
|
||||||
\\fB{}\\fR
|
|
||||||
- {}
|
|
||||||
",
|
|
||||||
name, description
|
|
||||||
);
|
|
||||||
|
|
||||||
let tmp = tempfile::tempdir()?;
|
|
||||||
|
|
||||||
fs::write(tmp.path().join("include"), include)?;
|
|
||||||
|
|
||||||
let include = tmp.path().join("include").to_string_lossy().into_owned();
|
|
||||||
|
|
||||||
let version = Command::new(bin)
|
|
||||||
.arg("--version")
|
|
||||||
.out()?
|
|
||||||
.split_whitespace()
|
|
||||||
.nth(1)
|
|
||||||
.unwrap()
|
|
||||||
.to_owned();
|
|
||||||
|
|
||||||
let output = Command::new("help2man")
|
|
||||||
.args(&[
|
|
||||||
"--include",
|
|
||||||
&include,
|
|
||||||
"--manual",
|
|
||||||
"Intermodal Manual",
|
|
||||||
"--no-info",
|
|
||||||
"--source",
|
|
||||||
&format!("Intermodal {}", version),
|
|
||||||
])
|
|
||||||
.arg(&command_line)
|
|
||||||
.stdout(Stdio::piped())
|
|
||||||
.output()?;
|
|
||||||
|
|
||||||
if !output.status.success() {
|
|
||||||
throw!(anyhow!(
|
|
||||||
"Failed to generate man page for `{}` failed: {}",
|
|
||||||
command_line,
|
|
||||||
output.status
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let man = str::from_utf8(&output.stdout)?
|
|
||||||
.replace("📦 ", "\n")
|
|
||||||
.replace("\n.SS ", "\n.SH ")
|
|
||||||
.replace("\"USAGE:\"", "\"SYNOPSIS:\"");
|
|
||||||
|
|
||||||
let re = Regex::new(r"(?ms).SH DESCRIPTION.*?.SH").unwrap();
|
|
||||||
|
|
||||||
let man = re.replace(&man, ".SH").into_owned();
|
|
||||||
|
|
||||||
let narrow_help = Command::new(bin)
|
|
||||||
.args(command.as_slice())
|
|
||||||
.env("IMDL_TERM_WIDTH", "80")
|
|
||||||
.arg("--help")
|
|
||||||
.out()?;
|
|
||||||
|
|
||||||
Self {
|
|
||||||
bin: bin.into(),
|
|
||||||
help: narrow_help,
|
|
||||||
command,
|
|
||||||
man,
|
|
||||||
subcommands,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn slug(&self) -> String {
|
|
||||||
let mut slug = self.bin.split('/').last().unwrap().to_owned();
|
|
||||||
|
|
||||||
for name in &self.command {
|
|
||||||
slug.push('-');
|
|
||||||
slug.push_str(&name);
|
|
||||||
}
|
|
||||||
|
|
||||||
slug
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn command_line(&self) -> String {
|
|
||||||
self.slug().replace('-', " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn page(&self) -> String {
|
|
||||||
format!("# `{}`\n```\n{}\n```", self.command_line(), self.help)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "update-readme"
|
|
||||||
version = "0.0.0"
|
|
||||||
authors = ["Casey Rodarmor <casey@rodarmor.com>"]
|
|
||||||
edition = "2018"
|
|
||||||
publish = false
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
glob = "0.3.0"
|
|
||||||
regex = "1.3.3"
|
|
||||||
structopt = "0.3"
|
|
|
@ -1,7 +0,0 @@
|
||||||
use crate::common::*;
|
|
||||||
|
|
||||||
pub(crate) struct Bep {
|
|
||||||
pub(crate) number: usize,
|
|
||||||
pub(crate) title: String,
|
|
||||||
pub(crate) status: Status,
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
// stdlib
|
|
||||||
pub(crate) use std::{
|
|
||||||
error::Error,
|
|
||||||
fmt::{self, Display, Formatter},
|
|
||||||
fs,
|
|
||||||
str::FromStr,
|
|
||||||
};
|
|
||||||
|
|
||||||
// crates.io
|
|
||||||
pub(crate) use glob::glob;
|
|
||||||
pub(crate) use regex::Regex;
|
|
||||||
pub(crate) use structopt::StructOpt;
|
|
||||||
|
|
||||||
// local
|
|
||||||
pub(crate) use crate::{bep::Bep, opt::Opt, status::Status};
|
|
|
@ -1,10 +0,0 @@
|
||||||
mod bep;
|
|
||||||
mod common;
|
|
||||||
mod opt;
|
|
||||||
mod status;
|
|
||||||
|
|
||||||
use crate::common::*;
|
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
|
||||||
Opt::from_args().run()
|
|
||||||
}
|
|
|
@ -1,223 +0,0 @@
|
||||||
use crate::common::*;
|
|
||||||
|
|
||||||
const README: &str = "README.md";
|
|
||||||
|
|
||||||
const HEADING_PATTERN: &str = "(?m)^(?P<MARKER>#+) (?P<TEXT>.*)$";
|
|
||||||
|
|
||||||
const TOC_PATTERN: &str = "(?ms)## Manual.*## General";
|
|
||||||
|
|
||||||
#[derive(StructOpt)]
|
|
||||||
pub(crate) enum Opt {
|
|
||||||
SupportedBeps,
|
|
||||||
Toc,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Opt {
|
|
||||||
pub(crate) fn run(self) -> Result<(), Box<dyn Error>> {
|
|
||||||
match self {
|
|
||||||
Self::Toc => Self::update_toc(),
|
|
||||||
Self::SupportedBeps => Self::update_supported_beps(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_toc() -> Result<(), Box<dyn Error>> {
|
|
||||||
let readme = fs::read_to_string(README)?;
|
|
||||||
|
|
||||||
let header_re = Regex::new(HEADING_PATTERN)?;
|
|
||||||
|
|
||||||
let mut toc = Vec::new();
|
|
||||||
for captures in header_re.captures_iter(&readme).skip(2) {
|
|
||||||
let marker = captures.name("MARKER").unwrap().as_str();
|
|
||||||
let text = captures.name("TEXT").unwrap().as_str();
|
|
||||||
let level = marker.len();
|
|
||||||
let indentation = " ".repeat((level - 2) * 2);
|
|
||||||
let slug = text
|
|
||||||
.to_lowercase()
|
|
||||||
.replace(' ', "-")
|
|
||||||
.replace('.', "")
|
|
||||||
.replace('&', "");
|
|
||||||
toc.push(format!("{}- [{}](#{})", indentation, text, slug));
|
|
||||||
}
|
|
||||||
|
|
||||||
let toc = toc.join("\n");
|
|
||||||
|
|
||||||
let toc_re = Regex::new(TOC_PATTERN)?;
|
|
||||||
|
|
||||||
let readme = toc_re.replace(
|
|
||||||
&readme,
|
|
||||||
format!("## Manual\n\n{}\n\n## General", toc).as_str(),
|
|
||||||
);
|
|
||||||
|
|
||||||
fs::write(README, readme.as_bytes())?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_supported_beps() -> Result<(), Box<dyn Error>> {
|
|
||||||
let title_re = Regex::new("(?m)^:Title: (?P<title>.*)$")?;
|
|
||||||
|
|
||||||
let mut beps = Vec::new();
|
|
||||||
|
|
||||||
for result in glob("tmp/bittorrent.org/beps/bep_*.rst")? {
|
|
||||||
let path = result?;
|
|
||||||
|
|
||||||
let number = path
|
|
||||||
.file_stem()
|
|
||||||
.unwrap()
|
|
||||||
.to_string_lossy()
|
|
||||||
.split('_')
|
|
||||||
.nth(1)
|
|
||||||
.unwrap()
|
|
||||||
.parse::<usize>()?;
|
|
||||||
|
|
||||||
if number == 1000 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let rst = fs::read_to_string(path)?;
|
|
||||||
|
|
||||||
let title = title_re
|
|
||||||
.captures(&rst)
|
|
||||||
.unwrap()
|
|
||||||
.name("title")
|
|
||||||
.unwrap()
|
|
||||||
.as_str()
|
|
||||||
.trim()
|
|
||||||
.to_owned();
|
|
||||||
|
|
||||||
beps.push(Bep {
|
|
||||||
status: Status::Unknown,
|
|
||||||
number,
|
|
||||||
title,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
beps.sort_by_key(|bep| bep.number);
|
|
||||||
|
|
||||||
let table_re = Regex::new(
|
|
||||||
r"(?mx)
|
|
||||||
^[|]\ BEP.*
|
|
||||||
(
|
|
||||||
\n
|
|
||||||
[|]
|
|
||||||
.*
|
|
||||||
)*
|
|
||||||
",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let readme = fs::read_to_string(README)?;
|
|
||||||
|
|
||||||
let parts = table_re.split(&readme).collect::<Vec<&str>>();
|
|
||||||
|
|
||||||
assert_eq!(parts.len(), 2);
|
|
||||||
|
|
||||||
let before = parts[0];
|
|
||||||
let after = parts[1];
|
|
||||||
let original = table_re
|
|
||||||
.captures(&readme)
|
|
||||||
.unwrap()
|
|
||||||
.get(0)
|
|
||||||
.unwrap()
|
|
||||||
.as_str()
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
let row_re = Regex::new(
|
|
||||||
r"(?x)
|
|
||||||
^
|
|
||||||
\|
|
|
||||||
\s*
|
|
||||||
\[
|
|
||||||
(?P<number>[0-9]+)
|
|
||||||
\]
|
|
||||||
.*
|
|
||||||
\s*
|
|
||||||
\|
|
|
||||||
(?P<status>.*)
|
|
||||||
\|
|
|
||||||
(?P<title>.*)
|
|
||||||
\|
|
|
||||||
$
|
|
||||||
",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let mut originals = Vec::new();
|
|
||||||
|
|
||||||
for row in original.lines().skip(2) {
|
|
||||||
let captures = row_re.captures(row).unwrap();
|
|
||||||
originals.push(Bep {
|
|
||||||
number: captures.name("number").unwrap().as_str().parse()?,
|
|
||||||
status: captures.name("status").unwrap().as_str().trim().parse()?,
|
|
||||||
title: captures.name("title").unwrap().as_str().to_owned(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_eq!(originals.len(), beps.len());
|
|
||||||
|
|
||||||
let mut width = (0, 0, 0);
|
|
||||||
|
|
||||||
let rows = beps
|
|
||||||
.into_iter()
|
|
||||||
.zip(originals)
|
|
||||||
.map(|(bep, original)| {
|
|
||||||
assert_eq!(bep.number, original.number);
|
|
||||||
|
|
||||||
let row = (
|
|
||||||
format!(
|
|
||||||
"[{:02}](http://bittorrent.org/beps/bep_{:04}.html)",
|
|
||||||
bep.number, bep.number
|
|
||||||
),
|
|
||||||
original.status.to_string(),
|
|
||||||
bep.title,
|
|
||||||
);
|
|
||||||
|
|
||||||
width.0 = width.0.max(row.0.len());
|
|
||||||
width.1 = width.1.max(row.1.len());
|
|
||||||
width.2 = width.2.max(row.2.len());
|
|
||||||
|
|
||||||
row
|
|
||||||
})
|
|
||||||
.collect::<Vec<(String, String, String)>>();
|
|
||||||
|
|
||||||
let mut lines = Vec::new();
|
|
||||||
|
|
||||||
lines.push(format!(
|
|
||||||
"| {:w0$} | {:w1$} | {:w2$} |",
|
|
||||||
"BEP",
|
|
||||||
"Status",
|
|
||||||
"Title",
|
|
||||||
w0 = width.0,
|
|
||||||
w1 = width.1,
|
|
||||||
w2 = width.2,
|
|
||||||
));
|
|
||||||
|
|
||||||
lines.push(format!(
|
|
||||||
"|:{:-<w0$}:|:{:-<w1$}:|:{:-<w2$}-|",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
w0 = width.0,
|
|
||||||
w1 = width.1,
|
|
||||||
w2 = width.2,
|
|
||||||
));
|
|
||||||
|
|
||||||
for (bep, status, title) in rows {
|
|
||||||
lines.push(format!(
|
|
||||||
"| {:w0$} | {:w1$} | {:w2$} |",
|
|
||||||
bep,
|
|
||||||
status,
|
|
||||||
title,
|
|
||||||
w0 = width.0,
|
|
||||||
w1 = width.1,
|
|
||||||
w2 = width.2,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let table = lines.join("\n");
|
|
||||||
|
|
||||||
let readme = &[before.trim(), "", &table, after.trim(), ""].join("\n");
|
|
||||||
|
|
||||||
fs::write(README, readme.as_bytes())?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,78 +0,0 @@
|
||||||
use crate::common::*;
|
|
||||||
|
|
||||||
pub(crate) enum Status {
|
|
||||||
Unknown,
|
|
||||||
NotApplicable,
|
|
||||||
Supported,
|
|
||||||
NotSupported { tracking_issue: Option<u64> },
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for Status {
|
|
||||||
type Err = String;
|
|
||||||
|
|
||||||
fn from_str(text: &str) -> Result<Self, Self::Err> {
|
|
||||||
let error = || format!("invalid status: {}", text);
|
|
||||||
|
|
||||||
let unescaped = text.replace('\\', "");
|
|
||||||
|
|
||||||
let (emoji, tracking_issue) = if !unescaped.starts_with('[') {
|
|
||||||
(text, None)
|
|
||||||
} else {
|
|
||||||
let status_pattern = Regex::new(
|
|
||||||
r"(?x)
|
|
||||||
^
|
|
||||||
\[
|
|
||||||
(?P<emoji>:[a-zA-Z0-9]+:)
|
|
||||||
\]
|
|
||||||
\(
|
|
||||||
https://github.com/casey/intermodal/issues/(?P<tracking_issue>[0-9]+)
|
|
||||||
\)
|
|
||||||
$
|
|
||||||
",
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let captures = status_pattern.captures(&unescaped).ok_or_else(error)?;
|
|
||||||
|
|
||||||
let emoji = captures.name("emoji").unwrap().as_str();
|
|
||||||
|
|
||||||
let tracking_issue = captures
|
|
||||||
.name("tracking_issue")
|
|
||||||
.map(|text| text.as_str().parse::<u64>().unwrap());
|
|
||||||
|
|
||||||
(emoji, tracking_issue)
|
|
||||||
};
|
|
||||||
|
|
||||||
match emoji {
|
|
||||||
"x" => Ok(Status::NotSupported { tracking_issue }),
|
|
||||||
"+" => Ok(Status::Supported),
|
|
||||||
"-" => Ok(Status::NotApplicable),
|
|
||||||
"?" => Ok(Status::Unknown),
|
|
||||||
":x:" => Ok(Status::NotSupported { tracking_issue }),
|
|
||||||
":white_check_mark:" => Ok(Status::Supported),
|
|
||||||
":heavy_minus_sign:" => Ok(Status::NotApplicable),
|
|
||||||
":grey_question:" => Ok(Status::Unknown),
|
|
||||||
_ => Err(error()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Status {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Unknown => write!(f, ":grey_question:"),
|
|
||||||
Self::NotApplicable => write!(f, ":heavy_minus_sign:"),
|
|
||||||
Self::Supported => write!(f, ":white_check_mark:"),
|
|
||||||
Self::NotSupported {
|
|
||||||
tracking_issue: None,
|
|
||||||
} => write!(f, ":x:"),
|
|
||||||
Self::NotSupported {
|
|
||||||
tracking_issue: Some(number),
|
|
||||||
} => write!(
|
|
||||||
f,
|
|
||||||
"[:x:](https://github.com/casey/intermodal/issues/{})",
|
|
||||||
number
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -9,7 +9,7 @@ Summary
|
||||||
- [`imdl torrent`](./commands/imdl-torrent.md)
|
- [`imdl torrent`](./commands/imdl-torrent.md)
|
||||||
- [`imdl torrent create`](./commands/imdl-torrent-create.md)
|
- [`imdl torrent create`](./commands/imdl-torrent-create.md)
|
||||||
- [`imdl torrent link`](./commands/imdl-torrent-link.md)
|
- [`imdl torrent link`](./commands/imdl-torrent-link.md)
|
||||||
- [`imdl torrent piece length`](./commands/imdl-torrent-piece-length.md)
|
- [`imdl torrent piece-length`](./commands/imdl-torrent-piece-length.md)
|
||||||
- [`imdl torrent show`](./commands/imdl-torrent-show.md)
|
- [`imdl torrent show`](./commands/imdl-torrent-show.md)
|
||||||
- [`imdl torrent stats`](./commands/imdl-torrent-stats.md)
|
- [`imdl torrent stats`](./commands/imdl-torrent-stats.md)
|
||||||
- [`imdl torrent verify`](./commands/imdl-torrent-verify.md)
|
- [`imdl torrent verify`](./commands/imdl-torrent-verify.md)
|
||||||
|
|
|
@ -4,14 +4,17 @@ imdl-completions 0.1.5
|
||||||
Print shell completion scripts to standard output.
|
Print shell completion scripts to standard output.
|
||||||
|
|
||||||
USAGE:
|
USAGE:
|
||||||
imdl completions --shell <SHELL>
|
imdl completions [OPTIONS] --shell <SHELL>
|
||||||
|
|
||||||
FLAGS:
|
FLAGS:
|
||||||
-h, --help Print help message.
|
-h, --help Print help message.
|
||||||
-V, --version Print version number.
|
-V, --version Print version number.
|
||||||
|
|
||||||
OPTIONS:
|
OPTIONS:
|
||||||
-s, --shell <SHELL> Print completions for `SHELL`. [possible values: zsh,
|
-d, --dir <DIR> Write completion script to `DIR` with an appropriate
|
||||||
bash, fish, powershell, elvish]
|
filename. If `--shell` is not given, write all
|
||||||
|
completion scripts.
|
||||||
|
-s, --shell <SHELL> Print completion script for `SHELL`. [possible
|
||||||
|
values: zsh, bash, fish, powershell, elvish]
|
||||||
|
|
||||||
```
|
```
|
|
@ -10,13 +10,13 @@ FLAGS:
|
||||||
-h, --help Print help message.
|
-h, --help Print help message.
|
||||||
-O, --open Open generated magnet link. Uses `xdg-open`, `gnome-open`,
|
-O, --open Open generated magnet link. Uses `xdg-open`, `gnome-open`,
|
||||||
or `kde-open` on Linux; `open` on macOS; and `cmd /C start`
|
or `kde-open` on Linux; `open` on macOS; and `cmd /C start`
|
||||||
on Windows
|
on Windows.
|
||||||
-V, --version Print version number.
|
-V, --version Print version number.
|
||||||
|
|
||||||
OPTIONS:
|
OPTIONS:
|
||||||
-s, --select-only <INDICES>...
|
-s, --select-only <INDICES>...
|
||||||
Specify files that torrent clients select for download. Values are
|
Select files to download. Values are indices into the `info.files`
|
||||||
indices into the info.files list. e.g. `--select-only 1,2,3`
|
list, e.g. `--select-only 1,2,3`.
|
||||||
-i, --input <METAINFO>
|
-i, --input <METAINFO>
|
||||||
Generate magnet link from metainfo at `PATH`. If `PATH` is `-`, read
|
Generate magnet link from metainfo at `PATH`. If `PATH` is `-`, read
|
||||||
metainfo from standard input.
|
metainfo from standard input.
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# `imdl torrent piece length`
|
# `imdl torrent piece-length`
|
||||||
```
|
```
|
||||||
imdl-torrent-piece-length 0.1.5
|
imdl-torrent-piece-length 0.1.5
|
||||||
Display information about automatic piece length selection.
|
Display information about automatic piece length selection.
|
||||||
|
|
|
@ -1,39 +1,62 @@
|
||||||
# Intermodal
|
# Intermodal: A 40' shipping container for the Internet
|
||||||
|
|
||||||
Intermodal is, as the moment, a BitTorrent metainfo utility. The binary is called `imdl`.
|
Intermodal is a user-friendly and featureful command-line BitTorrent metainfo utility for Linux, Windows, and macOS.
|
||||||
|
|
||||||
Project development is hosted on GitHub at [github.com/casey/intermodal](https://github.com/casey/intermodal).
|
Project development is hosted on [GitHub](https://github.com/casey/intermodal).
|
||||||
|
|
||||||
|
The binary is called `imdl`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ imdl --help
|
||||||
|
```
|
||||||
|
|
||||||
|
BitTorrent metainfo related functionality is under the `torrent` subcommand:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ imdl torrent --help
|
||||||
|
```
|
||||||
|
|
||||||
Intermodal can be used to create `.torrent` files:
|
Intermodal can be used to create `.torrent` files:
|
||||||
|
|
||||||
```
|
```sh
|
||||||
$ imdl torrent create --input foo
|
$ imdl torrent create --input foo
|
||||||
```
|
```
|
||||||
|
|
||||||
Print information about existing torrent files:
|
Print information about existing `.torrent` files:
|
||||||
|
|
||||||
```
|
```sh
|
||||||
$ imdl torrent show --input foo.torrent
|
$ imdl torrent show --input foo.torrent
|
||||||
```
|
```
|
||||||
|
|
||||||
Verify downloaded torrents:
|
Verify downloaded torrents:
|
||||||
|
|
||||||
```
|
```sh
|
||||||
$ imdl torrent verify --input foo.torrent --content foo
|
$ imdl torrent verify --input foo.torrent --content foo
|
||||||
```
|
```
|
||||||
|
|
||||||
Generate magnet links from torrent files:
|
Generate magnet links from `.torrent` files:
|
||||||
|
|
||||||
```
|
```sh
|
||||||
$ imdl torrent link --input foo.torrent
|
$ imdl torrent link --input foo.torrent
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Show infromation about the piece length picker:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ imdl torrent piece-length
|
||||||
|
```
|
||||||
|
|
||||||
|
Print completion scripts for the `imdl` binary:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ imdl completions --shell zsh
|
||||||
|
```
|
||||||
|
|
||||||
Functionality that is not yet finalized, but still available for preview, can be accessed with the `--unstable` flag:
|
Functionality that is not yet finalized, but still available for preview, can be accessed with the `--unstable` flag:
|
||||||
|
|
||||||
Print information about a collection of torrents:
|
Print information about a collection of torrents:
|
||||||
|
|
||||||
```
|
```sh
|
||||||
$ imdl --unstable torrent stats --input dir
|
$ imdl --unstable torrent stats --input dir
|
||||||
```
|
```
|
||||||
|
|
||||||
Happy sharing!
|
Happy sharing!
|
||||||
|
|
|
@ -213,8 +213,10 @@ esac
|
||||||
;;
|
;;
|
||||||
(completions)
|
(completions)
|
||||||
_arguments "${_arguments_options[@]}" \
|
_arguments "${_arguments_options[@]}" \
|
||||||
'-s+[Print completions for `SHELL`.]: :(zsh bash fish powershell elvish)' \
|
'-s+[Print completion script for `SHELL`.]: :(zsh bash fish powershell elvish)' \
|
||||||
'--shell=[Print completions for `SHELL`.]: :(zsh bash fish powershell elvish)' \
|
'--shell=[Print completion script for `SHELL`.]: :(zsh bash fish powershell elvish)' \
|
||||||
|
'-d+[Write completion script to `DIR` with an appropriate filename. If `--shell` is not given, write all completion scripts.]' \
|
||||||
|
'--dir=[Write completion script to `DIR` with an appropriate filename. If `--shell` is not given, write all completion scripts.]' \
|
||||||
'-h[Print help message.]' \
|
'-h[Print help message.]' \
|
||||||
'--help[Print help message.]' \
|
'--help[Print help message.]' \
|
||||||
'-V[Print version number.]' \
|
'-V[Print version number.]' \
|
|
@ -192,8 +192,10 @@ Sort in ascending order by size, break ties in descending path order:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
'imdl;completions' {
|
'imdl;completions' {
|
||||||
[CompletionResult]::new('-s', 's', [CompletionResultType]::ParameterName, 'Print completions for `SHELL`.')
|
[CompletionResult]::new('-s', 's', [CompletionResultType]::ParameterName, 'Print completion script for `SHELL`.')
|
||||||
[CompletionResult]::new('--shell', 'shell', [CompletionResultType]::ParameterName, 'Print completions for `SHELL`.')
|
[CompletionResult]::new('--shell', 'shell', [CompletionResultType]::ParameterName, 'Print completion script for `SHELL`.')
|
||||||
|
[CompletionResult]::new('-d', 'd', [CompletionResultType]::ParameterName, 'Write completion script to `DIR` with an appropriate filename. If `--shell` is not given, write all completion scripts.')
|
||||||
|
[CompletionResult]::new('--dir', 'dir', [CompletionResultType]::ParameterName, 'Write completion script to `DIR` with an appropriate filename. If `--shell` is not given, write all completion scripts.')
|
||||||
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help message.')
|
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help message.')
|
||||||
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help message.')
|
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help message.')
|
||||||
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version number.')
|
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version number.')
|
|
@ -70,7 +70,7 @@ _imdl() {
|
||||||
;;
|
;;
|
||||||
|
|
||||||
imdl__completions)
|
imdl__completions)
|
||||||
opts=" -h -V -s --help --version --shell "
|
opts=" -h -V -s -d --help --version --shell --dir "
|
||||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
|
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
|
||||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||||
return 0
|
return 0
|
||||||
|
@ -85,6 +85,14 @@ _imdl() {
|
||||||
COMPREPLY=($(compgen -W "zsh bash fish powershell elvish" -- "${cur}"))
|
COMPREPLY=($(compgen -W "zsh bash fish powershell elvish" -- "${cur}"))
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
|
--dir)
|
||||||
|
COMPREPLY=($(compgen -f "${cur}"))
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
-d)
|
||||||
|
COMPREPLY=($(compgen -f "${cur}"))
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
COMPREPLY=()
|
COMPREPLY=()
|
||||||
;;
|
;;
|
||||||
|
|
|
@ -178,8 +178,10 @@ Sort in ascending order by size, break ties in descending path order:
|
||||||
cand --version 'Prints version information'
|
cand --version 'Prints version information'
|
||||||
}
|
}
|
||||||
&'imdl;completions'= {
|
&'imdl;completions'= {
|
||||||
cand -s 'Print completions for `SHELL`.'
|
cand -s 'Print completion script for `SHELL`.'
|
||||||
cand --shell 'Print completions for `SHELL`.'
|
cand --shell 'Print completion script for `SHELL`.'
|
||||||
|
cand -d 'Write completion script to `DIR` with an appropriate filename. If `--shell` is not given, write all completion scripts.'
|
||||||
|
cand --dir 'Write completion script to `DIR` with an appropriate filename. If `--shell` is not given, write all completion scripts.'
|
||||||
cand -h 'Print help message.'
|
cand -h 'Print help message.'
|
||||||
cand --help 'Print help message.'
|
cand --help 'Print help message.'
|
||||||
cand -V 'Print version number.'
|
cand -V 'Print version number.'
|
||||||
|
|
|
@ -90,7 +90,8 @@ complete -c imdl -n "__fish_seen_subcommand_from verify" -s h -l help -d 'Print
|
||||||
complete -c imdl -n "__fish_seen_subcommand_from verify" -s V -l version -d 'Print version number.'
|
complete -c imdl -n "__fish_seen_subcommand_from verify" -s V -l version -d 'Print version number.'
|
||||||
complete -c imdl -n "__fish_seen_subcommand_from help" -s h -l help -d 'Prints help information'
|
complete -c imdl -n "__fish_seen_subcommand_from help" -s h -l help -d 'Prints help information'
|
||||||
complete -c imdl -n "__fish_seen_subcommand_from help" -s V -l version -d 'Prints version information'
|
complete -c imdl -n "__fish_seen_subcommand_from help" -s V -l version -d 'Prints version information'
|
||||||
complete -c imdl -n "__fish_seen_subcommand_from completions" -s s -l shell -d 'Print completions for `SHELL`.' -r -f -a "zsh bash fish powershell elvish"
|
complete -c imdl -n "__fish_seen_subcommand_from completions" -s s -l shell -d 'Print completion script for `SHELL`.' -r -f -a "zsh bash fish powershell elvish"
|
||||||
|
complete -c imdl -n "__fish_seen_subcommand_from completions" -s d -l dir -d 'Write completion script to `DIR` with an appropriate filename. If `--shell` is not given, write all completion scripts.'
|
||||||
complete -c imdl -n "__fish_seen_subcommand_from completions" -s h -l help -d 'Print help message.'
|
complete -c imdl -n "__fish_seen_subcommand_from completions" -s h -l help -d 'Print help message.'
|
||||||
complete -c imdl -n "__fish_seen_subcommand_from completions" -s V -l version -d 'Print version number.'
|
complete -c imdl -n "__fish_seen_subcommand_from completions" -s V -l version -d 'Print version number.'
|
||||||
complete -c imdl -n "__fish_seen_subcommand_from help" -s h -l help -d 'Prints help information'
|
complete -c imdl -n "__fish_seen_subcommand_from help" -s h -l help -d 'Prints help information'
|
||||||
|
|
46
justfile
46
justfile
|
@ -51,10 +51,6 @@ preview-readme:
|
||||||
book:
|
book:
|
||||||
mdbook serve book --open --dest-dir ../www/book
|
mdbook serve book --open --dest-dir ../www/book
|
||||||
|
|
||||||
# add git log messages to changelog
|
|
||||||
changes:
|
|
||||||
git log --pretty=format:%s >> CHANGELOG.md
|
|
||||||
|
|
||||||
dev-deps:
|
dev-deps:
|
||||||
brew install grip
|
brew install grip
|
||||||
cargo install mdbook
|
cargo install mdbook
|
||||||
|
@ -63,37 +59,16 @@ dev-deps:
|
||||||
brew install imagemagick
|
brew install imagemagick
|
||||||
brew install gifsicle
|
brew install gifsicle
|
||||||
|
|
||||||
# update readme table of contents
|
# update generated documentation
|
||||||
update-toc:
|
gen:
|
||||||
cargo run --package update-readme toc
|
cargo run --package gen all
|
||||||
|
|
||||||
generate-completions:
|
|
||||||
./bin/generate-completions
|
|
||||||
|
|
||||||
man-watch:
|
|
||||||
cargo build
|
|
||||||
cargo watch \
|
|
||||||
--ignore '/man' \
|
|
||||||
--ignore '/man/*' \
|
|
||||||
--ignore '/book/src/commands' \
|
|
||||||
--ignore '/book/src/commands/*' \
|
|
||||||
--clear --exec 'run --package man'
|
|
||||||
|
|
||||||
man:
|
|
||||||
cargo build
|
|
||||||
cargo run --package man
|
|
||||||
|
|
||||||
check-man: man
|
|
||||||
git diff --no-ext-diff --quiet --exit-code
|
|
||||||
|
|
||||||
check-minimal-versions:
|
check-minimal-versions:
|
||||||
./bin/check-minimal-versions
|
./bin/check-minimal-versions
|
||||||
|
|
||||||
check: test clippy lint check-minimal-versions changelog-update
|
check: test clippy lint check-minimal-versions gen
|
||||||
git diff --no-ext-diff --quiet --exit-code
|
git diff --no-ext-diff --quiet --exit-code
|
||||||
cargo +nightly fmt --all -- --check
|
cargo +nightly fmt --all -- --check
|
||||||
cargo run --package update-readme toc
|
|
||||||
git diff --no-ext-diff --quiet --exit-code
|
|
||||||
|
|
||||||
draft: push
|
draft: push
|
||||||
hub pull-request -o --draft
|
hub pull-request -o --draft
|
||||||
|
@ -109,9 +84,7 @@ merge:
|
||||||
done
|
done
|
||||||
just done
|
just done
|
||||||
|
|
||||||
update: man changelog-update update-toc
|
publish-check: check
|
||||||
|
|
||||||
publish-check: check check-man
|
|
||||||
cargo outdated --exit-code 1
|
cargo outdated --exit-code 1
|
||||||
grep {{version}} CHANGELOG.md
|
grep {{version}} CHANGELOG.md
|
||||||
|
|
||||||
|
@ -126,15 +99,6 @@ publish: publish-check
|
||||||
cargo publish
|
cargo publish
|
||||||
just merge
|
just merge
|
||||||
|
|
||||||
changelog-update:
|
|
||||||
cargo run --package changelog update
|
|
||||||
|
|
||||||
changelog-types:
|
|
||||||
cargo run --package changelog types
|
|
||||||
|
|
||||||
changelog-issue-template:
|
|
||||||
cargo run --package changelog issue-template
|
|
||||||
|
|
||||||
# record, upload, and render demo animation
|
# record, upload, and render demo animation
|
||||||
demo: demo-record demo-upload demo-render
|
demo: demo-record demo-upload demo-render
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.11.
|
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.13.
|
||||||
.TH IMDL-COMPLETIONS "1" "April 2020" "Intermodal v0.1.5" "Intermodal Manual"
|
.TH IMDL-COMPLETIONS "1" "April 2020" "Intermodal v0.1.5" "Intermodal Manual"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
\fBimdl\ completions\fR
|
\fBimdl\ completions\fR
|
||||||
- Print shell completion scripts to standard output.
|
- Print shell completion scripts to standard output.
|
||||||
.SH "SYNOPSIS:"
|
.SH "SYNOPSIS:"
|
||||||
.IP
|
.IP
|
||||||
imdl completions \fB\-\-shell\fR <SHELL>
|
imdl completions [OPTIONS] \fB\-\-shell\fR <SHELL>
|
||||||
.SH "FLAGS:"
|
.SH "FLAGS:"
|
||||||
.TP
|
.TP
|
||||||
\fB\-h\fR, \fB\-\-help\fR
|
\fB\-h\fR, \fB\-\-help\fR
|
||||||
|
@ -15,5 +15,9 @@ Print help message.
|
||||||
Print version number.
|
Print version number.
|
||||||
.SH "OPTIONS:"
|
.SH "OPTIONS:"
|
||||||
.TP
|
.TP
|
||||||
|
\fB\-d\fR, \fB\-\-dir\fR <DIR>
|
||||||
|
Write completion script to `DIR` with an appropriate filename. If `\-\-shell` is not given,
|
||||||
|
write all completion scripts.
|
||||||
|
.TP
|
||||||
\fB\-s\fR, \fB\-\-shell\fR <SHELL>
|
\fB\-s\fR, \fB\-\-shell\fR <SHELL>
|
||||||
Print completions for `SHELL`. [possible values: zsh, bash, fish, powershell, elvish]
|
Print completion script for `SHELL`. [possible values: zsh, bash, fish, powershell, elvish]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.11.
|
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.13.
|
||||||
.TH IMDL-TORRENT-CREATE "1" "April 2020" "Intermodal v0.1.5" "Intermodal Manual"
|
.TH IMDL-TORRENT-CREATE "1" "April 2020" "Intermodal v0.1.5" "Intermodal Manual"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
\fBimdl\ torrent\ create\fR
|
\fBimdl\ torrent\ create\fR
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.11.
|
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.13.
|
||||||
.TH IMDL-TORRENT-LINK "1" "April 2020" "Intermodal v0.1.5" "Intermodal Manual"
|
.TH IMDL-TORRENT-LINK "1" "April 2020" "Intermodal v0.1.5" "Intermodal Manual"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
\fBimdl\ torrent\ link\fR
|
\fBimdl\ torrent\ link\fR
|
||||||
|
@ -13,15 +13,15 @@ Print help message.
|
||||||
.TP
|
.TP
|
||||||
\fB\-O\fR, \fB\-\-open\fR
|
\fB\-O\fR, \fB\-\-open\fR
|
||||||
Open generated magnet link. Uses `xdg\-open`, `gnome\-open`, or `kde\-open` on Linux; `open` on macOS;
|
Open generated magnet link. Uses `xdg\-open`, `gnome\-open`, or `kde\-open` on Linux; `open` on macOS;
|
||||||
and `cmd \fI\,/C\/\fP start` on Windows
|
and `cmd \fI\,/C\/\fP start` on Windows.
|
||||||
.TP
|
.TP
|
||||||
\fB\-V\fR, \fB\-\-version\fR
|
\fB\-V\fR, \fB\-\-version\fR
|
||||||
Print version number.
|
Print version number.
|
||||||
.SH "OPTIONS:"
|
.SH "OPTIONS:"
|
||||||
.TP
|
.TP
|
||||||
\fB\-s\fR, \fB\-\-select\-only\fR <INDICES>...
|
\fB\-s\fR, \fB\-\-select\-only\fR <INDICES>...
|
||||||
Specify files that torrent clients select for download. Values are indices into
|
Select files to download. Values are indices into the `info.files` list, e.g.
|
||||||
the info.files list. e.g. `\-\-select\-only 1,2,3`
|
`\-\-select\-only 1,2,3`.
|
||||||
.TP
|
.TP
|
||||||
\fB\-i\fR, \fB\-\-input\fR <METAINFO>
|
\fB\-i\fR, \fB\-\-input\fR <METAINFO>
|
||||||
Generate magnet link from metainfo at `PATH`. If `PATH` is `\-`, read metainfo from
|
Generate magnet link from metainfo at `PATH`. If `PATH` is `\-`, read metainfo from
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.11.
|
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.13.
|
||||||
.TH IMDL-TORRENT-PIECE-LENGTH "1" "April 2020" "Intermodal v0.1.5" "Intermodal Manual"
|
.TH IMDL-TORRENT-PIECE-LENGTH "1" "April 2020" "Intermodal v0.1.5" "Intermodal Manual"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
\fBimdl\ torrent\ piece-length\fR
|
\fBimdl\ torrent\ piece-length\fR
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.11.
|
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.13.
|
||||||
.TH IMDL-TORRENT-SHOW "1" "April 2020" "Intermodal v0.1.5" "Intermodal Manual"
|
.TH IMDL-TORRENT-SHOW "1" "April 2020" "Intermodal v0.1.5" "Intermodal Manual"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
\fBimdl\ torrent\ show\fR
|
\fBimdl\ torrent\ show\fR
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.11.
|
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.13.
|
||||||
.TH IMDL-TORRENT-STATS "1" "April 2020" "Intermodal v0.1.5" "Intermodal Manual"
|
.TH IMDL-TORRENT-STATS "1" "April 2020" "Intermodal v0.1.5" "Intermodal Manual"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
\fBimdl\ torrent\ stats\fR
|
\fBimdl\ torrent\ stats\fR
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.11.
|
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.13.
|
||||||
.TH IMDL-TORRENT-VERIFY "1" "April 2020" "Intermodal v0.1.5" "Intermodal Manual"
|
.TH IMDL-TORRENT-VERIFY "1" "April 2020" "Intermodal v0.1.5" "Intermodal Manual"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
\fBimdl\ torrent\ verify\fR
|
\fBimdl\ torrent\ verify\fR
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.11.
|
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.13.
|
||||||
.TH IMDL-TORRENT "1" "April 2020" "Intermodal v0.1.5" "Intermodal Manual"
|
.TH IMDL-TORRENT "1" "April 2020" "Intermodal v0.1.5" "Intermodal Manual"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
\fBimdl\ torrent\fR
|
\fBimdl\ torrent\fR
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.11.
|
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.13.
|
||||||
.TH \FBIMDL\FR "1" "April 2020" "Intermodal v0.1.5" "Intermodal Manual"
|
.TH \FBIMDL\FR "1" "April 2020" "Intermodal v0.1.5" "Intermodal Manual"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
\fBimdl\fR
|
\fBimdl\fR
|
||||||
|
|
|
@ -17,6 +17,7 @@ pub(crate) use std::{
|
||||||
path::{self, Path, PathBuf},
|
path::{self, Path, PathBuf},
|
||||||
process::{self, ExitStatus},
|
process::{self, ExitStatus},
|
||||||
str::{self, FromStr},
|
str::{self, FromStr},
|
||||||
|
string::FromUtf8Error,
|
||||||
sync::Once,
|
sync::Once,
|
||||||
time::{SystemTime, SystemTimeError},
|
time::{SystemTime, SystemTimeError},
|
||||||
usize,
|
usize,
|
||||||
|
@ -39,8 +40,8 @@ pub(crate) use structopt::{
|
||||||
clap::{self, AppSettings},
|
clap::{self, AppSettings},
|
||||||
StructOpt,
|
StructOpt,
|
||||||
};
|
};
|
||||||
pub(crate) use strum::VariantNames;
|
pub(crate) use strum::{IntoEnumIterator, VariantNames};
|
||||||
pub(crate) use strum_macros::{EnumString, EnumVariantNames, IntoStaticStr};
|
pub(crate) use strum_macros::{EnumIter, EnumString, EnumVariantNames, IntoStaticStr};
|
||||||
pub(crate) use unicode_width::UnicodeWidthStr;
|
pub(crate) use unicode_width::UnicodeWidthStr;
|
||||||
pub(crate) use url::{Host, Url};
|
pub(crate) use url::{Host, Url};
|
||||||
pub(crate) use walkdir::WalkDir;
|
pub(crate) use walkdir::WalkDir;
|
||||||
|
|
|
@ -187,6 +187,11 @@ impl Env {
|
||||||
Ok(self.dir().join(path).clean())
|
Ok(self.dir().join(path).clean())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn write(&mut self, path: impl AsRef<Path>, contents: impl AsRef<[u8]>) -> Result<()> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
fs::write(self.resolve(path)?, contents).context(error::Filesystem { path })
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn read(&mut self, source: InputTarget) -> Result<Input> {
|
pub(crate) fn read(&mut self, source: InputTarget) -> Result<Input> {
|
||||||
let data = match &source {
|
let data = match &source {
|
||||||
InputTarget::Path(path) => {
|
InputTarget::Path(path) => {
|
||||||
|
|
|
@ -111,6 +111,8 @@ pub(crate) enum Error {
|
||||||
PieceLengthZero,
|
PieceLengthZero,
|
||||||
#[snafu(display("Private torrents must have tracker"))]
|
#[snafu(display("Private torrents must have tracker"))]
|
||||||
PrivateTrackerless,
|
PrivateTrackerless,
|
||||||
|
#[snafu(display("Completion script for shell `{}` not UTF-8: {}", shell.name(), source))]
|
||||||
|
ShellDecode { shell: Shell, source: FromUtf8Error },
|
||||||
#[snafu(display("Failed to write to standard error: {}", source))]
|
#[snafu(display("Failed to write to standard error: {}", source))]
|
||||||
Stderr { source: io::Error },
|
Stderr { source: io::Error },
|
||||||
#[snafu(display("Failed to read from standard input: {}", source))]
|
#[snafu(display("Failed to read from standard input: {}", source))]
|
||||||
|
|
|
@ -34,6 +34,9 @@ mod errln;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod err;
|
mod err;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod out;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod outln;
|
mod outln;
|
||||||
|
|
||||||
|
|
11
src/out.rs
Normal file
11
src/out.rs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
macro_rules! out {
|
||||||
|
($env:expr) => {
|
||||||
|
write!($env.out_mut(), "").context(crate::error::Stdout)
|
||||||
|
};
|
||||||
|
($env:expr, $fmt:expr) => {
|
||||||
|
write!($env.out_mut(), $fmt).context(crate::error::Stdout)
|
||||||
|
};
|
||||||
|
($env:expr, $fmt:expr, $($arg:tt)*) => {
|
||||||
|
write!($env.out_mut(), $fmt, $($arg)*).context(crate::error::Stdout)
|
||||||
|
};
|
||||||
|
}
|
34
src/shell.rs
34
src/shell.rs
|
@ -2,7 +2,7 @@ use super::*;
|
||||||
|
|
||||||
use structopt::clap;
|
use structopt::clap;
|
||||||
|
|
||||||
#[derive(EnumVariantNames, EnumString)]
|
#[derive(Copy, Clone, EnumVariantNames, IntoStaticStr, EnumString, EnumIter, Debug)]
|
||||||
#[strum(serialize_all = "kebab-case")]
|
#[strum(serialize_all = "kebab-case")]
|
||||||
pub(crate) enum Shell {
|
pub(crate) enum Shell {
|
||||||
Zsh,
|
Zsh,
|
||||||
|
@ -12,6 +12,38 @@ pub(crate) enum Shell {
|
||||||
Elvish,
|
Elvish,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Shell {
|
||||||
|
pub(crate) fn completion_script(self) -> Result<String> {
|
||||||
|
let buffer = Vec::new();
|
||||||
|
let mut cursor = Cursor::new(buffer);
|
||||||
|
|
||||||
|
Arguments::clap().gen_completions_to(env!("CARGO_PKG_NAME"), self.into(), &mut cursor);
|
||||||
|
|
||||||
|
let buffer = cursor.into_inner();
|
||||||
|
|
||||||
|
let script = String::from_utf8(buffer).context(error::ShellDecode { shell: self })?;
|
||||||
|
|
||||||
|
let mut script = script.trim().to_owned();
|
||||||
|
script.push('\n');
|
||||||
|
|
||||||
|
Ok(script)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn completion_script_filename(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Bash => "imdl.bash",
|
||||||
|
Self::Fish => "imdl.fish",
|
||||||
|
Self::Zsh => "_imdl",
|
||||||
|
Self::Powershell => "_imdl.ps1",
|
||||||
|
Self::Elvish => "imdl.elvish",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn name(self) -> &'static str {
|
||||||
|
self.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Into<clap::Shell> for Shell {
|
impl Into<clap::Shell> for Shell {
|
||||||
fn into(self) -> clap::Shell {
|
fn into(self) -> clap::Shell {
|
||||||
match self {
|
match self {
|
||||||
|
|
|
@ -12,24 +12,48 @@ pub(crate) struct Completions {
|
||||||
short = "s",
|
short = "s",
|
||||||
value_name = "SHELL",
|
value_name = "SHELL",
|
||||||
possible_values = Shell::VARIANTS,
|
possible_values = Shell::VARIANTS,
|
||||||
help = "Print completions for `SHELL`.",
|
required_unless = "dir",
|
||||||
|
help = "Print completion script for `SHELL`.",
|
||||||
)]
|
)]
|
||||||
shell: Shell,
|
shell: Option<Shell>,
|
||||||
|
#[structopt(
|
||||||
|
long = "dir",
|
||||||
|
short = "d",
|
||||||
|
value_name = "DIR",
|
||||||
|
empty_values = false,
|
||||||
|
parse(from_os_str),
|
||||||
|
help = "Write completion script to `DIR` with an appropriate filename. If `--shell` is not \
|
||||||
|
given, write all completion scripts."
|
||||||
|
)]
|
||||||
|
dir: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Completions {
|
impl Completions {
|
||||||
pub(crate) fn run(self, env: &mut Env) -> Result<(), Error> {
|
pub(crate) fn run(self, env: &mut Env) -> Result<()> {
|
||||||
let buffer = Vec::new();
|
if let Some(shell) = self.shell {
|
||||||
let mut cursor = Cursor::new(buffer);
|
if let Some(dir) = self.dir {
|
||||||
|
Self::write(env, &dir, shell)?;
|
||||||
|
} else {
|
||||||
|
let script = shell.completion_script()?;
|
||||||
|
out!(env, "{}", script)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let dir = self
|
||||||
|
.dir
|
||||||
|
.ok_or_else(|| Error::internal("Expected `--dir` to be set"))?;
|
||||||
|
|
||||||
Arguments::clap().gen_completions_to(env!("CARGO_PKG_NAME"), self.shell.into(), &mut cursor);
|
for shell in Shell::iter() {
|
||||||
|
Self::write(env, &dir, shell)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let buffer = cursor.into_inner();
|
Ok(())
|
||||||
|
}
|
||||||
let script = String::from_utf8(buffer).expect("Clap completion not UTF-8");
|
|
||||||
|
|
||||||
outln!(env, "{}", script.trim())?;
|
|
||||||
|
|
||||||
|
fn write(env: &mut Env, dir: &Path, shell: Shell) -> Result<()> {
|
||||||
|
let script = shell.completion_script()?;
|
||||||
|
let dst = dir.join(shell.completion_script_filename());
|
||||||
|
env.write(dst, script)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,4 +77,44 @@ mod tests {
|
||||||
|
|
||||||
assert!(env.out().starts_with("_imdl() {"));
|
assert!(env.out().starts_with("_imdl() {"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single_dir() {
|
||||||
|
let mut env = test_env! {
|
||||||
|
args: [
|
||||||
|
"completions",
|
||||||
|
"--shell",
|
||||||
|
"bash",
|
||||||
|
"--dir",
|
||||||
|
".",
|
||||||
|
],
|
||||||
|
tree: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
env.assert_ok();
|
||||||
|
|
||||||
|
let script = env.read_to_string("imdl.bash");
|
||||||
|
|
||||||
|
assert!(script.starts_with("_imdl() {"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn all_dir() {
|
||||||
|
let mut env = test_env! {
|
||||||
|
args: [
|
||||||
|
"completions",
|
||||||
|
"--dir",
|
||||||
|
".",
|
||||||
|
],
|
||||||
|
tree: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
env.assert_ok();
|
||||||
|
|
||||||
|
let script = env.read_to_string("imdl.bash");
|
||||||
|
assert!(script.starts_with("_imdl() {"));
|
||||||
|
|
||||||
|
let script = env.read_to_string("_imdl.ps1");
|
||||||
|
assert!(script.starts_with("using namespace"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,10 @@ impl TestEnv {
|
||||||
fs::create_dir(self.env.resolve(path).unwrap()).unwrap();
|
fs::create_dir(self.env.resolve(path).unwrap()).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn read_to_string(&self, path: impl AsRef<Path>) -> String {
|
||||||
|
fs::read_to_string(self.env.resolve(path).unwrap()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub(crate) fn metadata(&self, path: impl AsRef<Path>) -> fs::Metadata {
|
pub(crate) fn metadata(&self, path: impl AsRef<Path>) -> fs::Metadata {
|
||||||
fs::metadata(self.env.resolve(path).unwrap()).unwrap()
|
fs::metadata(self.env.resolve(path).unwrap()).unwrap()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user