Skip to content

Commit 795d9ce

Browse files
authored
refactor: simplify code (#181)
1 parent ec41cd5 commit 795d9ce

54 files changed

Lines changed: 1650 additions & 2520 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,8 @@ Cargo.lock
1717

1818
# MSVC Windows builds of rustc generate these, which store debugging information
1919
*.pdb
20+
21+
/.claude
22+
/.agents
23+
/.junie
24+
skills-lock.json

src/catalog.rs

Lines changed: 82 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,40 @@
11
use std::io::{BufRead, Read};
22
use std::path::{Path, PathBuf};
33

4-
use anyhow::Error;
4+
use anyhow::{Result, bail};
55
use quick_xml::escape::unescape;
66
use quick_xml::events::{BytesStart, Event};
77

88
use crate::utils::attributes::get_attributes;
99
use crate::utils::file_utils::{escape_filename, join_scope_id_and_name};
1010
use crate::utils::xml_utils::{
11-
XmlEventType, end_element_to_string, end_element_to_string_from_start_element,
12-
extract_values_from_xml_paths, push_rest_of_element_to_skeleton, skip_rest_of_element,
13-
start_element_to_string,
11+
XmlEventType, end_element_to_string, extract_values_from_xml_paths, format_end_tag,
12+
push_rest_of_element_to_skeleton, skip_rest_of_element, start_element_to_string,
1413
};
1514
use crate::utils::{
16-
FolderStructure, build_out_dir_path, create_dir, move_to_subfolder,
17-
write_rest_of_element_to_file,
15+
FolderStructure, build_out_dir_path, create_dir, move_to_subfolder, push_line_to_skeleton,
16+
rename_file_if_necessary, write_rest_of_element_to_file,
1817
};
19-
use crate::utils::{push_line_to_skeleton, rename_file_if_necessary};
2018
use crate::xml_processor::ProcessingContext;
2119

2220
/// Parse and explode a generic catalog, handling both wrapped and unwrapped formats
2321
pub fn xml_explode_catalog<R: Read + BufRead>(
2422
context: &mut ProcessingContext<'_, R>,
25-
_start_tag: &BytesStart,
2623
folder_structure: Option<&FolderStructure>,
27-
// catalog_config: &CatalogConfig,
28-
) -> Result<Option<FolderStructure>, Error> {
29-
let catalog_type = match context.catalog_type {
30-
Some(catalog_type) => catalog_type,
31-
None => return Err(anyhow::anyhow!("❌ Catalog type not specified")),
24+
) -> Result<Option<FolderStructure>> {
25+
let Some(catalog_type) = context.catalog_type else {
26+
bail!("Catalog type not specified");
3227
};
3328
let catalog_config = catalog_type.get_config();
34-
let catalog_item_name = catalog_config.catalog_item_name.clone();
29+
let catalog_item_name = catalog_config.catalog_item_name;
3530
let wrapped_in_object_list = catalog_config.wrapped_in_object_list;
3631
let uses_folders = catalog_config.uses_folders;
3732
let id_path = &catalog_config.id_path;
3833

3934
let out_dir_path_base = build_out_dir_path(context, None)?;
4035
create_dir(&out_dir_path_base);
4136

42-
let mut buf = Vec::new(); // buffer for reading xml events
37+
let mut buf = Vec::new();
4338

4439
// Adjust depth based on whether items are wrapped in ObjectList
4540
let base_depth = context.path_stack.len(); // depth of the catalog start tag, e.g., 4 for BaseDirectoryCatalog, if the path is FMSaveAsXML/Structure/AddAction/BaseDirectoryCatalog
@@ -53,7 +48,7 @@ pub fn xml_explode_catalog<R: Read + BufRead>(
5348

5449
// We'll build a folder structure for custom functions, layouts, scripts, etc.
5550
let mut folder_structure_result = if uses_folders {
56-
Some(FolderStructure::new())
51+
Some(FolderStructure::default())
5752
} else {
5853
None
5954
};
@@ -82,17 +77,16 @@ pub fn xml_explode_catalog<R: Read + BufRead>(
8277

8378
// Is the current element an ancillary element?
8479
// Ancillary elements are elements that are not catalog items or <ObjectList>, e.g., <UUID> or <TagList>
85-
let is_ancillary_element = rel_depth == 2
86-
&& (
87-
// Siblings of ObjectList wrapper at depth 2
88-
(wrapped_in_object_list && e.name().as_ref() != b"ObjectList")
89-
// When not wrapped in <ObjectList>, siblings of catalog items
90-
|| (!wrapped_in_object_list && e.name().as_ref() != catalog_item_name)
91-
);
80+
let expected_element = if wrapped_in_object_list {
81+
b"ObjectList".as_ref()
82+
} else {
83+
catalog_item_name
84+
};
85+
let is_ancillary_element = rel_depth == 2 && e.name().as_ref() != expected_element;
9286

9387
// Handle ancillary elements like <UUID> and <TagList>
9488
if is_ancillary_element {
95-
handle_ancillary_element(context, &e, base_depth, rel_depth);
89+
handle_ancillary_element(context, base_depth, rel_depth);
9690
rel_depth -= 1;
9791
}
9892

@@ -104,23 +98,24 @@ pub fn xml_explode_catalog<R: Read + BufRead>(
10498
// Handle catalog items (e.g. <BaseDirectory>)
10599
// Parse attributes needed for folder tracking
106100
if uses_folders {
107-
let (id, name, is_folder, is_marker, is_separator) =
108-
parse_folder_attributes(&e);
109-
current_id = id;
110-
current_name = name;
101+
let attrs = parse_folder_attributes(&e);
102+
current_id = attrs.id;
103+
current_name = attrs.name;
111104

112-
if is_folder {
105+
if attrs.is_folder {
113106
current_path.push(join_scope_id_and_name(
114107
&current_id,
115108
&escape_filename(&current_name),
116109
));
117110
}
118-
if is_marker {
111+
if attrs.is_marker {
119112
current_path.pop();
120113
}
121114

122-
if (is_folder || is_marker || is_separator) && !context.flags.lossless {
123-
skip_rest_of_element(context.reader, &e);
115+
if (attrs.is_folder || attrs.is_marker || attrs.is_separator)
116+
&& !context.flags.lossless
117+
{
118+
skip_rest_of_element(context.reader);
124119
rel_depth -= 1;
125120
continue;
126121
}
@@ -139,30 +134,26 @@ pub fn xml_explode_catalog<R: Read + BufRead>(
139134
base_depth + rel_depth - 1,
140135
id_path,
141136
);
142-
rename_file_if_necessary(&file_path, context.path_stack, &catalog_item_name);
137+
rename_file_if_necessary(&file_path, context.path_stack, catalog_item_name);
143138

144139
// Move to subfolder if necessary
145-
let subfolder_dir_path = determine_subfolder_path(
140+
if let Some(subfolder) = determine_subfolder_path(
146141
&out_dir_path_base,
147142
folder_structure,
148143
&file_path,
149144
id_path,
150145
uses_folders,
151146
&current_path,
152-
);
153-
if let Some(subfolder_dir_path) = subfolder_dir_path
154-
&& subfolder_dir_path != out_dir_path_base
155-
&& !subfolder_dir_path.to_string_lossy().is_empty()
156-
{
157-
let _ = move_to_subfolder(&file_path, &subfolder_dir_path);
147+
) {
148+
let _ = move_to_subfolder(&file_path, &subfolder);
158149
}
159150

160151
update_folder_structure(&mut folder_structure_result, &current_id, &current_path);
161152

162153
// The element will be consumed by now, so we can't rely on catching the end tag in the Ok(Event::End) arm below
163154
// Instead, write it out manually here
164155
if context.flags.lossless {
165-
let end_tag = end_element_to_string_from_start_element(&e);
156+
let end_tag = format_end_tag(e.name().as_ref());
166157
push_line_to_skeleton(
167158
context.skeleton,
168159
base_depth,
@@ -199,7 +190,7 @@ pub fn xml_explode_catalog<R: Read + BufRead>(
199190
_ => {}
200191
}
201192

202-
buf.clear()
193+
buf.clear();
203194
}
204195

205196
Ok(folder_structure_result)
@@ -213,59 +204,58 @@ fn add_start_tag_to_skeleton<R: Read + BufRead>(
213204
rel_depth: usize,
214205
wrapped_in_object_list: bool,
215206
) {
216-
if !context.flags.lossless {
217-
return;
218-
}
219-
220-
let should_add_to_skeleton = rel_depth == 2 || (wrapped_in_object_list && rel_depth == 3);
221-
if !should_add_to_skeleton {
207+
let should_add = rel_depth == 2 || (wrapped_in_object_list && rel_depth == 3);
208+
if !context.flags.lossless || !should_add {
222209
return;
223210
}
224211

212+
// When should_add is true, the element is always a child of the catalog:
213+
// depth 2 is always a child, and depth 3 only passes when wrapped_in_object_list
225214
push_line_to_skeleton(
226215
context.skeleton,
227216
base_depth,
228217
rel_depth,
229218
start_tag,
230-
if wrapped_in_object_list {
231-
rel_depth >= 2
232-
} else {
233-
rel_depth == 2
234-
},
219+
true,
235220
XmlEventType::Start,
236221
);
237222
}
238223

224+
#[derive(Default)]
225+
struct FolderAttributes {
226+
id: String,
227+
name: String,
228+
is_folder: bool,
229+
is_marker: bool,
230+
is_separator: bool,
231+
}
232+
239233
/// Parse folder-related attributes from a catalog item
240-
fn parse_folder_attributes(e: &BytesStart) -> (String, String, bool, bool, bool) {
241-
let mut current_id = String::new();
242-
let mut current_name = String::new();
243-
let mut is_folder = false;
244-
let mut is_marker = false;
245-
let mut is_separator = false;
234+
fn parse_folder_attributes(e: &BytesStart) -> FolderAttributes {
235+
let mut attrs = FolderAttributes::default();
246236

247-
for attr in get_attributes(e).unwrap() {
237+
for attr in get_attributes(e) {
248238
match attr.0.as_str() {
249-
"id" => current_id = attr.1.to_string(),
250-
"name" => current_name = unescape(attr.1.as_str()).unwrap().to_string(),
239+
"id" => attrs.id = attr.1,
240+
"name" => attrs.name = unescape(&attr.1).unwrap().into_owned(),
251241
"isFolder" => match attr.1.as_str() {
252-
"True" => is_folder = true,
253-
"Marker" => is_marker = true,
242+
"True" => attrs.is_folder = true,
243+
"Marker" => attrs.is_marker = true,
254244
_ => {}
255245
},
256-
"isSeparatorItem" => {
257-
if attr.1.as_str() == "True" {
258-
is_separator = true
259-
}
260-
}
246+
"isSeparatorItem" => attrs.is_separator = attr.1 == "True",
261247
_ => {}
262248
}
263249
}
264250

265-
(current_id, current_name, is_folder, is_marker, is_separator)
251+
attrs
266252
}
267253

268-
/// Determine the subfolder path for a catalog item
254+
/// Determine the subfolder path for a catalog item.
255+
/// Uses `current_path` for catalogs that track their own folder hierarchy (scripts, custom functions, layouts),
256+
/// or looks up the path via `folder_structure` for dependent catalogs (steps, calcs).
257+
/// Returns `Some(subfolder)` only when the item should be moved, i.e., the resolved
258+
/// subfolder differs from `out_dir_path_base`.
269259
fn determine_subfolder_path(
270260
out_dir_path_base: &Path,
271261
folder_structure: Option<&FolderStructure>,
@@ -274,59 +264,47 @@ fn determine_subfolder_path(
274264
uses_folders: bool,
275265
current_path: &[String],
276266
) -> Option<PathBuf> {
277-
let folder_structure = match folder_structure {
278-
Some(folder_structure) => folder_structure,
267+
let subfolder = match folder_structure {
268+
Some(fs) => {
269+
// For dependent catalogs that use a previously-built folder structure
270+
if id_path.is_empty() {
271+
return None;
272+
}
273+
let results = extract_values_from_xml_paths(file_path, &[id_path]).ok()?;
274+
let id = results.first()?.as_ref()?;
275+
let path = fs.get_path_for_id(id);
276+
if path.is_empty() {
277+
return None;
278+
}
279+
out_dir_path_base.join(path.join("/"))
280+
}
279281
None => {
280-
return if uses_folders && !current_path.is_empty() {
281-
// Track folders using current catalog item
282-
Some(out_dir_path_base.join(current_path.join("/")))
283-
} else {
284-
None
285-
};
282+
// For catalogs with their own folder tracking
283+
if !uses_folders || current_path.is_empty() {
284+
return None;
285+
}
286+
out_dir_path_base.join(current_path.join("/"))
286287
}
287288
};
288289

289-
// If a folder structure was provided, use that
290-
if id_path.is_empty() {
291-
return None;
292-
}
293-
294-
let paths = vec![id_path];
295-
let results = match extract_values_from_xml_paths(file_path, &paths) {
296-
Ok(results) => results,
297-
Err(_) => return None,
298-
};
299-
300-
let id = match results.first() {
301-
Some(Some(id)) => id,
302-
_ => return None,
303-
};
304-
305-
let function_path = folder_structure.get_path_for_id(id);
306-
if function_path.is_empty() {
307-
return None;
308-
}
309-
310-
Some(out_dir_path_base.join(function_path.join("/")))
290+
(subfolder != out_dir_path_base).then_some(subfolder)
311291
}
312292

313293
/// Handle ancillary elements like <UUID> and <TagList>
314294
fn handle_ancillary_element<R: Read + BufRead>(
315295
context: &mut ProcessingContext<'_, R>,
316-
e: &BytesStart,
317296
base_depth: usize,
318297
rel_depth: usize,
319298
) {
320299
if context.flags.lossless {
321300
push_rest_of_element_to_skeleton(
322301
context.reader,
323-
e,
324302
context.skeleton,
325303
base_depth + rel_depth - 1,
326304
context.flags,
327305
);
328306
} else {
329-
skip_rest_of_element(context.reader, e);
307+
skip_rest_of_element(context.reader);
330308
}
331309
}
332310

0 commit comments

Comments
 (0)