From fe5667086a14b39b8a8b0708e214f8fd4bd74115 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Such=C3=A1nek?= Date: Fri, 27 Oct 2023 21:38:37 +0200 Subject: [PATCH 1/6] Detect the private ticket footnote; #24 --- Cargo.lock | 70 +++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/config.rs | 6 +++++ src/convert.rs | 5 +--- src/footnote.rs | 59 +++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 5 ++++ 6 files changed, 142 insertions(+), 4 deletions(-) create mode 100644 src/footnote.rs diff --git a/Cargo.lock b/Cargo.lock index 63993b2..b8044b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,7 @@ dependencies = [ "bugzilla_query", "color-eyre", "counter", + "ignore", "include_dir", "jira_query", "log", @@ -178,6 +179,16 @@ dependencies = [ "syn", ] +[[package]] +name = "bstr" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c79ad7fb2dd38f3dabd76b09c6a5a20c038fc0213ef1e9afd30eb777f120f019" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bugzilla_query" version = "1.0.2" @@ -397,6 +408,19 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +[[package]] +name = "globset" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759c97c1e17c55525b57192c06a267cda0ac5210b222d6b82189a2338fa1c13d" +dependencies = [ + "aho-corasick", + "bstr", + "fnv", + "log", + "regex", +] + [[package]] name = "h2" version = "0.3.21" @@ -547,6 +571,23 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "ignore" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492" +dependencies = [ + "globset", + "lazy_static", + "log", + "memchr", + "regex", + "same-file", + "thread_local", + "walkdir", + "winapi-util", +] + [[package]] name = "include_dir" version = "0.7.3" @@ -1034,6 +1075,15 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.22" @@ -1267,6 +1317,16 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "time" version = "0.3.30" @@ -1457,6 +1517,16 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "walkdir" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" diff --git a/Cargo.toml b/Cargo.toml index 024e327..9236641 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ counter = "^0.5" regex = "1.10" once_cell = "1.18" include_dir = "0.7" +ignore = "0.4" [build-dependencies] bpaf = { version = "0.9", features = ["derive", "docgen"]} diff --git a/src/config.rs b/src/config.rs index f34063e..ff3ec71 100644 --- a/src/config.rs +++ b/src/config.rs @@ -24,6 +24,8 @@ use std::sync::Arc; use color_eyre::eyre::{eyre, Result, WrapErr}; use serde::Deserialize; +use crate::footnote; + /// The name of this program, as specified in Cargo.toml. Used later to access configuration files. const PROGRAM_NAME: &str = env!("CARGO_PKG_NAME"); @@ -422,6 +424,7 @@ pub struct Project { pub tickets: Vec>, pub trackers: tracker::Config, pub templates: Template, + pub private_footnote: bool, } impl Project { @@ -452,12 +455,15 @@ impl Project { let trackers = parse_trackers(&trackers_path)?; let templates = parse_templates(&templates_path)?; + let private_footnote = footnote::is_footnote_defined(&abs_path)?; + Ok(Self { base_dir: abs_path, generated_dir, tickets, trackers, templates, + private_footnote, }) } } diff --git a/src/convert.rs b/src/convert.rs index 918331e..b6ba175 100644 --- a/src/convert.rs +++ b/src/convert.rs @@ -13,10 +13,7 @@ use regex::Regex; use serde::Deserialize; use crate::config::{tracker::Service, KeyOrSearch}; - -/// A shared error message that displays if the static regular expressions -/// are invalid, and the regex library can't parse them. -const REGEX_ERROR: &str = "Invalid built-in regular expression."; +use crate::REGEX_ERROR; /// A regular expression that matches the Bugzilla ID format in CoRN 3. static BZ_REGEX: Lazy = Lazy::new(|| Regex::new(r"^BZ#(\d+)$").expect(REGEX_ERROR)); diff --git a/src/footnote.rs b/src/footnote.rs new file mode 100644 index 0000000..938d163 --- /dev/null +++ b/src/footnote.rs @@ -0,0 +1,59 @@ +use std::fs; +use std::path::Path; + +use color_eyre::{Result, eyre::WrapErr}; +use ignore::Walk; +use once_cell::sync::Lazy; +use regex::Regex; + +use crate::REGEX_ERROR; + +/// This regex looks for a footnote definition with the `PrivateTicketFootnote` ID. +static FOOTNOTE_ATTR_REGEX: Lazy = + Lazy::new(|| Regex::new(r"footnoteref:\[PrivateTicketFootnote,.+\]").expect(REGEX_ERROR)); + + +pub fn is_footnote_defined(project: &Path) -> Result { + for result in Walk::new(project) { + // Each item yielded by the iterator is either a directory entry or an error. + let dir_entry = result?; + + let file_path = dir_entry.path(); + + if is_file_adoc(file_path) && file_contains_footnote(file_path)? { + log::info!("The private ticket footnote is defined."); + return Ok(true); + } + } + + log::info!("The private ticket footnote is not defined."); + Ok(false) +} + +fn is_file_adoc(path: &Path) -> bool { + let adoc_extensions = ["adoc", "asciidoc"]; + + let file_ext = path.extension().and_then(|ext| ext.to_str()); + + if let Some(ext) = file_ext { + if adoc_extensions.contains(&ext) { + return true; + } + } + + false +} + +fn file_contains_footnote(path: &Path) -> Result { + let text = fs::read_to_string(path) + .wrap_err("Cannot read AsciiDoc file in the project repository.")?; + + let found_attr = text.lines().any(|line| { + // Detect and reject basic line comments. + !line.starts_with("//") && + // If any line contains the footnote attribute definition, return `true`. + FOOTNOTE_ATTR_REGEX.is_match(line) + }); + + Ok(found_attr) +} diff --git a/src/lib.rs b/src/lib.rs index a1b979b..3d8baf1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,6 +37,7 @@ pub mod cli; mod config; mod convert; mod extra_fields; +mod footnote; mod init; mod logging; mod note; @@ -55,6 +56,10 @@ use templating::{DocumentVariant, Module}; use crate::config::Project; pub use crate::ticket_abstraction::AbstractTicket; +/// A shared error message that displays if the static regular expressions +/// are invalid, and the regex library can't parse them. +pub const REGEX_ERROR: &str = "Invalid built-in regular expression."; + /// Run the subcommand that the user picked on the command line. pub fn run(cli: &Cli) -> Result<()> { // Initialize the logging system based on the set verbosity From 1d8cf8825eb9c3c32059deb465976b7b32369854 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Such=C3=A1nek?= Date: Fri, 27 Oct 2023 21:53:33 +0200 Subject: [PATCH 2/6] Always display the private ticket footnote; 24 --- src/note.rs | 24 +++++++++++------------- src/references.rs | 6 ++++-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/note.rs b/src/note.rs index 15d3faa..82aa1ed 100644 --- a/src/note.rs +++ b/src/note.rs @@ -67,24 +67,22 @@ impl AbstractTicket { /// /// For example, `link:https://...bugzilla...12345[BZ#12345]`. #[must_use] - pub fn signature(&self) -> String { + pub fn signature(&self, with_priv_footnote: bool) -> String { let id = &self.id; if self.public { // If the ticket is public, add a clickable link. format!("link:{}[{}]", &self.url, id) } else { - // If the ticket is private, add a footnote that explains - // why some links aren't clickable. - // The shared ID of the private footnote is arbitrary and large to avoid clashes: - // 255 is the u8 max value. - // The `{private-footnote}` attribute is defined in the reference template, - // and the user can override it in their AsciiDoc files. - // - // TODO: This works with asciidoctor, but the footnote doesn't render - // at all with Pantheon. Disabling for now. - // format!("{id}{{fn-private}}") - id.to_string() + // If the ticket is private, and the project configures a dedicated footnote, + // add a footnote that explains why the link isn't clickable. + // This uses the deprecated AsciiDoc `footnoteref` syntax + // so that you can build the document with very outdated asciidoctor. + if with_priv_footnote { + format!("{id}footnoteref:[PrivateTicketFootnote]") + } else { + id.to_string() + } } } @@ -92,7 +90,7 @@ impl AbstractTicket { /// The result is a comma-separated list of signatures, enclosed in parentheses. #[must_use] fn all_signatures(&self) -> String { - let mut signatures = vec![self.signature()]; + let mut signatures = vec![self.signature(true)]; if let Some(references) = self.references.as_ref() { signatures.append(&mut references.clone()); diff --git a/src/references.rs b/src/references.rs index c14da9e..842bf8a 100644 --- a/src/references.rs +++ b/src/references.rs @@ -82,8 +82,10 @@ impl ReferenceSignatures { let ticket = issue.into_abstract(None, config)?; signatures .entry(query) - .and_modify(|e| e.push(ticket.signature())) - .or_insert_with(|| vec![ticket.signature()]); + // In reference IDs, never display the private ticket footnote, + // even when it's defined in the project. Too much clutter on one line. + .and_modify(|e| e.push(ticket.signature(false))) + .or_insert_with(|| vec![ticket.signature(false)]); } Ok(()) From 5ff96c7b032d89690ec360a8c22dedc54924321e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Such=C3=A1nek?= Date: Fri, 27 Oct 2023 22:04:23 +0200 Subject: [PATCH 3/6] Display the private ticket footnote only when enabled in project; #24 --- src/footnote.rs | 1 - src/lib.rs | 2 ++ src/note.rs | 8 ++++---- src/templating.rs | 11 +++++++---- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/footnote.rs b/src/footnote.rs index 938d163..337d796 100644 --- a/src/footnote.rs +++ b/src/footnote.rs @@ -26,7 +26,6 @@ pub fn is_footnote_defined(project: &Path) -> Result { } } - log::info!("The private ticket footnote is not defined."); Ok(false) } diff --git a/src/lib.rs b/src/lib.rs index 3d8baf1..12a34e4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -157,11 +157,13 @@ impl Document { &tickets_for_internal, &project.templates, DocumentVariant::Internal, + project.private_footnote, ); let external_modules = templating::format_document( &tickets_for_external, &project.templates, DocumentVariant::External, + project.private_footnote, ); let (status_table, json_status) = status_report::analyze_status(&abstract_tickets)?; diff --git a/src/note.rs b/src/note.rs index 82aa1ed..68af8be 100644 --- a/src/note.rs +++ b/src/note.rs @@ -22,7 +22,7 @@ use crate::ticket_abstraction::AbstractTicket; impl AbstractTicket { /// Compose a release note from an abstract ticket. #[must_use] - pub fn release_note(&self, variant: DocumentVariant) -> String { + pub fn release_note(&self, variant: DocumentVariant, with_priv_footnote: bool) -> String { let anchor = self.anchor_declaration(); // This debug information line appears at empty release notes @@ -51,7 +51,7 @@ impl AbstractTicket { "{}\n{}\n\n{} {}", anchor, doc_text_unix, - self.all_signatures(), + self.all_signatures(with_priv_footnote), // In the internal variant, add the debug information line. if variant == DocumentVariant::Internal { &debug_info @@ -89,8 +89,8 @@ impl AbstractTicket { /// Prepare a list with signatures to this ticket and all its optional references. /// The result is a comma-separated list of signatures, enclosed in parentheses. #[must_use] - fn all_signatures(&self) -> String { - let mut signatures = vec![self.signature(true)]; + fn all_signatures(&self, with_priv_footnote: bool) -> String { + let mut signatures = vec![self.signature(with_priv_footnote)]; if let Some(references) = self.references.as_ref() { signatures.append(&mut references.clone()); diff --git a/src/templating.rs b/src/templating.rs index 7976c65..a7eea21 100644 --- a/src/templating.rs +++ b/src/templating.rs @@ -161,6 +161,7 @@ impl config::Section { id: &str, tickets: &[&AbstractTicket], variant: DocumentVariant, + with_priv_footnote: bool, ticket_stats: &mut HashMap, u32>, ) -> Option { let matching_tickets: Vec<_> = tickets.iter().filter(|t| self.matches_ticket(t)).collect(); @@ -178,7 +179,7 @@ impl config::Section { } else { let release_notes: Vec<_> = matching_tickets .iter() - .map(|t| t.release_note(variant)) + .map(|t| t.release_note(variant, with_priv_footnote)) .collect(); let template = Leaf { @@ -206,6 +207,7 @@ impl config::Section { tickets: &[&AbstractTicket], prefix: Option<&str>, variant: DocumentVariant, + with_priv_footnote: bool, ticket_stats: &mut HashMap, u32>, ) -> Option { let matching_tickets: Vec<&AbstractTicket> = tickets @@ -227,7 +229,7 @@ impl config::Section { let included_modules: Vec = sections .iter() .filter_map(|s| { - s.modules(&matching_tickets, Some(&module_id), variant, ticket_stats) + s.modules(&matching_tickets, Some(&module_id), variant, with_priv_footnote, ticket_stats) }) .collect(); // If the assembly receives no modules, because all its modules are empty, return None. @@ -261,7 +263,7 @@ impl config::Section { } else { // If the module receives no release notes and its body is empty, return None. // Otherwise, return the module formatted with its release notes. - self.render(&module_id, tickets, variant, ticket_stats) + self.render(&module_id, tickets, variant, with_priv_footnote, ticket_stats) .map(|text| Module { file_name: format!("ref_{module_id}.adoc"), text, @@ -335,6 +337,7 @@ pub fn format_document( tickets: &[&AbstractTicket], template: &config::Template, variant: DocumentVariant, + with_priv_footnote: bool, ) -> Vec { // Prepare a container for ticket usage statistics. let mut ticket_stats = HashMap::new(); @@ -354,7 +357,7 @@ pub fn format_document( let chapters: Vec<_> = template .chapters .iter() - .filter_map(|section| section.modules(tickets, None, variant, &mut ticket_stats)) + .filter_map(|section| section.modules(tickets, None, variant, with_priv_footnote, &mut ticket_stats)) .collect(); log::debug!("Chapters: {:#?}", chapters); From c78778834a5cbaa7824c17592aeeaacb7640207d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Such=C3=A1nek?= Date: Mon, 30 Oct 2023 13:54:17 +0100 Subject: [PATCH 4/6] Adjust logging with the private footnote --- src/config.rs | 2 ++ src/lib.rs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/config.rs b/src/config.rs index ff3ec71..9f49352 100644 --- a/src/config.rs +++ b/src/config.rs @@ -455,6 +455,8 @@ impl Project { let trackers = parse_trackers(&trackers_path)?; let templates = parse_templates(&templates_path)?; + log::info!("Valid release notes project in {}.", abs_path.display()); + let private_footnote = footnote::is_footnote_defined(&abs_path)?; Ok(Self { diff --git a/src/lib.rs b/src/lib.rs index 12a34e4..59e510f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -121,7 +121,7 @@ fn build_rn_project(project_dir: &Path) -> Result<()> { // TODO: Recognize the optional paths to different config files. let project = Project::new(project_dir)?; - log::info!("Building release notes in {}", &project.base_dir.display()); + log::info!("Building the release notes project."); let document = Document::new(&project)?; From b265af7dada59acdc66c7e495f19784a858463c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Such=C3=A1nek?= Date: Mon, 30 Oct 2023 14:32:50 +0100 Subject: [PATCH 5/6] My cat contributed to this documentation --- docs/main.adoc | 2 + ...planatory-footnote-to-private-tickets.adoc | 69 +++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 docs/modules/proc_adding-an-explanatory-footnote-to-private-tickets.adoc diff --git a/docs/main.adoc b/docs/main.adoc index f2d9667..43d206e 100644 --- a/docs/main.adoc +++ b/docs/main.adoc @@ -18,6 +18,8 @@ include::modules/proc_building-release-notes.adoc[leveloffset=+1] include::assembly_organizing-tickets-in-your-project-using-templates.adoc[leveloffset=+1] +include::modules/proc_adding-an-explanatory-footnote-to-private-tickets.adoc[leveloffset=+1] + include::modules/ref_differences-between-acorns-and-corn-3.adoc[leveloffset=+1] diff --git a/docs/modules/proc_adding-an-explanatory-footnote-to-private-tickets.adoc b/docs/modules/proc_adding-an-explanatory-footnote-to-private-tickets.adoc new file mode 100644 index 0000000..a91b125 --- /dev/null +++ b/docs/modules/proc_adding-an-explanatory-footnote-to-private-tickets.adoc @@ -0,0 +1,69 @@ +:_newdoc-version: 2.15.0 +:_template-generated: 2023-10-30 +:_mod-docs-content-type: PROCEDURE + +[id="adding-an-explanatory-footnote-to-private-tickets_{context}"] += Adding an explanatory footnote to private tickets + +[role="_abstract"] +Public ticket IDs contain a clickable link to the ticket. Private ticket IDs are non-clickable. If this is confusing to the readers, you can automatically generate a footnote next to private ticket IDs that explains why the ticket link is missing. + +For example: + +* link:https://bugzilla.redhat.com/show_bug.cgi?id=2157953[Bugzilla:2157953] +* Bugzilla:2216257footnoteref:[PrivateTicketFootnote,This ticket is private.] + +.Prerequisites + +* Prepare the text that you want to add to every private ticket ID as a footnote. + +.Procedure + +. In your release notes project, enter a footnote with the `PrivateTicketFootnote` ID in a manually written AsciiDoc file. ++ +The footnote must appear before you include the first file generated by {name}, such as near the book introduction or at the start of the main file. ++ +.Placement of the footnote in main.adoc +==== +[source,asciidoc"] +---- += Release notes for {Product} {Version} + +This is the abstract paragraph. + +Release notes include links where you can access the original tracking ticket. Private tickets have no links and instead feature ýžzuuuuujuzzzzzzzzzzzz´´´´´´´´´´´´´ +---- +==== + +. Include one command or action for each step with the exception of simple follow-step, for example: +.. Do this thing and then select *Next*. +.. Do this other thing and then select *Next*. + +. Use an unnumbered bullet (*) if the procedure includes only one step. + +.Verification + +Delete this section if it does not apply to your module. Provide the user with verification methods for the procedure, such as expected output or commands that confirm success or failure. + +* Provide an example of expected command output or a pop-up window that the user receives when the procedure is successful. +* List actions for the user to complete, such as entering a command, to determine the success or failure of the procedure. +* Make each step an instruction. +* Include one command or action per step. +* Use an unnumbered bullet (*) if the verification includes only one step. + +[role="_additional-resources"] +.Next steps + +* This section is optional. +* Provide a bulleted list of links that contain instructions that might be useful to the user after they complete this procedure. +* Use an unnumbered bullet (*) if the list includes only one step. + +NOTE: Do not use *Next steps* to provide a second list of instructions. + +[role="_additional-resources"] +.Additional resources + +* This section is optional. +* Provide a bulleted list of links to other closely-related material. These links can include `link:` and `xref:` macros. +* Use an unnumbered bullet (*) if the list includes only one step. + From 33de1872e04a48e276e8097079da4af2ca211791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Such=C3=A1nek?= Date: Mon, 30 Oct 2023 14:44:55 +0100 Subject: [PATCH 6/6] Document the private ticket footnote option; #24 --- ...planatory-footnote-to-private-tickets.adoc | 54 ++++++++----------- src/footnote.rs | 30 +++++++++++ 2 files changed, 52 insertions(+), 32 deletions(-) diff --git a/docs/modules/proc_adding-an-explanatory-footnote-to-private-tickets.adoc b/docs/modules/proc_adding-an-explanatory-footnote-to-private-tickets.adoc index a91b125..e295c16 100644 --- a/docs/modules/proc_adding-an-explanatory-footnote-to-private-tickets.adoc +++ b/docs/modules/proc_adding-an-explanatory-footnote-to-private-tickets.adoc @@ -2,6 +2,8 @@ :_template-generated: 2023-10-30 :_mod-docs-content-type: PROCEDURE +:private-footnote-text: This ticket is private. + [id="adding-an-explanatory-footnote-to-private-tickets_{context}"] = Adding an explanatory footnote to private tickets @@ -11,59 +13,47 @@ Public ticket IDs contain a clickable link to the ticket. Private ticket IDs ar For example: * link:https://bugzilla.redhat.com/show_bug.cgi?id=2157953[Bugzilla:2157953] -* Bugzilla:2216257footnoteref:[PrivateTicketFootnote,This ticket is private.] - -.Prerequisites +* Bugzilla:2216257{blank}footnoteref:[PrivateTicketFootnote,{private-footnote-text}] -* Prepare the text that you want to add to every private ticket ID as a footnote. .Procedure +. Prepare the text that you want to add to every private ticket ID as a footnote. For example: ++ +[source,asciidoc] +---- +This ticket is private. +---- + . In your release notes project, enter a footnote with the `PrivateTicketFootnote` ID in a manually written AsciiDoc file. + -The footnote must appear before you include the first file generated by {name}, such as near the book introduction or at the start of the main file. +The footnote must appear before you include the first file generated by {name}, such as near the book introduction or at the start of the main file. This requirement is caused by a technical limitation in the deprecated `footnoteref` macro. ++ +You can store the footnote text in a separate attribute, so that it is more convenient to edit: + .Placement of the footnote in main.adoc ==== [source,asciidoc"] ---- +:private-footnote-text: This ticket is private. + = Release notes for {Product} {Version} This is the abstract paragraph. -Release notes include links where you can access the original tracking ticket. Private tickets have no links and instead feature ýžzuuuuujuzzzzzzzzzzzz´´´´´´´´´´´´´ +Release notes include links to access the original tracking tickets. Private tickets have no links and instead feature the following footnote{blank}footnoteref:[PrivateTicketFootnote,{private-footnote-text}.] + + ---- ==== -. Include one command or action for each step with the exception of simple follow-step, for example: -.. Do this thing and then select *Next*. -.. Do this other thing and then select *Next*. - -. Use an unnumbered bullet (*) if the procedure includes only one step. - .Verification -Delete this section if it does not apply to your module. Provide the user with verification methods for the procedure, such as expected output or commands that confirm success or failure. - -* Provide an example of expected command output or a pop-up window that the user receives when the procedure is successful. -* List actions for the user to complete, such as entering a command, to determine the success or failure of the procedure. -* Make each step an instruction. -* Include one command or action per step. -* Use an unnumbered bullet (*) if the verification includes only one step. - -[role="_additional-resources"] -.Next steps - -* This section is optional. -* Provide a bulleted list of links that contain instructions that might be useful to the user after they complete this procedure. -* Use an unnumbered bullet (*) if the list includes only one step. - -NOTE: Do not use *Next steps* to provide a second list of instructions. +. Build the release notes project. +. Open a preview. +. Compare the IDs of public and private tickets. Verify that all private tickets feature the footnote. [role="_additional-resources"] .Additional resources -* This section is optional. -* Provide a bulleted list of links to other closely-related material. These links can include `link:` and `xref:` macros. -* Use an unnumbered bullet (*) if the list includes only one step. - +* If you want to hide all links to certain Jira projects and set them as private, see `private_projects` in xref:required-and-optional-fields-in-tracker-configuration_enabling-access-to-your-ticket-trackers[]. diff --git a/src/footnote.rs b/src/footnote.rs index 337d796..5159713 100644 --- a/src/footnote.rs +++ b/src/footnote.rs @@ -1,3 +1,27 @@ +/* +acorns: Generate an AsciiDoc release notes document from tracking tickets. +Copyright (C) 2023 Marek Suchánek + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +//! This module provides functionality that connects to the signature format in the release note, +//! based on an optional footnote found in the manual AsciiDoc files in the docs repo. +//! +//! If any manual AsciiDoc file defines the `PrivateTicketFootnote` footnote, private tickets +//! will add the footnote to the non-clickable ticket signature. + use std::fs; use std::path::Path; @@ -13,6 +37,9 @@ static FOOTNOTE_ATTR_REGEX: Lazy = Lazy::new(|| Regex::new(r"footnoteref:\[PrivateTicketFootnote,.+\]").expect(REGEX_ERROR)); +/// Search the AsciiDoc files in the RN project and see if any of them defines +/// the `PrivateTicketFootnote` footnote. Return `true` if the footnote is defined. +#[must_use] pub fn is_footnote_defined(project: &Path) -> Result { for result in Walk::new(project) { // Each item yielded by the iterator is either a directory entry or an error. @@ -29,6 +56,7 @@ pub fn is_footnote_defined(project: &Path) -> Result { Ok(false) } +/// Estimate if the given file is an AsciiDoc file. fn is_file_adoc(path: &Path) -> bool { let adoc_extensions = ["adoc", "asciidoc"]; @@ -43,6 +71,8 @@ fn is_file_adoc(path: &Path) -> bool { false } +/// Return `true` if the given file contains the footnote defined +/// in the `FOOTNOTE_ATTR_REGEX` regular expression. fn file_contains_footnote(path: &Path) -> Result { let text = fs::read_to_string(path) .wrap_err("Cannot read AsciiDoc file in the project repository.")?;