-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add ManSection enum, own title line struct and other minor updates #14
Changes from 12 commits
8ff1a40
0fb32f2
d485bbf
5d928a4
749f443
c300e98
19d169a
251d935
3d3515f
6bb464d
9a5ca7f
d591ca8
d4a176a
5401897
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
name: pipeline | ||
|
||
on: [push, pull_request] | ||
|
||
jobs: | ||
check: | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- uses: actions/checkout@v2 | ||
|
||
- uses: actions-rs/toolchain@v1 | ||
with: | ||
profile: minimal | ||
toolchain: stable | ||
override: true | ||
components: rustfmt, clippy | ||
|
||
- uses: Swatinem/rust-cache@v1 | ||
|
||
- name: build | ||
uses: actions-rs/cargo@v1 | ||
with: | ||
command: test | ||
args: --no-run | ||
|
||
- name: test | ||
uses: actions-rs/cargo@v1 | ||
with: | ||
command: test | ||
args: -- --nocapture --quiet | ||
|
||
- name: examples | ||
uses: actions-rs/cargo@v1 | ||
with: | ||
command: test | ||
args: --examples -- --nocapture --quiet | ||
|
||
- name: formatting | ||
uses: actions-rs/cargo@v1 | ||
with: | ||
command: fmt | ||
args: --all -- --check | ||
|
||
- name: check | ||
uses: actions-rs/cargo@v1 | ||
with: | ||
command: check | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,16 @@ | ||
target | ||
### Created by https://www.gitignore.io | ||
### Rust ### | ||
# Generated by Cargo | ||
# will have compiled files and executables | ||
debug/ | ||
target/ | ||
|
||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries | ||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html | ||
Cargo.lock | ||
|
||
# These are backup files generated by rustfmt | ||
**/*.rs.bk | ||
|
||
# MSVC Windows builds of rustc generate these, which store debugging information | ||
*.pdb | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,7 +29,3 @@ pre-release-replacements = [ | |
] | ||
|
||
[dependencies] | ||
|
||
[dev-dependencies] | ||
pretty_assertions = "1.0.0" | ||
duct = "0.13" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
use roff::*; | ||
|
||
// View this example by running `cargo run --example demo | man -l -`. | ||
|
||
fn main() { | ||
let page = Roff::new("corrupt", ManSection::Executable) | ||
.date("2021-12-25") | ||
.manual("General Commands Manual") | ||
.source("corrupt v1") | ||
.section( | ||
"name", | ||
&["corrupt - modify files by randomly changing bits"], | ||
) | ||
.section( | ||
"SYNOPSIS", | ||
&[ | ||
bold("corrupt"), | ||
" ".into(), | ||
"[".into(), | ||
bold("-n"), | ||
" ".into(), | ||
italic("BITS"), | ||
"]".into(), | ||
" ".into(), | ||
"[".into(), | ||
bold("--bits"), | ||
" ".into(), | ||
italic("BITS"), | ||
"]".into(), | ||
" ".into(), | ||
italic("file"), | ||
"...".into(), | ||
], | ||
) | ||
.section( | ||
"description", | ||
&[ | ||
bold("corrupt"), | ||
" modifies files by toggling a randomly chosen bit.".into(), | ||
], | ||
) | ||
.section( | ||
"options", | ||
&[list( | ||
&[ | ||
bold("-n"), | ||
", ".into(), | ||
bold("--bits"), | ||
"=".into(), | ||
italic("BITS"), | ||
], | ||
&["Set the number of bits to modify. ", "Default is one bit."], | ||
)], | ||
); | ||
|
||
println!("{}", page.render()); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,28 +1,123 @@ | ||
use std::fmt::Write; | ||
|
||
#[derive(PartialEq, Eq)] | ||
pub struct Roff { | ||
/// Title line for a manpage. | ||
pub struct Title { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should at least implement There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point! |
||
title: String, | ||
section: i8, | ||
section: ManSection, | ||
date: Option<String>, | ||
source: Option<String>, | ||
manual: Option<String>, | ||
} | ||
Comment on lines
+4
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How much of this is roff vs man policy? |
||
|
||
impl Title { | ||
pub fn new(title: &str, section: ManSection) -> Self { | ||
Title { | ||
title: title.into(), | ||
section, | ||
date: None, | ||
source: None, | ||
manual: None, | ||
} | ||
} | ||
} | ||
|
||
impl Troffable for Title { | ||
fn render(&self) -> String { | ||
let manual = self.manual.as_deref().unwrap_or_default(); | ||
let date = self.date.as_deref().unwrap_or_default(); | ||
let source = self.source.as_deref().unwrap_or_default(); | ||
|
||
format!( | ||
r#".TH "{}" "{}" "{}" "{}" "{}""#, | ||
self.title.to_uppercase(), | ||
self.section.value(), | ||
date, | ||
source, | ||
manual | ||
) | ||
} | ||
} | ||
|
||
/// Manpage sections. | ||
/// | ||
/// The most common is [`ManSection::Executable`], and is the recommended default. | ||
#[derive(Clone, Copy)] | ||
pub enum ManSection { | ||
/// Executable programs or shell commands | ||
Executable, | ||
/// System calls (functions provided by the kernel) | ||
SystemCalls, | ||
/// Library calls (functions within program libraries) | ||
LibraryCalls, | ||
/// Special files (usually found in /dev) | ||
SpecialFiles, | ||
/// File formats and conventions, e.g. /etc/passwd | ||
FileFormats, | ||
/// Games | ||
Games, | ||
/// Miscellaneous (including macro packages and conventions), e.g. man(7), groff(7) | ||
Miscellaneous, | ||
/// System administration commands (usually only for root) | ||
SystemAdministrationCommands, | ||
/// Kernel routines [Non standard] | ||
KernelRoutines, | ||
} | ||
|
||
Comment on lines
+41
to
+65
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unless I'm missing something, this seems like man policy when this crate is focused on the underlying markup. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
My thought is to prefer to keep this crate man agnostic unless its getting in the way of getting It looks like we could have the Section enum and the title formatting external to this crate and lose nothing but I'm not as knee deep in both crates as you are, so I defer to your judgement. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Due to the current architecture this crate cannot be used to create anything besides manpages, so it is in a sense already specific to manpages and not agnostic. I'm starting to lean more towards having a more intermediate crate specifically for creating manpages, I have a prototype laying around that I could revive. Thoughts? |
||
impl ManSection { | ||
pub fn value(&self) -> i8 { | ||
match self { | ||
ManSection::Executable => 1, | ||
ManSection::SystemCalls => 2, | ||
ManSection::LibraryCalls => 3, | ||
ManSection::SpecialFiles => 4, | ||
ManSection::FileFormats => 5, | ||
ManSection::Games => 6, | ||
ManSection::Miscellaneous => 7, | ||
ManSection::SystemAdministrationCommands => 8, | ||
ManSection::KernelRoutines => 9, | ||
} | ||
} | ||
} | ||
|
||
pub struct Roff { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will fix! |
||
title: Title, | ||
content: Vec<Section>, | ||
} | ||
|
||
impl Roff { | ||
pub fn new(title: &str, section: i8) -> Self { | ||
pub fn new(title: &str, section: ManSection) -> Self { | ||
Roff { | ||
title: title.into(), | ||
section, | ||
title: Title::new(title, section), | ||
content: Vec::new(), | ||
} | ||
} | ||
|
||
/// Date of the last nontrivial change to the manpage. Should be formatted | ||
/// in `YYYY-MM-DD`. | ||
pub fn date(mut self, date: impl Into<String>) -> Self { | ||
self.title.date = Some(date.into()); | ||
self | ||
} | ||
Comment on lines
+95
to
+100
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we accept There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's probably a better way of doing it, good point. Not sure I'd like to pull in a full library for date changes here, we might do it in |
||
|
||
/// The source of the command, function or system call. | ||
pub fn source(mut self, source: impl Into<String>) -> Self { | ||
self.title.source = Some(source.into()); | ||
self | ||
} | ||
|
||
/// The title of the manual. | ||
pub fn manual(mut self, manual: impl Into<String>) -> Self { | ||
self.title.manual = Some(manual.into()); | ||
self | ||
} | ||
|
||
pub fn section<'a, C, I>(mut self, title: &str, content: I) -> Self | ||
where | ||
I: IntoIterator<Item = &'a C>, | ||
C: Troffable + 'a, | ||
{ | ||
let title = title.into(); | ||
let content = content.into_iter().map(|x| x.render()).collect(); | ||
let content = content.into_iter().map(Troffable::render).collect(); | ||
|
||
self.content.push(Section { title, content }); | ||
self | ||
|
@@ -33,13 +128,18 @@ impl Troffable for Roff { | |
fn render(&self) -> String { | ||
let mut res = String::new(); | ||
|
||
writeln!( | ||
&mut res, | ||
".TH {} {}", | ||
self.title.to_uppercase(), | ||
self.section | ||
) | ||
.unwrap(); | ||
writeln!(&mut res, "{}", self.title.render()).unwrap(); | ||
|
||
// Compatibility settings: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this needed? What requires turning of justification and hyphenation? I'm looking at this from a manual page generation point of view, and non-justified manual pages are unusual, and thus jarring to read. I could live with it, but I'd like the comment to explain what requires it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On the topic of compat settings, asciidoctor does https://github.com/asciidoctor/asciidoctor/blob/main/lib/asciidoctor/converter/manpage.rb#L61 (with links out for information) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The point about plain apostrophes is a good one, and I should add that to my roff crate version. However, I can't see any explanation of why hyphenation and justification should be turned off. The sentence space size I can live with: it removes the need to make sure sentences end with a newline after the terminating punctuation. |
||
// | ||
// Set sentence_space_size to 0 to prevent extra space between sentences separated | ||
// by a newline the alternative is to add \& at the end of the line | ||
writeln!(&mut res, ".ss \\n[.ss] 0").unwrap(); | ||
// Disable hyphenation | ||
writeln!(&mut res, ".nh").unwrap(); | ||
// Disable justification (adjust text to the left margin only) | ||
writeln!(&mut res, ".ad l").unwrap(); | ||
|
||
epage marked this conversation as resolved.
Show resolved
Hide resolved
|
||
for section in &self.content { | ||
writeln!(&mut res, "{}", escape(§ion.render())).unwrap(); | ||
} | ||
|
@@ -48,7 +148,6 @@ impl Troffable for Roff { | |
} | ||
} | ||
|
||
#[derive(PartialEq, Eq)] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How come you've been dropping There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't really know why it's derived, I can't imagine it was used for something besides testing (the first full commit has them derived but not used) because comparing manpages for equality sounds very niche. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I lean towards keeping them (slightly less important than |
||
struct Section { | ||
title: String, | ||
content: String, | ||
|
@@ -58,7 +157,7 @@ impl Troffable for Section { | |
fn render(&self) -> String { | ||
let mut res = String::new(); | ||
|
||
writeln!(&mut res, ".SH {}", self.title.to_uppercase()).unwrap(); | ||
writeln!(&mut res, ".SH \"{}\"", self.title.to_uppercase()).unwrap(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will fail if the title contains quote characters. I'm not sure how to fix that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oddly, I'm not seeing any quote escaping in asciidoctor, only left/right quotes. I wonder if that is an artifact of them only using left/right quotes There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For some more information on escaping, check out https://www.ibm.com/docs/en/aix/7.2?topic=t-troff-command (search for mm Strings) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've been reading the groff(7), and I can't see any way to escape quotes in control lines or macro arguments. |
||
res.push_str(&self.content); | ||
|
||
res | ||
|
@@ -101,10 +200,10 @@ pub fn italic(input: &str) -> String { | |
format!(r"\fI{}\fP", input) | ||
} | ||
|
||
pub fn list<C1: Troffable, C2: Troffable>(header: &'_ [C1], content: &'_ [C2]) -> String { | ||
pub fn list<C1: Troffable, C2: Troffable>(header: &[C1], content: &[C2]) -> String { | ||
format!(".TP\n{}\n{}", header.render(), content.render()) | ||
} | ||
|
||
fn escape(input: &str) -> String { | ||
pub fn escape(input: &str) -> String { | ||
input.replace("-", r"\-") | ||
} | ||
Comment on lines
+207
to
209
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was an oversight on my part, I forgot that everything is escaped when rendered, I'll fix this. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How come you are changing the
.gitignore
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Force of habit, it carried over from my previous work on
roff-rs
, I can revert this if you want to.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please do so. imo person/machine specific ignores should be set in the personal ignores rather than checked into the shared ignores.