feat: add no-css and no-header-css flags for #19
refactor: change to yaml configuration for the CLI refactor: change all flags to kebab case
This commit is contained in:
parent
d67169425d
commit
d1d1a0f3f4
8 changed files with 133 additions and 84 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -395,6 +395,7 @@ dependencies = [
|
|||
"textwrap",
|
||||
"unicode-width",
|
||||
"vec_map",
|
||||
"yaml-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2756,6 +2757,12 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e66366e18dc58b46801afbf2ca7661a9f59cc8c5962c29892b6039b4f86fa992"
|
||||
|
||||
[[package]]
|
||||
name = "yansi"
|
||||
version = "0.5.0"
|
||||
|
|
|
@ -15,7 +15,7 @@ readme = "README.md"
|
|||
# atty = "0.2.14"
|
||||
async-std = "1.9.0"
|
||||
chrono = "0.4.19"
|
||||
clap = "2.33.3"
|
||||
clap = {version = "2.33.3", features = ["yaml"]}
|
||||
colored = "2.0.0"
|
||||
comfy-table = "3.0.0"
|
||||
derive_builder = "0.10.2"
|
||||
|
|
7
src/assets/body.min.css
vendored
Normal file
7
src/assets/body.min.css
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
/*!
|
||||
* Writ v1.0.4
|
||||
*
|
||||
* Copyright © 2015, Curtis McEnroe <curtis@cmcenroe.me>
|
||||
*
|
||||
* https://cmcenroe.me/writ/LICENSE (ISC)
|
||||
*/dd,hr,ol ol,ol ul,ul ol,ul ul{margin:0}pre,table{overflow-x:auto}a,ins{text-decoration:none}html{font-family:Georgia,Lucida Bright,Book Antiqua,serif;font-size:16px;line-height:1.5rem}code,kbd,pre,samp{font-family:Fira Code,Liberation Mono,Menlo,Courier,monospace;font-size:.833rem;color:#111}kbd{font-weight:700}small{font-size:.833em}th{font-weight:400}blockquote,dl,ol,p,pre,table,ul{margin:1.5rem 0 0}pre,table{margin-bottom:-1px}hr{border:none;padding:1.5rem 0 0}table{line-height:calc(1.5rem - 1px);width:100%;border-collapse:collapse}pre{margin-top:calc(1.5rem - 1px)}body{color:#222;margin:1.5rem 1ch}a,a code,header nav a:visited{color:#00e}a:visited,a:visited code{color:#60b}mark{color:inherit;background-color:#fe0}code,pre,samp,tfoot,thead{background-color:rgba(0,0,0,.05)}blockquote,ins,main aside{border:rgba(0,0,0,.05) solid}blockquote,main aside{border-width:0 0 0 .5ch}code,pre,samp{border:rgba(0,0,0,.1) solid}td,th{border:solid #dbdbdb}body>header{text-align:center}body>footer,main{display:block;max-width:78ch;margin:auto}main aside,main figure{float:right;margin:1.5rem 0 0 1ch}main aside{max-width:26ch;padding:0 0 0 .5ch}blockquote{margin-right:3ch;margin-left:1.5ch;padding:0 0 0 1ch}pre{border-width:1px;border-radius:2px;padding:0 .5ch}pre code{border:none;padding:0;background-color:transparent;white-space:inherit}code,ins,samp,td,th{border-width:1px}img{max-width:100%}dd,ol,ul{padding:0 0 0 3ch}ul>li{list-style-type:disc}li ul>li{list-style-type:circle}li li ul>li{list-style-type:square}ol>li{list-style-type:decimal}li ol>li{list-style-type:lower-roman}li li ol>li{list-style-type:lower-alpha}nav ul{padding:0;list-style-type:none}nav ul li{display:inline;padding-left:1ch;white-space:nowrap}nav ul li:first-child{padding-left:0}ins,mark{padding:1px}td,th{padding:0 .5ch}sub,sup{font-size:.75em;line-height:1em}code,samp{border-radius:2px;padding:.1em .2em;white-space:nowrap}
|
7
src/assets/headers.min.css
vendored
Normal file
7
src/assets/headers.min.css
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
/*!
|
||||
* Writ v1.0.4
|
||||
*
|
||||
* Copyright © 2015, Curtis McEnroe <curtis@cmcenroe.me>
|
||||
*
|
||||
* https://cmcenroe.me/writ/LICENSE (ISC)
|
||||
*/h1,h2,h3,h4,h5,h6,th{font-weight:400}h1{font-size:2.488em}h2{font-size:2.074em}h3{font-size:1.728em}h4{font-size:1.44em}h5{font-size:1.2em}h6{font-size:1em}h1,h2,h3{line-height:3rem}h1,h2,h3,h4,h5,h6{margin:1.5rem 0 0}
|
7
src/assets/writ.min.css
vendored
7
src/assets/writ.min.css
vendored
|
@ -1,7 +0,0 @@
|
|||
/*!
|
||||
* Writ v1.0.4
|
||||
*
|
||||
* Copyright © 2015, Curtis McEnroe <curtis@cmcenroe.me>
|
||||
*
|
||||
* https://cmcenroe.me/writ/LICENSE (ISC)
|
||||
*/dd,hr,ol ol,ol ul,ul ol,ul ul{margin:0}pre,table{overflow-x:auto}a,ins{text-decoration:none}html{font-family:Georgia,Lucida Bright,Book Antiqua,serif;font-size:16px;line-height:1.5rem}code,kbd,pre,samp{font-family:Fira Code,Liberation Mono,Menlo,Courier,monospace;font-size:.833rem;color:#111}kbd{font-weight:700}h1,h2,h3,h4,h5,h6,th{font-weight:400}h1{font-size:2.488em}h2{font-size:2.074em}h3{font-size:1.728em}h4{font-size:1.44em}h5{font-size:1.2em}h6{font-size:1em}small{font-size:.833em}h1,h2,h3{line-height:3rem}blockquote,dl,h1,h2,h3,h4,h5,h6,ol,p,pre,table,ul{margin:1.5rem 0 0}pre,table{margin-bottom:-1px}hr{border:none;padding:1.5rem 0 0}table{line-height:calc(1.5rem - 1px);width:100%;border-collapse:collapse}pre{margin-top:calc(1.5rem - 1px)}body{color:#222;margin:1.5rem 1ch}a,a code,header nav a:visited{color:#00e}a:visited,a:visited code{color:#60b}mark{color:inherit;background-color:#fe0}code,pre,samp,tfoot,thead{background-color:rgba(0,0,0,.05)}blockquote,ins,main aside{border:rgba(0,0,0,.05) solid}blockquote,main aside{border-width:0 0 0 .5ch}code,pre,samp{border:rgba(0,0,0,.1) solid}td,th{border:solid #dbdbdb}body>header{text-align:center}body>footer,main{display:block;max-width:78ch;margin:auto}main aside,main figure{float:right;margin:1.5rem 0 0 1ch}main aside{max-width:26ch;padding:0 0 0 .5ch}blockquote{margin-right:3ch;margin-left:1.5ch;padding:0 0 0 1ch}pre{border-width:1px;border-radius:2px;padding:0 .5ch}pre code{border:none;padding:0;background-color:transparent;white-space:inherit}code,ins,samp,td,th{border-width:1px}img{max-width:100%}dd,ol,ul{padding:0 0 0 3ch}ul>li{list-style-type:disc}li ul>li{list-style-type:circle}li li ul>li{list-style-type:square}ol>li{list-style-type:decimal}li ol>li{list-style-type:lower-roman}li li ol>li{list-style-type:lower-alpha}nav ul{padding:0;list-style-type:none}nav ul li{display:inline;padding-left:1ch;white-space:nowrap}nav ul li:first-child{padding-left:0}ins,mark{padding:1px}td,th{padding:0 .5ch}sub,sup{font-size:.75em;line-height:1em}code,samp{border-radius:2px;padding:.1em .2em;white-space:nowrap}
|
94
src/cli.rs
94
src/cli.rs
|
@ -1,7 +1,7 @@
|
|||
use std::{fs, num::NonZeroUsize, path::Path};
|
||||
|
||||
use chrono::{DateTime, Local};
|
||||
use clap::{App, AppSettings, Arg, ArgMatches};
|
||||
use clap::{load_yaml, App, ArgMatches};
|
||||
use flexi_logger::LevelFilter as LogLevel;
|
||||
use itertools::Itertools;
|
||||
|
||||
|
@ -22,80 +22,13 @@ pub struct AppConfig {
|
|||
pub start_time: DateTime<Local>,
|
||||
pub is_logging_to_file: bool,
|
||||
pub inline_toc: bool,
|
||||
pub css_config: CSSConfig,
|
||||
}
|
||||
|
||||
impl AppConfig {
|
||||
pub fn init_with_cli() -> Result<AppConfig, Error> {
|
||||
let app = App::new("paperoni")
|
||||
.settings(&[
|
||||
AppSettings::ArgRequiredElseHelp,
|
||||
AppSettings::UnifiedHelpMessage,
|
||||
])
|
||||
.version(clap::crate_version!())
|
||||
.about(
|
||||
"Paperoni is a CLI tool made in Rust for downloading web articles as EPUBs",
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("urls")
|
||||
.help("Urls of web articles")
|
||||
.multiple(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("file")
|
||||
.short("f")
|
||||
.long("file")
|
||||
.help("Input file containing links")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("output_directory")
|
||||
.long("output-dir")
|
||||
.short("o")
|
||||
.help("Directory to store output epub documents")
|
||||
.conflicts_with("output_name")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("output_name")
|
||||
.long("merge")
|
||||
.help("Merge multiple articles into a single epub")
|
||||
.long_help("Merge multiple articles into a single epub that will be given the name provided")
|
||||
.conflicts_with("output_directory")
|
||||
.takes_value(true),
|
||||
).arg(
|
||||
Arg::with_name("max-conn")
|
||||
.long("max_conn")
|
||||
.help("The maximum number of concurrent HTTP connections when downloading articles. Default is 8")
|
||||
.long_help("The maximum number of concurrent HTTP connections when downloading articles. Default is 8.\nNOTE: It is advised to use as few connections as needed i.e between 1 and 50. Using more connections can end up overloading your network card with too many concurrent requests.")
|
||||
.takes_value(true))
|
||||
.arg(
|
||||
Arg::with_name("verbosity")
|
||||
.short("v")
|
||||
.multiple(true)
|
||||
.help("Enables logging of events and set the verbosity level. Use --help to read on its usage")
|
||||
.long_help(
|
||||
"This takes upto 4 levels of verbosity in the following order.
|
||||
- Error (-v)
|
||||
- Warn (-vv)
|
||||
- Info (-vvv)
|
||||
- Debug (-vvvv)
|
||||
When this flag is passed, it disables the progress bars and logs to stderr.
|
||||
If you would like to send the logs to a file (and enable progress bars), pass the log-to-file flag."
|
||||
)
|
||||
.takes_value(false))
|
||||
.arg(
|
||||
Arg::with_name("log-to-file")
|
||||
.long("log-to-file")
|
||||
.help("Enables logging of events to a file located in .paperoni/logs with a default log level of debug. Use -v to specify the logging level")
|
||||
.takes_value(false))
|
||||
.arg(
|
||||
Arg::with_name("inline-toc")
|
||||
.long("inline-toc")
|
||||
.requires("output_name")
|
||||
.help("Add an inlined Table of Contents page at the start of the merged article.")
|
||||
.long_help("Add an inlined Table of Contents page at the start of the merged article. This does not affect the Table of Contents navigation")
|
||||
);
|
||||
|
||||
let yaml_config = load_yaml!("cli_config.yml");
|
||||
let app = App::from_yaml(yaml_config).version(clap::crate_version!());
|
||||
Self::try_from(app.get_matches())
|
||||
}
|
||||
|
||||
|
@ -159,7 +92,7 @@ impl<'a> TryFrom<ArgMatches<'a>> for AppConfig {
|
|||
Some(max_conn) => max_conn.parse::<NonZeroUsize>()?.get(),
|
||||
None => DEFAULT_MAX_CONN,
|
||||
})
|
||||
.merged(arg_matches.value_of("output_name").map(|name| {
|
||||
.merged(arg_matches.value_of("output-name").map(|name| {
|
||||
if name.ends_with(".epub") {
|
||||
name.to_owned()
|
||||
} else {
|
||||
|
@ -200,6 +133,16 @@ impl<'a> TryFrom<ArgMatches<'a>> for AppConfig {
|
|||
.transpose()?,
|
||||
)
|
||||
.start_time(Local::now())
|
||||
.css_config(
|
||||
match (
|
||||
arg_matches.is_present("no-css"),
|
||||
arg_matches.is_present("no-header-css"),
|
||||
) {
|
||||
(true, _) => CSSConfig::None,
|
||||
(_, true) => CSSConfig::NoHeaders,
|
||||
_ => CSSConfig::All,
|
||||
},
|
||||
)
|
||||
.try_init()
|
||||
}
|
||||
}
|
||||
|
@ -212,3 +155,10 @@ impl AppConfigBuilder {
|
|||
.init_merge_file()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum CSSConfig {
|
||||
All,
|
||||
NoHeaders,
|
||||
None,
|
||||
}
|
||||
|
|
66
src/cli_config.yml
Normal file
66
src/cli_config.yml
Normal file
|
@ -0,0 +1,66 @@
|
|||
name: paperoni
|
||||
about: Paperoni is a CLI tool made in Rust for downloading web articles as EPUBs
|
||||
settings:
|
||||
- ArgRequiredElseHelp
|
||||
- UnifiedHelpMessage
|
||||
args:
|
||||
- urls:
|
||||
help: Urls of web articles
|
||||
multiple: true
|
||||
- file:
|
||||
short: f
|
||||
long: file
|
||||
help: Input file containing links
|
||||
takes_value: true
|
||||
- output_directory:
|
||||
short: o
|
||||
long: output-dir
|
||||
help: Directory to store output epub documents
|
||||
conflicts_with: output-name
|
||||
takes_value: true
|
||||
- output-name:
|
||||
long: merge
|
||||
help: Merge multiple articles into a single epub
|
||||
long_help: Merge multiple articles into a single epub that will be given the name provided
|
||||
conflicts_with: output_directory
|
||||
takes_value: true
|
||||
- max-conn:
|
||||
long: max-conn
|
||||
help: The maximum number of concurrent HTTP connections when downloading articles. Default is 8
|
||||
long_help: "The maximum number of concurrent HTTP connections when downloading articles. Default is 8.\nNOTE: It is advised to use as few connections as needed i.e between 1 and 50. Using more connections can end up overloading your network card with too many concurrent requests."
|
||||
takes_value: true
|
||||
- verbosity:
|
||||
short: v
|
||||
multiple: true
|
||||
help: Enables logging of events and set the verbosity level. Use --help to read on its usage
|
||||
long_help: "This takes upto 4 levels of verbosity in the following order.
|
||||
\n- Error (-v)
|
||||
\n- Warn (-vv)
|
||||
\n- Info (-vvv)
|
||||
\n- Debug (-vvvv)
|
||||
\nWhen this flag is passed, it disables the progress bars and logs to stderr.
|
||||
\nIf you would like to send the logs to a file (and enable progress bars), pass the log-to-file flag."
|
||||
takes_value: false
|
||||
- log-to-file:
|
||||
long: log-to-file
|
||||
help: Enables logging of events to a file located in .paperoni/logs with a default log level of debug. Use -v to specify the logging level
|
||||
takes_value: false
|
||||
- inline-toc:
|
||||
long: inline-toc
|
||||
requires: output-name
|
||||
help: Add an inlined Table of Contents page at the start of the merged article.
|
||||
long_help: Add an inlined Table of Contents page at the start of the merged article. This does not affect the Table of Contents navigation"
|
||||
- no-css:
|
||||
long: no-css
|
||||
conflicts_with: no-header-css
|
||||
help: Removes the stylesheets used in the EPUB generation. Pass --help to learn more
|
||||
long_help: "Removes the stylesheets used in the EPUB generation.
|
||||
\nThe EPUB file will then be laid out based on your e-reader's default stylesheets.
|
||||
\nImages and code blocks may overflow when this flag is set and layout of generated
|
||||
\nPDFs will be affected. Use --no-header-css if you want to only disable the styling on headers."
|
||||
takes_value: false
|
||||
- no-header-css:
|
||||
long: no-header-css
|
||||
conflicts_with: no-css
|
||||
help: Removes the header CSS styling but preserves styling of images and codeblocks. To remove all the default CSS, use --no-css instead.
|
||||
takes_value: false
|
27
src/epub.rs
27
src/epub.rs
|
@ -38,7 +38,8 @@ pub fn generate_epubs(
|
|||
enabled_bar
|
||||
};
|
||||
|
||||
let stylesheet = include_bytes!("./assets/writ.min.css");
|
||||
let body_stylesheet = include_bytes!("./assets/body.min.css");
|
||||
let header_stylesheet = include_bytes!("./assets/headers.min.css");
|
||||
|
||||
let mut errors: Vec<PaperoniError> = Vec::new();
|
||||
|
||||
|
@ -72,7 +73,7 @@ pub fn generate_epubs(
|
|||
epub.inline_toc();
|
||||
}
|
||||
|
||||
match epub.stylesheet(stylesheet.as_bytes()) {
|
||||
match add_stylesheets(&mut epub, body_stylesheet, header_stylesheet, app_config) {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
error!("Unable to add stylesheets to epub file");
|
||||
|
@ -188,8 +189,7 @@ pub fn generate_epubs(
|
|||
epub.metadata("author", replace_escaped_characters(author))?;
|
||||
}
|
||||
|
||||
epub.stylesheet(stylesheet.as_bytes())?;
|
||||
|
||||
add_stylesheets(&mut epub, body_stylesheet, header_stylesheet, app_config)?;
|
||||
let title = replace_escaped_characters(article.metadata().title());
|
||||
epub.metadata("title", &title)?;
|
||||
|
||||
|
@ -250,6 +250,25 @@ fn replace_escaped_characters(value: &str) -> String {
|
|||
.replace(">", ">")
|
||||
}
|
||||
|
||||
fn add_stylesheets<T: epub_builder::Zip>(
|
||||
epub: &mut EpubBuilder<T>,
|
||||
body_stylesheet: &[u8],
|
||||
header_stylesheet: &[u8],
|
||||
app_config: &AppConfig,
|
||||
) -> Result<(), epub_builder::Error> {
|
||||
match app_config.css_config {
|
||||
crate::cli::CSSConfig::All => {
|
||||
epub.stylesheet([header_stylesheet, body_stylesheet].concat().as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
crate::cli::CSSConfig::NoHeaders => {
|
||||
epub.stylesheet(body_stylesheet.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: The type signature of the argument should change as it requires that merged articles create an entirely new Vec of references
|
||||
fn generate_appendix(articles: Vec<&Extractor>) -> String {
|
||||
let link_tags: String = articles
|
||||
|
|
Reference in a new issue