Move all output from bin/gen to target/gen

To make it clearer what is and isn't generated content, make gen place
all generated output in `target/gen`.

Also, try to make the readme clearer about the location of build
artifacts.

type: development
This commit is contained in:
Casey Rodarmor 2020-04-30 21:21:20 -07:00
parent bf29d74b3e
commit e7872f56f2
No known key found for this signature in database
GPG Key ID: 556186B153EC6FE0
28 changed files with 503 additions and 547 deletions

View File

@ -108,7 +108,7 @@ jobs:
if: matrix.os == 'macos-latest'
run: |
brew install help2man
cargo run --package gen all
cargo run --package gen -- --bin target/debug/imdl all
git diff --no-ext-diff --exit-code
- name: Install `mdbook`
@ -118,7 +118,7 @@ jobs:
- name: Build Book
run: |
cargo run --package gen book
cargo run --package gen -- --bin target/debug/imdl book
mdbook build book --dest-dir ../www/book
- name: Record Git Revision
@ -139,12 +139,12 @@ jobs:
shell: bash
run: ./bin/package ${{github.ref}} ${{matrix.os}} ${{matrix.target}}
- name: Publish
- name: Publish Release Archive
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
with:
draft: false
files: ${{steps.package.outputs.archive}}
prerelease: false
prerelease: true
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}

12
.gitignore vendored
View File

@ -1,17 +1,5 @@
**/*.rs.bk
/CHANGELOG.md
/book/book
/book/src/SUMMARY.md
/book/src/bittorrent.md
/book/src/changelog.md
/book/src/commands.md
/book/src/commands/
/book/src/faq.md
/book/src/introduction.md
/book/src/references
/book/src/references.md
/completions
/man
/target
/wiki
/www/book

10
Cargo.lock generated
View File

@ -354,6 +354,12 @@ version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
[[package]]
name = "fs_extra"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f2a4a2034423744d2cc7ca2068453168dcdb82c438419e639a26bd87839c674"
[[package]]
name = "gen"
version = "0.0.0"
@ -363,10 +369,9 @@ dependencies = [
"cargo_toml",
"chrono",
"fehler",
"fs_extra",
"git2",
"globset",
"ignore",
"lexiclean",
"libc",
"log",
"pretty_env_logger",
@ -379,7 +384,6 @@ dependencies = [
"strum_macros",
"tempfile",
"url",
"walkdir",
]
[[package]]

View File

@ -31,10 +31,7 @@ For more about the project and its goals, check out
- [Examples](#examples)
- [FAQ](#faq)
- [Notes for Packagers](#notes-for-packagers)
- [Package Artifacts](#package-artifacts)
- [Binary](#binary)
- [Man Pages](#man-pages)
- [Completion Scripts](#completion-scripts)
- [Build Artifacts](#build-artifacts)
- [Release Updates](#release-updates)
- [Chat](#chat)
- [Contributing](#contributing)
@ -181,46 +178,38 @@ Intermodal is distributed under the
a public domain dedication with a fallback all-permissive license. The SPDX
identifier of the CC0 is [CC0-1.0](https://spdx.org/licenses/CC0-1.0.html).
### Package Artifacts
### Build Artifacts
There are three primary build artifacts: the binary, the man pages, and the
shell completion scripts.
There are a number of build artifacts: the binary, the man pages, the
changelog, and the shell completion scripts.
#### Binary
The binary is built with `cargo`, and the other artifacts are built `gen`,
located in `bin/gen`.
The binary is called `imdl`, and can be built with:
The binary can be built with:
```
cargo build --release
```
cargo build --release
After building, the binary will be present at `target/release/imdl`.
_`gen` requires [`help2man`](https://www.gnu.org/software/help2man/) to be
installed, which is used to generate man pages from subcommand `--help`
strings._
#### Man Pages
The rest of the build artifacts can be built with `gen`:
Intermodal has a number of subcommands, each of which has a man page. The man
pages are generated from the `--help` text using
[`help2man`](https://www.gnu.org/software/help2man/).
cargo run --package gen -- --bin target/release/imdl all
To generate the man pages, ensure `help2man` is available, and run:
_The path to the built `imdl` executable should be passed to `gen` with the `--bin` flag._
```
mkdir -p man
cargo run --package gen man
```
After running the above commands, the following table shows the location of the
built artifacts.
After building, the man pages will be available in `man`.
#### Completion Scripts
Completion scripts are available for a number of shells. To generate them, run:
```
mkdir -p completions
cargo run --release completions --dir completions
```
After running, the completion scripts will be available in `completions`.
| Artifact | Location |
|--------------------|----------------------------|
| Binary | `target/release/imdl` |
| Man Pages | `target/gen/man/*` |
| Completion Scripts | `target/gen/completions/*` |
| Changelog | `target/gen/CHANGELOG.md` |
| Readme | `target/gen/README.md` |
### Release Updates

View File

@ -11,10 +11,9 @@ askama = "0.9.0"
cargo_toml = "0.8.0"
chrono = "0.4.11"
fehler = "1.0.0"
fs_extra = "1.1.0"
git2 = "0.13.1"
globset = "0.4.5"
ignore = "0.4.14"
lexiclean = "0.0.1"
libc = "0.2.69"
log = "0.4.8"
pretty_env_logger = "0.4.0"
@ -25,7 +24,6 @@ structopt = "0.3.12"
strum = "0.18.0"
strum_macros = "0.18.0"
tempfile = "3.1.0"
walkdir = "2.3.1"
[dependencies.serde]
version = "1.0.106"

16
bin/gen/src/arguments.rs Normal file
View File

@ -0,0 +1,16 @@
use crate::common::*;
#[derive(StructOpt)]
pub(crate) struct Arguments {
#[structopt(flatten)]
options: Options,
#[structopt(subcommand)]
subcommand: Subcommand,
}
impl Arguments {
#[throws]
pub(crate) fn run(self) {
self.subcommand.run(self.options)?;
}
}

View File

@ -2,7 +2,7 @@ use crate::common::*;
pub(crate) struct Bin {
path: PathBuf,
pub(crate) subcommands: Vec<Subcommand>,
pub(crate) subcommands: Vec<BinSubcommand>,
}
impl Bin {
@ -22,7 +22,7 @@ impl Bin {
#[throws]
fn add_subcommands(&mut self, command: &mut Vec<String>) {
let subcommand = Subcommand::new(&self.path, command.clone())?;
let subcommand = BinSubcommand::new(&self.path, command.clone())?;
for name in &subcommand.subcommands {
command.push(name.into());

View File

@ -0,0 +1,150 @@
use crate::common::*;
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq)]
pub(crate) struct BinSubcommand {
pub(crate) bin: PathBuf,
pub(crate) command: Vec<String>,
pub(crate) subcommands: Vec<String>,
}
impl BinSubcommand {
#[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 {
info!("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();
info!("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().context(error::Tempdir)?;
let include_path = tmp.path().join("include");
fs::write(&include_path, include).context(error::Filesystem {
path: &include_path,
})?;
let version = cmd!(&self.bin, "--version")
.out()?
.split_whitespace()
.nth(1)
.unwrap()
.to_owned();
info!("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 = "imdl".to_string();
for name in &self.command {
slug.push('-');
slug.push_str(&name);
}
slug
}
pub(crate) fn command_line(&self) -> String {
let mut line = "imdl".to_string();
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)
}
}

View File

@ -17,8 +17,6 @@ pub(crate) use cargo_toml::Manifest;
pub(crate) use chrono::{DateTime, NaiveDateTime, Utc};
pub(crate) use fehler::{throw, throws};
pub(crate) use git2::{Commit, Oid, Repository};
pub(crate) use ignore::overrides::OverrideBuilder;
pub(crate) use lexiclean::Lexiclean;
pub(crate) use libc::EXIT_FAILURE;
pub(crate) use log::info;
pub(crate) use regex::Regex;
@ -29,7 +27,6 @@ pub(crate) use structopt::StructOpt;
pub(crate) use strum::VariantNames;
pub(crate) use strum_macros::{EnumVariantNames, IntoStaticStr};
pub(crate) use url::Url;
pub(crate) use walkdir::WalkDir;
// modules
pub(crate) use crate::error;
@ -39,9 +36,9 @@ pub(crate) use crate::{command_ext::CommandExt, row::Row, slug::Slug, template_e
// structs and enums
pub(crate) use crate::{
bin::Bin, changelog::Changelog, config::Config, entry::Entry, error::Error, example::Example,
faq::Faq, faq_entry::FaqEntry, introduction::Introduction, kind::Kind, metadata::Metadata,
opt::Opt, package::Package, project::Project, readme::Readme, reference::Reference,
reference_section::ReferenceSection, release::Release, subcommand::Subcommand, summary::Summary,
table::Table,
arguments::Arguments, bin::Bin, bin_subcommand::BinSubcommand, changelog::Changelog,
config::Config, entry::Entry, error::Error, example::Example, faq::Faq, faq_entry::FaqEntry,
introduction::Introduction, kind::Kind, metadata::Metadata, options::Options, package::Package,
project::Project, readme::Readme, reference::Reference, reference_section::ReferenceSection,
release::Release, subcommand::Subcommand, summary::Summary, table::Table,
};

View File

@ -58,6 +58,12 @@ pub(crate) enum Error {
dst: PathBuf,
source: io::Error,
},
#[snafu(display("I/O error copying `{}` to `{}`: {}", src.display(), dst.display(), source))]
FilesystemRecursiveCopy {
src: PathBuf,
dst: PathBuf,
source: fs_extra::error::Error,
},
#[snafu(display("Git error: {}", source))]
Git { source: git2::Error },
#[snafu(display("Regex compilation error: {}", source))]
@ -73,10 +79,6 @@ pub(crate) enum Error {
TemplateRender { source: askama::Error },
#[snafu(display("Failed to get workdir for repo at `{}`", repo.display()))]
Workdir { repo: PathBuf },
#[snafu(display("Failed to build overrides: {}", source))]
Ignore { source: ignore::Error },
#[snafu(display("Failed to traverse worktree: {}", source))]
Walkdir { source: walkdir::Error },
#[snafu(display("Failed to strip path prefix: {}", source))]
StripPrefix { source: StripPrefixError },
}
@ -98,15 +100,3 @@ impl From<cargo_toml::Error> for Error {
Self::CargoToml { source }
}
}
impl From<ignore::Error> for Error {
fn from(source: ignore::Error) -> Self {
Self::Ignore { source }
}
}
impl From<walkdir::Error> for Error {
fn from(source: walkdir::Error) -> Self {
Self::Walkdir { source }
}
}

View File

@ -3,7 +3,9 @@ use crate::common::*;
#[macro_use]
mod cmd;
mod arguments;
mod bin;
mod bin_subcommand;
mod changelog;
mod command_ext;
mod common;
@ -16,7 +18,7 @@ mod faq_entry;
mod introduction;
mod kind;
mod metadata;
mod opt;
mod options;
mod package;
mod project;
mod readme;
@ -33,7 +35,7 @@ mod template_ext;
fn main() {
pretty_env_logger::init();
if let Err(error) = Opt::from_args().run() {
if let Err(error) = Arguments::from_args().run() {
let bold = Style::new().bold();
let red = Style::new().fg(ansi_term::Color::Red).bold();
eprintln!("{}: {}", red.paint("error"), bold.paint(error.to_string()));

View File

@ -1,283 +0,0 @@
use crate::common::*;
#[derive(StructOpt)]
pub(crate) enum Opt {
#[structopt(about("Update all generated docs"))]
All,
#[structopt(about("Generate book"))]
Book,
#[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("Diff generated content between commits"))]
Diff,
#[structopt(about("Generate readme"))]
Readme,
#[structopt(about("Generate man pages"))]
Man,
}
#[throws]
fn blank(path: impl AsRef<Path>, title: &str) {
let path = path.as_ref();
info!("Writing blank page to `{}`…", path.display());
let text = format!(
"
# {}
This page intentionally left blank.
",
title
);
fs::write(&path, text).context(error::Filesystem { path })?;
}
#[throws]
fn clean_dir(path: impl AsRef<Path>) {
let path = path.as_ref();
info!("Cleaning `{}`…", path.display());
if path.is_dir() {
fs::remove_dir_all(path).context(error::Filesystem { path: &path })?;
}
fs::create_dir_all(path).context(error::Filesystem { path: &path })?;
}
impl Opt {
#[throws]
pub(crate) fn run(self) {
let project = Project::load()?;
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::Diff => Self::diff(&project)?,
Self::All => Self::all(&project)?,
}
}
#[throws]
pub(crate) fn all(project: &Project) {
Self::changelog(&project)?;
Self::completion_scripts(&project)?;
Self::readme(&project)?;
Self::book(&project)?;
Self::man(&project)?;
}
#[throws]
pub(crate) fn changelog(project: &Project) {
info!("Generating changelog…");
let changelog = Changelog::new(&project)?;
let path = project.root.join("CHANGELOG.md");
fs::write(&path, changelog.render(false)?).context(error::Filesystem { path })?;
}
#[throws]
pub(crate) fn completion_scripts(project: &Project) {
info!("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 diff(project: &Project) {
let tmp = tempfile::tempdir().context(error::Tempdir)?;
let generated = &[
"/CHANGELOG.md",
"/README.md",
"/book/src/SUMMARY.md",
"/book/src/bittorrent.md",
"/book/src/changelog.md",
"/book/src/commands.md",
"/book/src/commands/*",
"/book/src/faq.md",
"/book/src/introduction.md",
"/book/src/references.md",
"/book/src/references/*",
"/completions/*",
"/man/*",
];
let gen = |name: &str| -> Result<(), Error> {
cmd!("cargo", "run", "--package", "gen", "all").status_into_result()?;
let dir = tmp.path().join(name);
fs::create_dir(&dir).context(error::Filesystem { path: &dir })?;
let mut builder = OverrideBuilder::new(&project.root);
for pattern in generated {
builder.add(pattern)?;
}
let overrides = builder.build()?;
for result in WalkDir::new(&project.root) {
let entry = result?;
let src = entry.path();
if src.is_dir() {
continue;
}
if !overrides.matched(&src, false).is_whitelist() {
continue;
}
let relative = src
.strip_prefix(&project.root)
.context(error::StripPrefix)?;
let dst = dir.join(relative);
let dst_dir = dst.join("..").lexiclean();
fs::create_dir_all(&dst_dir).context(error::Filesystem { path: dst_dir })?;
fs::copy(&src, &dst).context(error::FilesystemCopy { src, dst })?;
}
Ok(())
};
const HEAD: &str = "HEAD";
gen(HEAD)?;
let head = project.repo.head()?;
let head_commit = head.peel_to_commit()?;
let parent = head_commit.parent(0)?;
let parent_hash = parent.id().to_string();
cmd!("git", "checkout", &parent_hash).status_into_result()?;
gen(&parent_hash)?;
cmd!("colordiff", "-ur", parent_hash, HEAD)
.current_dir(tmp.path())
.status_into_result()
.ok();
cmd!(
"git",
"checkout",
head
.shorthand()
.map(str::to_owned)
.unwrap_or_else(|| head_commit.id().to_string())
)
.status_into_result()?;
}
#[throws]
pub(crate) fn readme(project: &Project) {
info!("Generating readme…");
let template = project.root.join("bin/gen/templates/README.md");
let readme = Readme::load(&project.config, &template)?;
let text = readme.render_newline()?;
let path = project.root.join("README.md");
fs::write(&path, text).context(error::Filesystem { path })?;
}
#[throws]
pub(crate) fn book(project: &Project) {
info!("Generating book…");
blank(project.root.join("book/src/commands.md"), "Commands")?;
blank(project.root.join("book/src/bittorrent.md"), "BitTorrent")?;
blank(project.root.join("book/src/references.md"), "References")?;
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).context(error::Filesystem { path: dst })?;
}
clean_dir(&project.root.join("book/src/references/"))?;
for section in &project.config.references {
section.render_to(project.root.join("book/src").join(section.path()))?;
}
Faq::new(&project.config.faq).render_to(project.root.join("book/src/faq.md"))?;
Summary::new(project).render_to(project.root.join("book/src/SUMMARY.md"))?;
Introduction::new(&project.config).render_to(project.root.join("book/src/introduction.md"))?;
let changelog = Changelog::new(&project)?;
let dst = project.root.join("book/src/changelog.md");
fs::write(&dst, changelog.render(true)?).context(error::Filesystem { path: dst })?;
}
#[throws]
pub(crate) fn man(project: &Project) {
info!("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()));
info!("Writing man page to `{}`", dst.display());
fs::write(&dst, man).context(error::Filesystem { path: dst })?;
}
}
}

11
bin/gen/src/options.rs Normal file
View File

@ -0,0 +1,11 @@
use crate::common::*;
#[derive(StructOpt)]
pub(crate) struct Options {
#[structopt(
long("bin"),
value_name("EXECUTABLE"),
help("Path to the `imdl` binary.")
)]
pub(crate) bin: PathBuf,
}

View File

@ -5,11 +5,12 @@ pub(crate) struct Project {
pub(crate) root: PathBuf,
pub(crate) config: Config,
pub(crate) bin: Bin,
pub(crate) executable: PathBuf,
}
impl Project {
#[throws]
pub(crate) fn load() -> Self {
pub(crate) fn load(options: &Options) -> Self {
let start_dir = env::current_dir().context(error::CurrentDir)?;
let repo = Repository::discover(&start_dir).context(error::RepositoryDiscover { start_dir })?;
@ -23,7 +24,7 @@ impl Project {
let config = Config::load(&root)?;
let bin = Bin::new(&root.join("target/debug/imdl"))?;
let bin = Bin::new(&options.bin)?;
let example_commands = config
.examples
@ -45,10 +46,22 @@ impl Project {
}
Project {
executable: options.bin.clone(),
bin,
config,
repo,
root,
config,
bin,
}
}
#[throws]
pub(crate) fn gen(&self) -> PathBuf {
let gen = self.root.join("target").join("gen");
if !gen.is_dir() {
fs::create_dir_all(&gen).context(error::Filesystem { path: &gen })?;
}
gen
}
}

View File

@ -9,6 +9,6 @@ pub(crate) struct ReferenceSection {
impl ReferenceSection {
pub(crate) fn path(&self) -> String {
format!("./references/{}.md", self.title.slug())
format!("references/{}.md", self.title.slug())
}
}

View File

@ -1,164 +1,240 @@
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>,
#[derive(StructOpt)]
pub(crate) enum Subcommand {
#[structopt(about("Update all generated docs"))]
All,
#[structopt(about("Generate book"))]
Book,
#[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("Diff generated content between commits"))]
Diff,
#[structopt(about("Generate readme"))]
Readme,
#[structopt(about("Generate man pages"))]
Man,
}
#[throws]
fn blank(path: impl AsRef<Path>, title: &str) {
let path = path.as_ref();
info!("Writing blank page to `{}`…", path.display());
let text = format!(
"
# {}
This page intentionally left blank.
",
title
);
fs::write(&path, text).context(error::Filesystem { path })?;
}
#[throws]
fn clean_dir(path: impl AsRef<Path>) {
let path = path.as_ref();
info!("Cleaning `{}`…", path.display());
if path.is_dir() {
fs::remove_dir_all(path).context(error::Filesystem { path: &path })?;
}
fs::create_dir_all(path).context(error::Filesystem { path: &path })?;
}
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()?;
pub(crate) fn run(self, options: Options) {
let project = Project::load(&options)?;
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());
match self {
Self::Changelog => Self::changelog(&project)?,
Self::CommitTemplate => {
println!("{}", Metadata::default().to_string());
}
}
Self {
bin: bin.into(),
command,
subcommands,
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::Diff => Self::diff(&project)?,
Self::All => Self::all(&project)?,
}
}
#[throws]
fn help(&self) -> String {
info!("Getting help for `{}`", self.command_line());
Command::new(&self.bin)
.args(self.command.as_slice())
.env("IMDL_TERM_WIDTH", "80")
.arg("--help")
.out()?
pub(crate) fn all(project: &Project) {
Self::changelog(&project)?;
Self::completion_scripts(&project)?;
Self::readme(&project)?;
Self::book(&project)?;
Self::man(&project)?;
}
#[throws]
pub(crate) fn man(&self) -> String {
let command_line = self.command_line();
pub(crate) fn changelog(project: &Project) {
info!("Generating changelog…");
let changelog = Changelog::new(&project)?;
info!("Generating man page for `{}`", command_line);
let path = project.gen()?.join("CHANGELOG.md");
let name = command_line.replace(" ", "\\ ");
fs::write(&path, changelog.render(false)?).context(error::Filesystem { path })?;
}
let help = self.help()?;
#[throws]
pub(crate) fn completion_scripts(project: &Project) {
info!("Generating completion scripts…");
let completions = project.gen()?.join("completions");
let description = if self.command.is_empty() {
"A 40' shipping container for the Internet".to_string()
} else {
help.lines().nth(1).unwrap().into()
};
clean_dir(&completions)?;
let include = format!(
"\
[NAME]
\\fB{}\\fR
- {}
",
name, description
);
cmd!(&project.executable, "completions", "--dir", completions).status_into_result()?
}
#[throws]
pub(crate) fn diff(project: &Project) {
let tmp = tempfile::tempdir().context(error::Tempdir)?;
let include_path = tmp.path().join("include");
let gen = |name: &str| -> Result<(), Error> {
let src = Path::new("target/gen");
fs::write(&include_path, include).context(error::Filesystem {
path: &include_path,
})?;
fs::remove_dir_all(src).context(error::Filesystem { path: src })?;
let version = cmd!(&self.bin, "--version")
.out()?
.split_whitespace()
.nth(1)
.unwrap()
.to_owned();
cmd!("cargo", "run", "--package", "gen", "all").status_into_result()?;
info!("Running help2man for `{}`", command_line);
let dir = tmp.path().join(name);
let mut command = self.bin.as_os_str().to_owned();
for arg in &self.command {
command.push(" ");
command.push(arg);
}
fs::create_dir(&dir).context(error::Filesystem { path: &dir })?;
let output = cmd!(
"help2man",
"--include",
&include_path,
"--manual",
"Intermodal Manual",
"--no-info",
"--source",
&format!("Intermodal {}", version),
command
fs_extra::dir::copy(src, &dir, &fs_extra::dir::CopyOptions::new())
.context(error::FilesystemRecursiveCopy { src, dst: dir })?;
Ok(())
};
const HEAD: &str = "HEAD";
gen(HEAD)?;
let head = project.repo.head()?;
let head_commit = head.peel_to_commit()?;
let parent = head_commit.parent(0)?;
let parent_hash = parent.id().to_string();
cmd!("git", "checkout", &parent_hash).status_into_result()?;
gen(&parent_hash)?;
cmd!("colordiff", "-ur", parent_hash, HEAD)
.current_dir(tmp.path())
.status_into_result()
.ok();
cmd!(
"git",
"checkout",
head
.shorthand()
.map(str::to_owned)
.unwrap_or_else(|| head_commit.id().to_string())
)
.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
.status_into_result()?;
}
#[throws]
pub(crate) fn page(&self) -> String {
let help = self.help()?;
format!("# `{}`\n```\n{}\n```", self.command_line(), help)
pub(crate) fn readme(project: &Project) {
info!("Generating readme…");
let template = project.root.join("bin/gen/templates/README.md");
let readme = Readme::load(&project.config, &template)?;
let text = readme.render_newline()?;
let path = project.gen()?.join("README.md");
fs::write(&path, &text).context(error::Filesystem { path })?;
let path = project.root.join("README.md");
fs::write(&path, &text).context(error::Filesystem { path })?;
}
#[throws]
pub(crate) fn book(project: &Project) {
info!("Generating book…");
let gen = project.gen()?;
let out = gen.join("book");
fs::create_dir_all(&out).context(error::Filesystem { path: &out })?;
blank(out.join("commands.md"), "Commands")?;
blank(out.join("bittorrent.md"), "BitTorrent")?;
blank(out.join("references.md"), "References")?;
let commands = out.join("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).context(error::Filesystem { path: dst })?;
}
clean_dir(&out.join("references"))?;
for section in &project.config.references {
section.render_to(out.join(section.path()))?;
}
Faq::new(&project.config.faq).render_to(out.join("faq.md"))?;
Summary::new(project).render_to(out.join("SUMMARY.md"))?;
Introduction::new(&project.config).render_to(out.join("introduction.md"))?;
let changelog = Changelog::new(&project)?;
let dst = out.join("changelog.md");
fs::write(&dst, changelog.render(true)?).context(error::Filesystem { path: dst })?;
}
#[throws]
pub(crate) fn man(project: &Project) {
info!("Generating man pages…");
let mans = project.gen()?.join("man");
clean_dir(&mans)?;
for subcommand in &project.bin.subcommands {
let man = subcommand.man()?;
let dst = mans.join(format!("{}.1", subcommand.slug()));
info!("Writing man page to `{}`", dst.display());
fs::write(&dst, man).context(error::Filesystem { path: dst })?;
}
}
}

View File

@ -154,46 +154,38 @@ Intermodal is distributed under the
a public domain dedication with a fallback all-permissive license. The SPDX
identifier of the CC0 is [CC0-1.0](https://spdx.org/licenses/CC0-1.0.html).
### Package Artifacts
### Build Artifacts
There are three primary build artifacts: the binary, the man pages, and the
shell completion scripts.
There are a number of build artifacts: the binary, the man pages, the
changelog, and the shell completion scripts.
#### Binary
The binary is built with `cargo`, and the other artifacts are built `gen`,
located in `bin/gen`.
The binary is called `imdl`, and can be built with:
The binary can be built with:
```
cargo build --release
```
cargo build --release
After building, the binary will be present at `target/release/imdl`.
_`gen` requires [`help2man`](https://www.gnu.org/software/help2man/) to be
installed, which is used to generate man pages from subcommand `--help`
strings._
#### Man Pages
The rest of the build artifacts can be built with `gen`:
Intermodal has a number of subcommands, each of which has a man page. The man
pages are generated from the `--help` text using
[`help2man`](https://www.gnu.org/software/help2man/).
cargo run --package gen -- --bin target/release/imdl all
To generate the man pages, ensure `help2man` is available, and run:
_The path to the built `imdl` executable should be passed to `gen` with the `--bin` flag._
```
mkdir -p man
cargo run --package gen man
```
After running the above commands, the following table shows the location of the
built artifacts.
After building, the man pages will be available in `man`.
#### Completion Scripts
Completion scripts are available for a number of shells. To generate them, run:
```
mkdir -p completions
cargo run --release completions --dir completions
```
After running, the completion scripts will be available in `completions`.
| Artifact | Location |
|--------------------|----------------------------|
| Binary | `target/release/imdl` |
| Man Pages | `target/gen/man/*` |
| Completion Scripts | `target/gen/completions/*` |
| Changelog | `target/gen/CHANGELOG.md` |
| Readme | `target/gen/README.md` |
### Release Updates

View File

@ -6,7 +6,7 @@ version=${1#"refs/tags/"}
os=$2
target=$3
src=`pwd`
dist=$src/dist
dist=$src/target/dist
bin=imdl
echo "Packaging $bin $version for $target..."
@ -38,32 +38,35 @@ case $os in
esac
echo "Building completions..."
rm -rf completions
mkdir completions
$executable completions --dir completions
cargo run --package gen -- --bin $executable completion-scripts
echo "Generating changelog..."
cargo run --package gen changelog
cargo run --package gen -- --bin $executable changelog
echo "Copying release files..."
mkdir dist
echo "Generating readme..."
cargo run --package gen -- --bin $executable readme
echo "Copying static files..."
mkdir $dist
cp -r \
$executable \
CHANGELOG.md \
CONTRIBUTING \
Cargo.lock \
Cargo.toml \
LICENSE \
README.md \
completions \
$dist
echo "Copying generated files..."
cp -r \
target/gen/README.md \
target/gen/CHANGELOG.md \
target/gen/completions \
$dist
if [[ $os != windows-latest ]]; then
echo "Building man pages..."
rm -rf man
mkdir man
cargo run --package gen man
cp -r man $dist/man
cargo run --package gen -- --bin $executable man
cp -r target/gen/man $dist/man
fi
cd $dist

1
book/src/SUMMARY.md Symbolic link
View File

@ -0,0 +1 @@
../../target/gen/book/SUMMARY.md

1
book/src/bittorrent.md Symbolic link
View File

@ -0,0 +1 @@
../../target/gen/book/bittorrent.md

1
book/src/changelog.md Symbolic link
View File

@ -0,0 +1 @@
../../target/gen/book/changelog.md

1
book/src/commands Symbolic link
View File

@ -0,0 +1 @@
../../target/gen/book/commands

1
book/src/commands.md Symbolic link
View File

@ -0,0 +1 @@
../../target/gen/book/commands.md

1
book/src/faq.md Symbolic link
View File

@ -0,0 +1 @@
../../target/gen/book/faq.md

1
book/src/introduction.md Symbolic link
View File

@ -0,0 +1 @@
../../target/gen/book/introduction.md

1
book/src/references Symbolic link
View File

@ -0,0 +1 @@
../../target/gen/book/references

1
book/src/references.md Symbolic link
View File

@ -0,0 +1 @@
../../target/gen/book/references.md

View File

@ -58,7 +58,8 @@ dev-deps:
# update generated documentation
gen:
cargo run --package gen all
cargo build
cargo run --package gen -- --bin target/debug/imdl all
check-minimal-versions:
./bin/check-minimal-versions