-
-
Notifications
You must be signed in to change notification settings - Fork 88
/
main.rs
237 lines (201 loc) · 7.87 KB
/
main.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
mod color;
mod error;
mod filetypes;
mod theme;
mod types;
mod util;
use rust_embed::RustEmbed;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::process;
use std::{env, fs};
use clap::{
crate_description, crate_name, crate_version, App, AppSettings, Arg, ArgMatches, SubCommand,
};
use crate::color::ColorMode;
use crate::error::{Result, VividError};
use crate::filetypes::FileTypes;
use crate::theme::Theme;
#[derive(RustEmbed)]
#[folder = "themes/"]
struct ThemeAssets;
const THEME_PATH_SYSTEM: &str = "/usr/share/vivid/themes/";
fn get_user_config_path() -> PathBuf {
#[cfg(target_os = "macos")]
let config_dir_op = env::var_os("XDG_CONFIG_HOME")
.map(PathBuf::from)
.filter(|p| p.is_absolute())
.or_else(|| dirs::home_dir().map(|d| d.join(".config")));
#[cfg(not(target_os = "macos"))]
let config_dir_op = dirs::config_dir();
config_dir_op
.map(|d| d.join("vivid"))
.expect("Could not get home directory")
}
fn load_filetypes_database(matches: &ArgMatches, user_config_path: &PathBuf) -> Result<FileTypes> {
let database_path_from_arg = matches.value_of("database").map(Path::new);
let mut database_path_user = user_config_path.clone();
database_path_user.push("filetypes.yml");
let database_path_env_s = env::var("VIVID_DATABASE").ok();
let database_path_env = database_path_env_s.as_ref().map(Path::new);
let database_path_system = Path::new("/usr/share/vivid/filetypes.yml");
let database_path = database_path_from_arg
.or(database_path_env)
.or_else(|| util::get_first_existing_path(&[&database_path_user, database_path_system]));
// If there is a specified database file and it exists, use it.
// Otherwise, use the embedded file.
match database_path {
Some(path) => FileTypes::from_path(path),
None => FileTypes::from_embedded(),
}
}
fn available_theme_names(user_config_path: &PathBuf) -> Result<Vec<String>> {
let theme_path_user = user_config_path.clone().join("themes");
let theme_path_system = PathBuf::from(THEME_PATH_SYSTEM);
let theme_paths = util::get_all_existing_paths(&[&theme_path_user, &theme_path_system]);
// build from default themes first
let mut available_themes: Vec<String> = ThemeAssets::iter()
.map(|theme_name| theme_name.trim_end_matches(".yml").to_owned())
.collect::<Vec<_>>();
for path in theme_paths {
let dir = fs::read_dir(path).map_err(VividError::IoError)?;
for theme_file in dir {
let theme_name = theme_file
.map_err(VividError::IoError)?
.file_name()
.into_string()
.map_err(|n| {
VividError::InvalidFileName(n.as_os_str().to_string_lossy().into_owned())
})?;
available_themes.push(theme_name.trim_end_matches(".yml").to_owned());
}
}
available_themes.sort();
available_themes.dedup();
Ok(available_themes)
}
fn load_theme(
sub_matches: &ArgMatches,
user_config_path: &PathBuf,
color_mode: ColorMode,
) -> Result<Theme> {
let theme_from_env = env::var("VIVID_THEME").ok();
let theme = sub_matches
.value_of("theme")
.or_else(|| theme_from_env.as_deref())
// Convert option to result, then unwrap value or return error if None
.ok_or_else(|| VividError::NoThemeProvided)?;
let theme_as_path = Path::new(theme);
let theme_file = format!("{}.yml", theme);
let mut theme_path_user = user_config_path.clone();
theme_path_user.push("themes");
theme_path_user.push(theme_file.clone());
let mut theme_path_system = PathBuf::new();
theme_path_system.push(THEME_PATH_SYSTEM);
theme_path_system.push(&theme_file);
let theme_path =
util::get_first_existing_path(&[&theme_as_path, &theme_path_user, &theme_path_system]);
match theme_path {
Some(path) => return Theme::from_path(path, color_mode),
None => {
if let Some(embedded_file) = ThemeAssets::get(&theme_file) {
if let Ok(embedded_data) = std::str::from_utf8(&embedded_file.data) {
return Theme::from_string(embedded_data, color_mode);
}
}
}
}
Err(VividError::CouldNotFindTheme(theme.to_string()))
}
fn run() -> Result<()> {
let app = App::new(crate_name!())
.version(crate_version!())
.about(crate_description!())
.global_setting(AppSettings::ColorAuto)
.global_setting(AppSettings::ColoredHelp)
.global_setting(AppSettings::DeriveDisplayOrder)
.global_setting(AppSettings::UnifiedHelpMessage)
.setting(AppSettings::SubcommandRequired)
.setting(AppSettings::InferSubcommands)
.setting(AppSettings::VersionlessSubcommands)
.max_term_width(100)
.arg(
Arg::with_name("color-mode")
.long("color-mode")
.short("m")
.takes_value(true)
.value_name("mode")
.possible_values(&["8-bit", "24-bit"])
.default_value("24-bit")
.help("Type of ANSI colors to be used"),
)
.arg(
Arg::with_name("database")
.long("database")
.short("d")
.takes_value(true)
.value_name("path")
.help("Path to filetypes database (filetypes.yml)"),
)
.subcommand(
SubCommand::with_name("generate")
.about("Generate a LS_COLORS expression")
.arg(Arg::with_name("theme").help("Name of the color theme")),
)
.subcommand(
SubCommand::with_name("preview")
.about("Preview a given theme")
.arg(Arg::with_name("theme").help("Name of the color theme")),
)
.subcommand(SubCommand::with_name("themes").about("Prints list of available themes"));
let matches = app.get_matches();
let color_mode = match matches.value_of("color-mode") {
Some("8-bit") => ColorMode::BitDepth8,
_ => ColorMode::BitDepth24,
};
let user_config_path = get_user_config_path();
let filetypes = load_filetypes_database(&matches, &user_config_path)?;
let stdout = io::stdout();
let mut stdout_lock = stdout.lock();
if let Some(sub_matches) = matches.subcommand_matches("generate") {
let theme = load_theme(&sub_matches, &user_config_path, color_mode)?;
let mut filetypes_list = filetypes.mapping.keys().collect::<Vec<_>>();
filetypes_list.sort_unstable_by_key(|entry| entry.len());
let mut ls_colors: Vec<String> = vec![];
for filetype in filetypes_list {
let category = &filetypes.mapping[filetype];
ls_colors.push(format!("{}={}", filetype, theme.get_style(&category)?));
}
writeln!(stdout_lock, "{}", ls_colors.join(":")).ok();
} else if let Some(sub_matches) = matches.subcommand_matches("preview") {
let theme = load_theme(&sub_matches, &user_config_path, color_mode)?;
let mut pairs = filetypes.mapping.iter().collect::<Vec<_>>();
pairs.sort_by_key(|(_, category)| *category);
for (entry, category) in pairs {
let ansi_code = theme.get_style(&category).unwrap_or_else(|_| "0".into());
writeln!(
stdout_lock,
"{}: \x1b[{}m{}\x1b[0m",
category.join("."),
ansi_code,
entry
)
.ok();
}
} else if matches.subcommand_matches("themes").is_some() {
for theme in available_theme_names(&user_config_path)? {
writeln!(stdout_lock, "{}", theme).ok();
}
}
Ok(())
}
fn main() {
let res = run();
match res {
Ok(()) => {}
Err(e) => {
eprintln!("Error: {}", e);
process::exit(1);
}
}
}