diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c2b394..2840fe1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased ### Added -- Nested tag support - "parent/child" tags used with the "Tag" hierarchy create nested folders. +- Nested tag support (Issue [#85](https://github.com/out-of-cheese-error/gooseberry/issues/85)) + - `gooseberry config kb nested` and `nested_tag` config option to determine pattern to use for nesting tags. + - `parentchild` tags used with the "Tag" hierarchy create nested folders. - Separate make and index commands, allow filtering annotations in both ( Issue [#90](https://github.com/out-of-cheese-error/gooseberry/issues/90)) - Better and more filtering options (Issue [#92](https://github.com/out-of-cheese-error/gooseberry/issues/92)) diff --git a/README.md b/README.md index 3d33b18..a4c10f1 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ This demonstrates the interactive search functionality. `Enter` adds a new tag, * [Page template](#page-template) * [Grouping annotations into folders and pages](#grouping-annotations-into-folders-and-pages) * [Sorting annotations within a page](#sorting-annotations-within-a-page) + * [Tags and nesting](#tags-and-nesting) * [Index link template](#index-link-template) * [Index filename](#index-filename) * [Ignoring tags](#ignoring-tags) @@ -371,14 +372,25 @@ The available options are: * Created * Updated -Multiple sort options can be combined in order of priority e.g. `sort = ["Tag", "Created"]` sorts by tags, then by the date of creation. +Multiple sort options can be combined in order of priority e.g. `sort = ["Tag", "Created"]` sorts by tags, then by the +date of creation. + +#### Tags and nesting + +`gooseberry config kb nested` + +This defines the pattern to use for nesting tags. e.g. if `nested_tag = "/"` then a tag of "parent/child" combined +with `hierarchy = ["Tag"]` would create a "parent" folder with a "child" file inside it. + +Commas (",") and semicolons (";") should not be used inside tags as they are used as separators by Gooseberry. #### Index link template `gooseberry config kb link` This configures the index file, which generally contains links to all other pages in the generated knowledge base -(unless `hierarchy=[]` in which case all annotations are rendered on the index page). The template controls how each of these links are rendered. +(unless `hierarchy=[]` in which case all annotations are rendered on the index page). The template controls how each of +these links are rendered. Available keys: diff --git a/src/configuration.rs b/src/configuration.rs index 0d0986f..d5943a4 100644 --- a/src/configuration.rs +++ b/src/configuration.rs @@ -16,6 +16,7 @@ use crate::gooseberry::knowledge_base::{ }; use crate::{utils, NAME}; +pub static DEFAULT_NESTED_TAG: &str = "/"; pub static DEFAULT_ANNOTATION_TEMPLATE: &str = r#" ### {{id}} @@ -98,6 +99,8 @@ pub struct GooseberryConfig { pub(crate) sort: Option>, /// Define tags to ignore pub(crate) ignore_tags: Option>, + /// Define nested tag pattern + pub(crate) nested_tag: Option, } /// Main project directory, cross-platform @@ -123,6 +126,7 @@ impl Default for GooseberryConfig { hierarchy: None, sort: None, ignore_tags: None, + nested_tag: None, }; config.make_dirs().unwrap(); config @@ -146,12 +150,14 @@ kb_dir = '' hierarchy = ['Tag'] sort = ['Created'] ignore_tags = [] +nested_tag = {} annotation_template = '''{}''' page_template = '''{}''' index_link_template = '''{}''' index_name = '{}' file_extension = '{}' "#, + DEFAULT_NESTED_TAG, DEFAULT_ANNOTATION_TEMPLATE, DEFAULT_PAGE_TEMPLATE, DEFAULT_INDEX_LINK_TEMPLATE, @@ -299,6 +305,7 @@ file_extension = '{}' self.set_page_template()?; self.set_index_link_template()?; self.set_index_name()?; + self.set_nested_tag()?; self.set_file_extension()?; self.set_hierarchy()?; self.set_sort()?; @@ -727,6 +734,17 @@ file_extension = '{}' Ok(()) } + pub fn set_nested_tag(&mut self) -> color_eyre::Result<()> { + self.nested_tag = Some(utils::user_input( + "What pattern should gooseberry use to define nested tags", + Some(self.nested_tag.as_deref().unwrap_or(DEFAULT_NESTED_TAG)), + true, + false, + )?); + self.store()?; + Ok(()) + } + pub fn set_file_extension(&mut self) -> color_eyre::Result<()> { self.file_extension = Some(utils::user_input( "What extension should gooseberry use for wiki files", diff --git a/src/gooseberry/cli.rs b/src/gooseberry/cli.rs index e0b7bb0..35274d8 100644 --- a/src/gooseberry/cli.rs +++ b/src/gooseberry/cli.rs @@ -262,6 +262,8 @@ pub enum KbConfigCommand { Sort, /// Set which tags to ignore Ignore, + /// Set string defining nested tags (e.g "/" => parent/child) + Nest, } impl ConfigCommand { @@ -295,6 +297,7 @@ impl ConfigCommand { KbConfigCommand::Page => config.set_page_template()?, KbConfigCommand::Link => config.set_index_link_template()?, KbConfigCommand::Index => config.set_index_name()?, + KbConfigCommand::Nest => config.set_nested_tag()?, KbConfigCommand::Extension => config.set_file_extension()?, KbConfigCommand::Hierarchy => config.set_hierarchy()?, KbConfigCommand::Sort => config.set_sort()?, diff --git a/src/gooseberry/knowledge_base.rs b/src/gooseberry/knowledge_base.rs index 3143ff3..5319f33 100644 --- a/src/gooseberry/knowledge_base.rs +++ b/src/gooseberry/knowledge_base.rs @@ -160,6 +160,7 @@ pub struct PageTemplate { fn group_annotations_by_order( order: OrderBy, annotations: Vec, + nested_tag: Option<&String>, ) -> HashMap> { let mut order_to_annotations = HashMap::new(); match order { @@ -173,7 +174,10 @@ fn group_annotations_by_order( .push(annotation); } else { for tag in &annotation.annotation.tags { - let tag = tag.replace("/", path_separator); + let mut tag = tag.to_owned(); + if let Some(nested_tag) = nested_tag { + tag = tag.replace(nested_tag, path_separator); + } order_to_annotations .entry(tag) .or_insert_with(Vec::new) @@ -290,7 +294,6 @@ impl Gooseberry { self.make_book(annotations, &kb_dir, make, index).await?; Ok(()) } - /// Write markdown files for wiki async fn make_book( &self, @@ -380,9 +383,11 @@ impl Gooseberry { if make && !folder.exists() { fs::create_dir(&folder)?; } - for (new_folder, annotations) in - group_annotations_by_order(order[depth], inner_annotations) - { + for (new_folder, annotations) in group_annotations_by_order( + order[depth], + inner_annotations, + self.config.nested_tag.as_ref(), + ) { (recurse_folder.f)( recurse_folder, annotations, diff --git a/tests/cli.rs b/tests/cli.rs index c629a35..2ed4812 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -32,6 +32,7 @@ hypothesis_group = '{}' kb_dir = '{}' hierarchy = ['Tag'] sort = ['Created'] +nested_tag = ' : ' annotation_template = '''{}''' page_template = '''{}''' index_link_template = '''{}''' @@ -448,6 +449,22 @@ async fn make() -> color_eyre::Result<()> { .tags .contains(&"test tag5".to_owned())); + // add a nested tag + thread::sleep(duration); + let mut cmd = Command::cargo_bin("gooseberry")?; + cmd.env("GOOSEBERRY_CONFIG", &test_data.config_file) + .arg("tag") + .arg("--tags=test_tag") + .arg("test_tag6 : test_tag7") + .assert() + .success(); + assert!(test_data + .hypothesis_client + .fetch_annotation(&test_data.annotations[0].id) + .await? + .tags + .contains(&"test_tag6 : test_tag7".to_owned())); + // make thread::sleep(duration); let mut cmd = Command::cargo_bin("gooseberry")?; @@ -491,10 +508,19 @@ async fn make() -> color_eyre::Result<()> { .exists()); // check all tag files - assert!(["test_tag", "test_tag1", "test_tag2", "test tag5"] + assert!(["test_tag", "test_tag1", "test_tag2", "test tag5",] .iter() .all(|t| file_names.contains(&format!("{}.md", t)))); + // check nested tags + assert!(file_names.contains("test_tag6")); + assert!(test_data + .temp_dir + .path() + .join("kb") + .join("test_tag6") + .join("test_tag7.md") + .exists()); test_data.clear().await?; Ok(()) }