1
0
mirror of https://github.com/sharkdp/bat synced 2026-07-29 18:01:43 +00:00

Move {main,app,clap_app}.rs into src/bin/

This commit is contained in:
Fahmi Akbar Wildana
2019-10-06 08:44:14 +07:00
committed by David Peter
parent f03b45f3e5
commit fc0ad4db2e
3 changed files with 0 additions and 0 deletions
+335
View File
@@ -0,0 +1,335 @@
use std::collections::HashSet;
use std::env;
use std::str::FromStr;
use atty::{self, Stream};
use crate::clap_app;
use clap::ArgMatches;
use wild;
use console::Term;
#[cfg(windows)]
use ansi_term;
use crate::assets::BAT_THEME_DEFAULT;
use crate::config::{get_args_from_config_file, get_args_from_env_var};
use crate::errors::*;
use crate::inputfile::InputFile;
use crate::line_range::{LineRange, LineRanges};
use crate::style::{OutputComponent, OutputComponents, OutputWrap};
use crate::syntax_mapping::SyntaxMapping;
use crate::util::transpose;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum PagingMode {
Always,
QuitIfOneScreen,
Never,
}
#[derive(Clone)]
pub struct Config<'a> {
/// List of files to print
pub files: Vec<InputFile<'a>>,
/// The explicitly configured language, if any
pub language: Option<&'a str>,
/// Whether or not to show/replace non-printable characters like space, tab and newline.
pub show_nonprintable: bool,
/// The character width of the terminal
pub term_width: usize,
/// The width of tab characters.
/// Currently, a value of 0 will cause tabs to be passed through without expanding them.
pub tab_width: usize,
/// Whether or not to simply loop through all input (`cat` mode)
pub loop_through: bool,
/// Whether or not the output should be colorized
pub colored_output: bool,
/// Whether or not the output terminal supports true color
pub true_color: bool,
/// Style elements (grid, line numbers, ...)
pub output_components: OutputComponents,
/// Text wrapping mode
pub output_wrap: OutputWrap,
/// Pager or STDOUT
pub paging_mode: PagingMode,
/// Specifies the lines that should be printed
pub line_ranges: LineRanges,
/// The syntax highlighting theme
pub theme: String,
/// File extension/name mappings
pub syntax_mapping: SyntaxMapping,
/// Command to start the pager
pub pager: Option<&'a str>,
/// Whether or not to use ANSI italics
pub use_italic_text: bool,
/// Lines to highlight
pub highlight_lines: Vec<usize>,
}
fn is_truecolor_terminal() -> bool {
env::var("COLORTERM")
.map(|colorterm| colorterm == "truecolor" || colorterm == "24bit")
.unwrap_or(false)
}
pub struct App {
pub matches: ArgMatches<'static>,
interactive_output: bool,
}
impl App {
pub fn new() -> Result<Self> {
#[cfg(windows)]
let _ = ansi_term::enable_ansi_support();
let interactive_output = atty::is(Stream::Stdout);
Ok(App {
matches: Self::matches(interactive_output)?,
interactive_output,
})
}
fn matches(interactive_output: bool) -> Result<ArgMatches<'static>> {
let args = if wild::args_os().nth(1) == Some("cache".into())
|| wild::args_os().any(|arg| arg == "--no-config")
{
// Skip the arguments in bats config file
wild::args_os().collect::<Vec<_>>()
} else {
let mut cli_args = wild::args_os();
// Read arguments from bats config file
let mut args = get_args_from_env_var()
.unwrap_or_else(get_args_from_config_file)
.chain_err(|| "Could not parse configuration file")?;
// Put the zero-th CLI argument (program name) first
args.insert(0, cli_args.next().unwrap());
// .. and the rest at the end
cli_args.for_each(|a| args.push(a));
args
};
Ok(clap_app::build_app(interactive_output).get_matches_from(args))
}
pub fn config(&self) -> Result<Config> {
let files = self.files();
let output_components = self.output_components()?;
let paging_mode = match self.matches.value_of("paging") {
Some("always") => PagingMode::Always,
Some("never") => PagingMode::Never,
Some("auto") | _ => {
if self.matches.occurrences_of("plain") > 1 {
// If we have -pp as an option when in auto mode, the pager should be disabled.
PagingMode::Never
} else if files.contains(&InputFile::StdIn) {
// If we are reading from stdin, only enable paging if we write to an
// interactive terminal and if we do not *read* from an interactive
// terminal.
if self.interactive_output && !atty::is(Stream::Stdin) {
PagingMode::QuitIfOneScreen
} else {
PagingMode::Never
}
} else if self.interactive_output {
PagingMode::QuitIfOneScreen
} else {
PagingMode::Never
}
}
};
let mut syntax_mapping = SyntaxMapping::new();
if let Some(values) = self.matches.values_of("map-syntax") {
for from_to in values {
let parts: Vec<_> = from_to.split(':').collect();
if parts.len() != 2 {
return Err("Invalid syntax mapping. The format of the -m/--map-syntax option is 'from:to'.".into());
}
syntax_mapping.insert(parts[0], parts[1]);
}
}
Ok(Config {
true_color: is_truecolor_terminal(),
language: self.matches.value_of("language").or_else(|| {
if self.matches.is_present("show-all") {
Some("show-nonprintable")
} else {
None
}
}),
show_nonprintable: self.matches.is_present("show-all"),
output_wrap: if !self.interactive_output {
// We don't have the tty width when piping to another program.
// There's no point in wrapping when this is the case.
OutputWrap::None
} else {
match self.matches.value_of("wrap") {
Some("character") => OutputWrap::Character,
Some("never") => OutputWrap::None,
Some("auto") | _ => {
if output_components.plain() {
OutputWrap::None
} else {
OutputWrap::Character
}
}
}
},
colored_output: match self.matches.value_of("color") {
Some("always") => true,
Some("never") => false,
Some("auto") | _ => self.interactive_output,
},
paging_mode,
term_width: self
.matches
.value_of("terminal-width")
.and_then(|w| {
if w.starts_with('+') || w.starts_with('-') {
// Treat argument as a delta to the current terminal width
w.parse().ok().map(|delta: i16| {
let old_width: u16 = Term::stdout().size().1;
let new_width: i32 = i32::from(old_width) + i32::from(delta);
if new_width <= 0 {
old_width as usize
} else {
new_width as usize
}
})
} else {
w.parse().ok()
}
})
.unwrap_or(Term::stdout().size().1 as usize),
loop_through: !(self.interactive_output
|| self.matches.value_of("color") == Some("always")
|| self.matches.value_of("decorations") == Some("always")),
files,
tab_width: self
.matches
.value_of("tabs")
.map(String::from)
.or_else(|| env::var("BAT_TABS").ok())
.and_then(|t| t.parse().ok())
.unwrap_or(
if output_components.plain() && paging_mode == PagingMode::Never {
0
} else {
4
},
),
theme: self
.matches
.value_of("theme")
.map(String::from)
.or_else(|| env::var("BAT_THEME").ok())
.unwrap_or_else(|| String::from(BAT_THEME_DEFAULT)),
line_ranges: LineRanges::from(
transpose(
self.matches
.values_of("line-range")
.map(|vs| vs.map(LineRange::from).collect()),
)?
.unwrap_or_else(|| vec![]),
),
output_components,
syntax_mapping,
pager: self.matches.value_of("pager"),
use_italic_text: match self.matches.value_of("italic-text") {
Some("always") => true,
_ => false,
},
highlight_lines: self
.matches
.values_of("highlight-line")
.and_then(|ws| ws.map(|w| w.parse().ok()).collect())
.unwrap_or_default(),
})
}
fn files(&self) -> Vec<InputFile> {
self.matches
.values_of("FILE")
.map(|values| {
values
.map(|filename| {
if filename == "-" {
InputFile::StdIn
} else {
InputFile::Ordinary(filename)
}
})
.collect()
})
.unwrap_or_else(|| vec![InputFile::StdIn])
}
fn output_components(&self) -> Result<OutputComponents> {
let matches = &self.matches;
Ok(OutputComponents(
if matches.value_of("decorations") == Some("never") {
HashSet::new()
} else if matches.is_present("number") {
[OutputComponent::Numbers].iter().cloned().collect()
} else if matches.is_present("plain") {
[OutputComponent::Plain].iter().cloned().collect()
} else {
let env_style_components: Option<Vec<OutputComponent>> =
transpose(env::var("BAT_STYLE").ok().map(|style_str| {
style_str
.split(',')
.map(|x| OutputComponent::from_str(&x))
.collect::<Result<Vec<OutputComponent>>>()
}))?;
matches
.value_of("style")
.map(|styles| {
styles
.split(',')
.map(|style| style.parse::<OutputComponent>())
.filter_map(|style| style.ok())
.collect::<Vec<_>>()
})
.or(env_style_components)
.unwrap_or_else(|| vec![OutputComponent::Full])
.into_iter()
.map(|style| style.components(self.interactive_output))
.fold(HashSet::new(), |mut acc, components| {
acc.extend(components.iter().cloned());
acc
})
},
))
}
}
+440
View File
@@ -0,0 +1,440 @@
use clap::{App as ClapApp, AppSettings, Arg, ArgGroup, SubCommand};
use std::path::Path;
pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> {
let clap_color_setting = if interactive_output {
AppSettings::ColoredHelp
} else {
AppSettings::ColorNever
};
let app = ClapApp::new(crate_name!())
.version(crate_version!())
.global_setting(clap_color_setting)
.global_setting(AppSettings::DeriveDisplayOrder)
.global_setting(AppSettings::UnifiedHelpMessage)
.global_setting(AppSettings::HidePossibleValuesInHelp)
.setting(AppSettings::ArgsNegateSubcommands)
.setting(AppSettings::AllowExternalSubcommands)
.setting(AppSettings::DisableHelpSubcommand)
.setting(AppSettings::VersionlessSubcommands)
.max_term_width(100)
.about(
"A cat(1) clone with wings.\n\n\
Use '--help' instead of '-h' to see a more detailed version of the help text.",
)
.long_about("A cat(1) clone with syntax highlighting and Git integration.")
.arg(
Arg::with_name("FILE")
.help("File(s) to print / concatenate. Use '-' for standard input.")
.long_help(
"File(s) to print / concatenate. Use a dash ('-') or no argument at all \
to read from standard input.",
)
.multiple(true)
.empty_values(false),
)
.arg(
Arg::with_name("language")
.short("l")
.long("language")
.overrides_with("language")
.help("Set the language for syntax highlighting.")
.long_help(
"Explicitly set the language for syntax highlighting. The language can be \
specified as a name (like 'C++' or 'LaTeX') or possible file extension \
(like 'cpp', 'hpp' or 'md'). Use '--list-languages' to show all supported \
language names and file extensions.",
)
.takes_value(true),
)
.arg(
Arg::with_name("list-languages")
.long("list-languages")
.short("L")
.conflicts_with("list-themes")
.help("Display all supported languages.")
.long_help("Display a list of supported languages for syntax highlighting."),
)
.arg(
Arg::with_name("map-syntax")
.short("m")
.long("map-syntax")
.multiple(true)
.takes_value(true)
.number_of_values(1)
.value_name("from:to")
.help("Map a file extension or name to an existing syntax.")
.long_help(
"Map a file extension or file name to an existing syntax. For example, \
to highlight *.conf files with the INI syntax, use '-m conf:ini'. \
To highlight files named '.myignore' with the Git Ignore syntax, use \
'-m .myignore:gitignore'.",
)
.takes_value(true),
)
.arg(
Arg::with_name("theme")
.long("theme")
.overrides_with("theme")
.takes_value(true)
.help("Set the color theme for syntax highlighting.")
.long_help(
"Set the theme for syntax highlighting. Use '--list-themes' to \
see all available themes. To set a default theme, add the \
'--theme=\"...\"' option to the configuration file or export the \
BAT_THEME environment variable (e.g.: export \
BAT_THEME=\"...\").",
),
)
.arg(
Arg::with_name("list-themes")
.long("list-themes")
.help("Display all supported highlighting themes.")
.long_help("Display a list of supported themes for syntax highlighting."),
)
.arg(
Arg::with_name("style")
.long("style")
.value_name("style-components")
// Need to turn this off for overrides_with to work as we want. See the bottom most
// example at https://docs.rs/clap/2.32.0/clap/struct.Arg.html#method.overrides_with
.use_delimiter(false)
.takes_value(true)
.overrides_with("style")
.overrides_with("plain")
.overrides_with("number")
// Cannot use clap's built in validation because we have to turn off clap's delimiters
.validator(|val| {
let mut invalid_vals = val.split(',').filter(|style| {
!&[
"auto", "full", "plain", "changes", "header", "grid", "numbers", "snip"
]
.contains(style)
});
if let Some(invalid) = invalid_vals.next() {
Err(format!("Unknown style, '{}'", invalid))
} else {
Ok(())
}
})
.help(
"Comma-separated list of style elements to display \
(*auto*, full, plain, changes, header, grid, numbers, snip).",
)
.long_help(
"Configure which elements (line numbers, file headers, grid \
borders, Git modifications, ..) to display in addition to the \
file contents. The argument is a comma-separated list of \
components to display (e.g. 'numbers,changes,grid') or a \
pre-defined style ('full'). To set a default style, add the \
'--style=\"..\"' option to the configuration file or export the \
BAT_STYLE environment variable (e.g.: export BAT_STYLE=\"..\"). \
Possible values: *auto*, full, plain, changes, header, grid, numbers, snip.",
),
)
.arg(
Arg::with_name("plain")
.overrides_with("plain")
.overrides_with("number")
.short("p")
.long("plain")
.multiple(true)
.help("Show plain style (alias for '--style=plain').")
.long_help(
"Only show plain style, no decorations. This is an alias for \
'--style=plain'. When '-p' is used twice ('-pp'), it also disables \
automatic paging (alias for '--style=plain --pager=never').",
),
)
.arg(
Arg::with_name("number")
.long("number")
.overrides_with("number")
.short("n")
.help("Show line numbers (alias for '--style=numbers').")
.long_help(
"Only show line numbers, no other decorations. This is an alias for \
'--style=numbers'",
),
)
.arg(
Arg::with_name("show-all")
.long("show-all")
.alias("show-nonprintable")
.short("A")
.conflicts_with("language")
.help("Show non-printable characters (space, tab, newline, ..).")
.long_help(
"Show non-printable characters like space, tab or newline. \
This option can also be used to print binary files. \
Use '--tabs' to control the width of the tab-placeholders."
),
)
.arg(
Arg::with_name("line-range")
.long("line-range")
.short("r")
.multiple(true)
.takes_value(true)
.number_of_values(1)
.value_name("N:M")
.help("Only print the lines from N to M.")
.long_help(
"Only print the specified range of lines for each file. \
For example:\n \
'--line-range 30:40' prints lines 30 to 40\n \
'--line-range :40' prints lines 1 to 40\n \
'--line-range 40:' prints lines 40 to the end of the file",
),
)
.arg(
Arg::with_name("highlight-line")
.long("highlight-line")
.short("H")
.takes_value(true)
.number_of_values(1)
.multiple(true)
.value_name("N")
.help("Highlight the given line.")
.long_help(
"Highlight the N-th line with a different background color",
),
)
.arg(
Arg::with_name("color")
.long("color")
.overrides_with("color")
.takes_value(true)
.value_name("when")
.possible_values(&["auto", "never", "always"])
.hide_default_value(true)
.default_value("auto")
.help("When to use colors (*auto*, never, always).")
.long_help(
"Specify when to use colored output. The automatic mode \
only enables colors if an interactive terminal is detected. \
Possible values: *auto*, never, always.",
),
)
.arg(
Arg::with_name("italic-text")
.long("italic-text")
.takes_value(true)
.value_name("when")
.possible_values(&["always", "never"])
.default_value("never")
.hide_default_value(true)
.help("Use italics in output (always, *never*)")
.long_help("Specify when to use ANSI sequences for italic text in the output. Possible values: always, *never*."),
)
.arg(
Arg::with_name("decorations")
.long("decorations")
.overrides_with("decorations")
.takes_value(true)
.value_name("when")
.possible_values(&["auto", "never", "always"])
.default_value("auto")
.hide_default_value(true)
.help("When to show the decorations (*auto*, never, always).")
.long_help(
"Specify when to use the decorations that have been specified \
via '--style'. The automatic mode only enables decorations if \
an interactive terminal is detected. Possible values: *auto*, never, always.",
),
)
.arg(
Arg::with_name("paging")
.long("paging")
.overrides_with("paging")
.takes_value(true)
.value_name("when")
.possible_values(&["auto", "never", "always"])
.default_value("auto")
.hide_default_value(true)
.help("Specify when to use the pager (*auto*, never, always).")
.long_help(
"Specify when to use the pager. To control which pager \
is used, set the PAGER or BAT_PAGER environment \
variables (the latter takes precedence) or use the '--pager' option. \
To disable the pager permanently, set BAT_PAGER to an empty string \
or set '--paging=never' in the configuration file. \
Possible values: *auto*, never, always.",
),
)
.arg(
Arg::with_name("pager")
.long("pager")
.overrides_with("pager")
.takes_value(true)
.value_name("command")
.hidden_short_help(true)
.help("Determine which pager to use.")
.long_help(
"Determine which pager is used. This option will overwrite \
the PAGER and BAT_PAGER environment variables. The default \
pager is 'less'. To disable the pager completely, use the \
'--paging' option. \
Example: '--pager \"less -RF\"'.",
),
)
.arg(
Arg::with_name("wrap")
.long("wrap")
.overrides_with("wrap")
.takes_value(true)
.value_name("mode")
.possible_values(&["auto", "never", "character"])
.default_value("auto")
.hide_default_value(true)
.help("Specify the text-wrapping mode (*auto*, never, character).")
.long_help("Specify the text-wrapping mode (*auto*, never, character). \
The '--terminal-width' option can be used in addition to \
control the output width."),
)
.arg(
Arg::with_name("tabs")
.long("tabs")
.overrides_with("tabs")
.takes_value(true)
.value_name("T")
.validator(
|t| {
t.parse::<u32>()
.map_err(|_t| "must be a number")
.map(|_t| ()) // Convert to Result<(), &str>
.map_err(|e| e.to_string())
}, // Convert to Result<(), String>
)
.help("Set the tab width to T spaces.")
.long_help(
"Set the tab width to T spaces. Use a width of 0 to pass tabs through \
directly",
),
)
.arg(
Arg::with_name("unbuffered")
.short("u")
.long("unbuffered")
.hidden_short_help(true)
.long_help(
"This option exists for POSIX-compliance reasons ('u' is for \
'unbuffered'). The output is always unbuffered - this option \
is simply ignored.",
),
)
.arg(
Arg::with_name("terminal-width")
.long("terminal-width")
.takes_value(true)
.value_name("width")
.hidden_short_help(true)
.allow_hyphen_values(true)
.validator(
|t| {
let is_offset = t.starts_with('+') || t.starts_with('-');
t.parse::<i32>()
.map_err(|_e| "must be an offset or number")
.and_then(|v| if v == 0 && !is_offset {
Err("terminal width cannot be zero".into())
} else {
Ok(())
})
.map_err(|e| e.to_string())
})
.help(
"Explicitly set the width of the terminal instead of determining it \
automatically. If prefixed with '+' or '-', the value will be treated \
as an offset to the actual terminal width. See also: '--wrap'.",
),
)
.arg(
Arg::with_name("no-config")
.long("no-config")
.hidden(true)
.help("Do not use the configuration file"),
)
.arg(
Arg::with_name("config-file")
.long("config-file")
.conflicts_with("list-languages")
.conflicts_with("list-themes")
.hidden(true)
.help("Show path to the configuration file."),
)
.arg(
Arg::with_name("config-dir")
.long("config-dir")
.hidden(true)
.help("Show bat's configuration directory."),
)
.arg(
Arg::with_name("cache-dir")
.long("cache-dir")
.hidden(true)
.help("Show bat's cache directory."),
)
.help_message("Print this help message.")
.version_message("Show version information.");
// Check if the current directory contains a file name cache. Otherwise,
// enable the 'bat cache' subcommand.
if Path::new("cache").exists() {
app
} else {
app.subcommand(
SubCommand::with_name("cache")
.about("Modify the syntax-definition and theme cache")
.arg(
Arg::with_name("build")
.long("build")
.short("b")
.help("Initialize (or update) the syntax/theme cache.")
.long_help(
"Initialize (or update) the syntax/theme cache by loading from \
the source directory (default: the configuration directory).",
),
)
.arg(
Arg::with_name("clear")
.long("clear")
.short("c")
.help("Remove the cached syntax definitions and themes."),
)
.group(
ArgGroup::with_name("cache-actions")
.args(&["build", "clear"])
.required(true),
)
.arg(
Arg::with_name("source")
.long("source")
.requires("build")
.takes_value(true)
.value_name("dir")
.help("Use a different directory to load syntaxes and themes from."),
)
.arg(
Arg::with_name("target")
.long("target")
.requires("build")
.takes_value(true)
.value_name("dir")
.help(
"Use a different directory to store the cached syntax and theme set.",
),
)
.arg(
Arg::with_name("blank")
.long("blank")
.requires("build")
.help(
"Create completely new syntax and theme sets \
(instead of appending to the default sets).",
),
),
)
}
}
+265
View File
@@ -0,0 +1,265 @@
// `error_chain!` can recurse deeply
#![recursion_limit = "1024"]
#[macro_use]
extern crate error_chain;
#[macro_use]
extern crate clap;
#[macro_use]
extern crate lazy_static;
extern crate ansi_term;
extern crate atty;
extern crate console;
extern crate content_inspector;
extern crate dirs as dirs_rs;
extern crate encoding;
extern crate git2;
extern crate shell_words;
extern crate syntect;
extern crate wild;
mod app;
mod assets;
mod clap_app;
mod config;
mod controller;
mod decorations;
mod diff;
mod dirs;
mod inputfile;
mod line_range;
mod output;
mod preprocessor;
mod printer;
mod style;
mod syntax_mapping;
mod terminal;
mod util;
use std::collections::HashSet;
use std::io;
use std::io::Write;
use std::path::Path;
use std::process;
use ansi_term::Colour::Green;
use ansi_term::Style;
use crate::app::{App, Config};
use crate::assets::{cache_dir, clear_assets, config_dir, HighlightingAssets};
use crate::config::config_file;
use crate::controller::Controller;
use crate::inputfile::InputFile;
use crate::style::{OutputComponent, OutputComponents};
mod errors {
error_chain! {
foreign_links {
Clap(::clap::Error);
Io(::std::io::Error);
SyntectError(::syntect::LoadingError);
ParseIntError(::std::num::ParseIntError);
}
}
pub fn handle_error(error: &Error) {
match error {
Error(ErrorKind::Io(ref io_error), _)
if io_error.kind() == super::io::ErrorKind::BrokenPipe =>
{
super::process::exit(0);
}
_ => {
use ansi_term::Colour::Red;
eprintln!("{}: {}", Red.paint("[bat error]"), error);
}
};
}
}
use crate::errors::*;
fn run_cache_subcommand(matches: &clap::ArgMatches) -> Result<()> {
if matches.is_present("build") {
let source_dir = matches.value_of("source").map(Path::new);
let target_dir = matches.value_of("target").map(Path::new);
let blank = matches.is_present("blank");
let assets = HighlightingAssets::from_files(source_dir, blank)?;
assets.save(target_dir)?;
} else if matches.is_present("clear") {
clear_assets();
}
Ok(())
}
pub fn list_languages(config: &Config) -> Result<()> {
let assets = HighlightingAssets::new();
let mut languages = assets
.syntax_set
.syntaxes()
.iter()
.filter(|syntax| !syntax.hidden && !syntax.file_extensions.is_empty())
.collect::<Vec<_>>();
languages.sort_by_key(|lang| lang.name.to_uppercase());
let stdout = io::stdout();
let mut stdout = stdout.lock();
if config.loop_through {
for lang in languages {
write!(stdout, "{}:{}\n", lang.name, lang.file_extensions.join(","))?;
}
} else {
let longest = languages
.iter()
.map(|syntax| syntax.name.len())
.max()
.unwrap_or(32); // Fallback width if they have no language definitions.
let comma_separator = ", ";
let separator = " ";
// Line-wrapping for the possible file extension overflow.
let desired_width = config.term_width - longest - separator.len();
let style = if config.colored_output {
Green.normal()
} else {
Style::default()
};
for lang in languages {
write!(stdout, "{:width$}{}", lang.name, separator, width = longest)?;
// Number of characters on this line so far, wrap before `desired_width`
let mut num_chars = 0;
let mut extension = lang.file_extensions.iter().peekable();
while let Some(word) = extension.next() {
// If we can't fit this word in, then create a line break and align it in.
let new_chars = word.len() + comma_separator.len();
if num_chars + new_chars >= desired_width {
num_chars = 0;
write!(stdout, "\n{:width$}{}", "", separator, width = longest)?;
}
num_chars += new_chars;
write!(stdout, "{}", style.paint(&word[..]))?;
if extension.peek().is_some() {
write!(stdout, "{}", comma_separator)?;
}
}
writeln!(stdout)?;
}
}
Ok(())
}
pub fn list_themes(cfg: &Config) -> Result<()> {
let assets = HighlightingAssets::new();
let themes = &assets.theme_set.themes;
let mut config = cfg.clone();
let mut style = HashSet::new();
style.insert(OutputComponent::Plain);
config.files = vec![InputFile::ThemePreviewFile];
config.output_components = OutputComponents(style);
let stdout = io::stdout();
let mut stdout = stdout.lock();
if config.colored_output {
for (theme, _) in themes.iter() {
writeln!(
stdout,
"Theme: {}\n",
Style::new().bold().paint(theme.to_string())
)?;
config.theme = theme.to_string();
let _controller = Controller::new(&config, &assets).run();
writeln!(stdout)?;
}
} else {
for (theme, _) in themes.iter() {
writeln!(stdout, "{}", theme)?;
}
}
Ok(())
}
fn run_controller(config: &Config) -> Result<bool> {
let assets = HighlightingAssets::new();
let controller = Controller::new(&config, &assets);
controller.run()
}
/// Returns `Err(..)` upon fatal errors. Otherwise, returns `Ok(true)` on full success and
/// `Ok(false)` if any intermediate errors occurred (were printed).
fn run() -> Result<bool> {
let app = App::new()?;
match app.matches.subcommand() {
("cache", Some(cache_matches)) => {
// If there is a file named 'cache' in the current working directory,
// arguments for subcommand 'cache' are not mandatory.
// If there are non-zero arguments, execute the subcommand cache, else, open the file cache.
if !cache_matches.args.is_empty() {
run_cache_subcommand(cache_matches)?;
Ok(true)
} else {
let mut config = app.config()?;
config.files = vec![InputFile::Ordinary(&"cache")];
run_controller(&config)
}
}
_ => {
let config = app.config()?;
if app.matches.is_present("list-languages") {
list_languages(&config)?;
Ok(true)
} else if app.matches.is_present("list-themes") {
list_themes(&config)?;
Ok(true)
} else if app.matches.is_present("config-file") {
println!("{}", config_file().to_string_lossy());
Ok(true)
} else if app.matches.is_present("config-dir") {
writeln!(io::stdout(), "{}", config_dir())?;
Ok(true)
} else if app.matches.is_present("cache-dir") {
writeln!(io::stdout(), "{}", cache_dir())?;
Ok(true)
} else {
run_controller(&config)
}
}
}
}
fn main() {
let result = run();
match result {
Err(error) => {
handle_error(&error);
process::exit(1);
}
Ok(false) => {
process::exit(1);
}
Ok(true) => {
process::exit(0);
}
}
}