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
|
use crate::{
cmd_output,
cmds::{
arg::sheetedit::JVSheeteditArgument, collect::single_file::JVSingleFileCollect,
converter::read_sheet_data_error::ReadSheetDataErrorConverter,
r#in::sheetedit::JVSheeteditInput, out::none::JVNoneOutput,
},
early_cmd_output,
systems::{
cmd::{
cmd_system::{AnyOutput, JVCommandContext},
errors::{CmdExecuteError, CmdPrepareError},
},
helpdoc::helpdoc_viewer,
},
};
use cli_utils::{
display::table::Table, env::editor::get_default_editor,
input::editor::input_with_editor_cutsom, string_vec,
};
use cmd_system_macros::exec;
use just_enough_vcs::system::sheet_system::{mapping::LocalMapping, sheet::SheetData};
use just_fmt::fmt_path::{PathFormatError, fmt_path};
use rust_i18n::t;
use std::{borrow::Cow, path::Path};
use tokio::fs::create_dir_all;
pub struct JVSheeteditCommand;
type Cmd = JVSheeteditCommand;
type Arg = JVSheeteditArgument;
type In = JVSheeteditInput;
type Collect = JVSingleFileCollect;
async fn help_str() -> String {
helpdoc_viewer::display("commands/sheetedit").await;
String::new()
}
async fn prepare(args: &Arg, ctx: &JVCommandContext) -> Result<In, CmdPrepareError> {
let file_path = args
.file
.as_ref()
.or(ctx.stdin_path.as_ref())
.ok_or_else(|| {
CmdPrepareError::Error(t!("sheetedit.error.no_file_input").trim().to_string())
})?;
let file = fmt_path(file_path.clone()).map_err(|e| match e {
PathFormatError::InvalidUtf8(e) => CmdPrepareError::Error(e.to_string()),
})?;
let editor = args.editor.clone().unwrap_or(get_default_editor());
Ok(In { file, editor })
}
async fn collect(args: &Arg, ctx: &JVCommandContext) -> Result<Collect, CmdPrepareError> {
let data = match (&args.file, &ctx.stdin_path) {
(_, Some(stdin_path)) => tokio::fs::read(stdin_path)
.await
.map_err(CmdPrepareError::Io)?,
(Some(file_path), None) => match tokio::fs::read(file_path).await {
Ok(data) => data,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
SheetData::empty().as_bytes().to_vec()
}
Err(e) => return Err(CmdPrepareError::Io(e)),
},
(None, None) => return early_cmd_output!(JVNoneOutput => JVNoneOutput),
};
Ok(Collect { data })
}
#[exec]
async fn exec(input: In, collect: Collect) -> Result<AnyOutput, CmdExecuteError> {
let sheet =
SheetData::try_from(collect.data).map_err(ReadSheetDataErrorConverter::to_exec_error)?;
let mappings = sheet.mappings();
let mut mappings_vec = mappings.iter().cloned().collect::<Vec<LocalMapping>>();
mappings_vec.sort();
let template = build_template(&input.file, mappings_vec).to_string();
let temp_file = input.file.with_added_extension("md");
create_dir_all(temp_file.parent().unwrap()).await?;
let edit_result = input_with_editor_cutsom(template, &temp_file, "#", input.editor).await;
match edit_result {
Ok(t) => {
let rebuild_sheet_data = SheetData::try_from(t.as_str())
.map_err(|e| CmdExecuteError::Error(e.to_string()))?;
tokio::fs::write(&input.file, rebuild_sheet_data.as_bytes()).await?;
}
Err(e) => return Err(CmdExecuteError::Error(e.to_string())),
}
cmd_output!(JVNoneOutput => JVNoneOutput {})
}
fn build_template(file: &Path, mappings: Vec<LocalMapping>) -> Cow<'static, str> {
let mapping_table = render_pretty_mappings(&mappings);
let template = t!(
"sheetedit.editor",
file_dir = file.display(),
info = mapping_table
);
template
}
fn render_pretty_mappings(mappings: &Vec<LocalMapping>) -> String {
let header = string_vec![
format!("# {}", t!("sheetedit.mapping")),
"",
t!("sheetedit.index_source"),
"",
t!("sheetedit.forward")
];
let mut table = Table::new(header);
for mapping in mappings {
let mapping_str = mapping
.to_string()
.split(" ")
.map(|s| s.to_string())
.collect::<Vec<String>>();
table.push_item(vec![
format!(
" {} ",
mapping_str.first().unwrap_or(&String::default())
), // Mapping
format!("{} ", mapping_str.get(1).unwrap_or(&String::default())), // => & ==
format!("{} ", mapping_str.get(2).unwrap_or(&String::default())), // Index
format!("{} ", mapping_str.get(3).unwrap_or(&String::default())), // => & ==
format!("{} ", mapping_str.get(4).unwrap_or(&String::default())), // Forward
]);
}
table.to_string()
}
crate::command_template!();
|