Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable optional private ticket footnote #28

Merged
merged 6 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]}
Expand Down
2 changes: 2 additions & 0 deletions docs/main.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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]


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
:_newdoc-version: 2.15.0
:_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

[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:2216257{blank}footnoteref:[PrivateTicketFootnote,{private-footnote-text}]


.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. 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 to access the original tracking tickets. Private tickets have no links and instead feature the following footnote{blank}footnoteref:[PrivateTicketFootnote,{private-footnote-text}.]

<Include your modules here.>
----
====

.Verification

. 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

* 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[].
8 changes: 8 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down Expand Up @@ -422,6 +424,7 @@ pub struct Project {
pub tickets: Vec<Arc<TicketQuery>>,
pub trackers: tracker::Config,
pub templates: Template,
pub private_footnote: bool,
}

impl Project {
Expand Down Expand Up @@ -452,12 +455,17 @@ 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 {
base_dir: abs_path,
generated_dir,
tickets,
trackers,
templates,
private_footnote,
})
}
}
Expand Down
5 changes: 1 addition & 4 deletions src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Regex> = Lazy::new(|| Regex::new(r"^BZ#(\d+)$").expect(REGEX_ERROR));
Expand Down
88 changes: 88 additions & 0 deletions src/footnote.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
acorns: Generate an AsciiDoc release notes document from tracking tickets.
Copyright (C) 2023 Marek Suchánek <[email protected]>

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 <https://www.gnu.org/licenses/>.
*/

//! 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;

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<Regex> =
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<bool> {
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);
}
}

Ok(false)
}

/// Estimate if the given file is an AsciiDoc file.
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
}

/// Return `true` if the given file contains the footnote defined
/// in the `FOOTNOTE_ATTR_REGEX` regular expression.
fn file_contains_footnote(path: &Path) -> Result<bool> {
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)
}
9 changes: 8 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ pub mod cli;
mod config;
mod convert;
mod extra_fields;
mod footnote;
mod init;
mod logging;
mod note;
Expand All @@ -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
Expand Down Expand Up @@ -116,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)?;

Expand Down Expand Up @@ -152,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)?;
Expand Down
Loading