From 61fa03db4f450313a3a97332819fbf3c4abb2bd5 Mon Sep 17 00:00:00 2001 From: Sean Bailey Date: Fri, 24 Jan 2020 10:55:08 +1100 Subject: [PATCH 1/5] Draft releases from GitHub actions --- .github/workflows/create_release.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml index b2b753d..9c96f5a 100644 --- a/.github/workflows/create_release.yml +++ b/.github/workflows/create_release.yml @@ -39,4 +39,6 @@ jobs: with: tag_name: ${{ github.ref }} release_name: Release ${{ steps.tags.outputs.value }} - body: ${{ steps.release_notes.outputs.value }} + body: | + ${{ steps.release_notes.outputs.value }} + draft: true From 7df6c93584f6890bf25cafa5d08d6255c2ebd207 Mon Sep 17 00:00:00 2001 From: Sean Bailey Date: Fri, 24 Jan 2020 11:41:51 +1100 Subject: [PATCH 2/5] Create FUNDING.yml --- .github/FUNDING.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..5d2a253 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: sean0x42 # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] From 0969936efe3fba16b20a528af0f5cb2628bf0c58 Mon Sep 17 00:00:00 2001 From: Sean Bailey Date: Wed, 29 Jan 2020 17:33:41 +1100 Subject: [PATCH 3/5] Create matchers module --- src/matchers/mod.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/matchers/mod.rs diff --git a/src/matchers/mod.rs b/src/matchers/mod.rs new file mode 100644 index 0000000..ce0e5d5 --- /dev/null +++ b/src/matchers/mod.rs @@ -0,0 +1,11 @@ +mod regex; +mod simple; + +pub use regex::RegexMatcher; +pub use simple::SimpleMatcher; +use crate::Opts; + +pub trait Matcher { + /// Find any sections within the document that match + pub fn match(document: &Document, options: &Opts) -> Document; +} From fefd0a808f9c77ba9f57bb685187d9f5ddede36f Mon Sep 17 00:00:00 2001 From: Sean Bailey Date: Thu, 30 Jan 2020 19:19:39 +1100 Subject: [PATCH 4/5] Add --regex, and --first flags. Plus bug fixes Fixed an issue with rogue newlines appearing in command output. --- CHANGELOG.md | 16 +++++++++--- src/bin.rs | 55 ++++++++++++++++++++++++++++-------------- src/document.rs | 7 +++--- src/lib.rs | 5 ---- src/matchers/mod.rs | 7 +++--- src/matchers/regex.rs | 24 ++++++++++++++++++ src/matchers/simple.rs | 22 +++++++++++++++++ 7 files changed, 102 insertions(+), 34 deletions(-) delete mode 100644 src/lib.rs create mode 100644 src/matchers/regex.rs create mode 100644 src/matchers/simple.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 391b052..6aec808 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,17 @@ Patch notes are automatically extracted from this changelog whenever a tag is pushed to the GitHub repository. The tag name must match a heading exactly. +## Next Release + +- Add `--regex` flag, which enables the use of regular expressions to search + for section titles. +- Add `--first` flag, which only prints the first matching section. +- Fix an issue where extra newlines where inserted into the final output. + + ## v0.1.1 - - Publish as a binary instead of a library +- Publish as a binary instead of a library ## v0.1.0-alpha @@ -14,6 +22,6 @@ pushed to the GitHub repository. The tag name must match a heading exactly. This version is the initial release of `markdown_extract`! It features the following: - - Extract sections from a markdown document - - Run from the command line - - Use as a Rust library +- Extract sections from a markdown document +- Run from the command line +- Use as a Rust library diff --git a/src/bin.rs b/src/bin.rs index 1a43446..d6db069 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -1,15 +1,32 @@ -use markdown_extract::{Document, Parser, Section}; +pub mod document; +mod matchers; +mod parser; + +use document::{Document, Section}; +use matchers::{Matcher, RegexMatcher, SimpleMatcher}; +use parser::Parser; use std::convert::TryInto; use std::error::Error; use std::fs::File; use std::path::PathBuf; use structopt::StructOpt; -/// Extracts sections of a markdown file. +/// Extract sections of a markdown file. #[derive(StructOpt)] #[structopt(name = "markdown-extract")] -struct Opts { - /// Title is case sensitive +pub struct Opts { + /// Only return the first match + #[structopt(short, long)] + first: bool, + + /// Compile pattern as a regular expression. + /// + /// Documentation for the regex syntax can be found at + /// + #[structopt(short, long)] + regex: bool, + + /// Treat pattern as case sensitive #[structopt(short = "s", long)] case_sensitive: bool, @@ -17,8 +34,8 @@ struct Opts { #[structopt(short, long)] ignore_first_heading: bool, - /// A title to find in section headings - title: String, + /// Pattern to match against section headings + pattern: String, /// Path to markdown file #[structopt(parse(from_os_str))] @@ -35,7 +52,7 @@ fn print_section(document: &Document, section: &Section, ignore_first_heading: b section.title ); } - println!("{}", section.body); + println!("{}", section.body.join("\n")); // Print children for child in §ion.children { @@ -52,17 +69,12 @@ fn run() -> Result<(), Box> { let file = File::open(&opts.path)?; let document = parser.parse_file(file)?; - let matches: Document = document - .iter() - .filter(|section| { - if opts.case_sensitive { - section.title.trim() == opts.title.trim() - } else { - section.title.to_lowercase().trim() == opts.title.to_lowercase().trim() - } - }) - .cloned() - .collect(); + // Match + let matches = if opts.regex { + RegexMatcher::get_matches(&document, &opts) + } else { + SimpleMatcher::get_matches(&document, &opts) + }; // Handle no matches if matches.is_empty() { @@ -70,6 +82,13 @@ fn run() -> Result<(), Box> { return Ok(()); } + // Only print the first match + if opts.first { + // It's okay to use `[0]` here since we check if the doc is empty above + print_section(&document, &matches[0], opts.ignore_first_heading); + return Ok(()); + } + // Print matching sections for section in matches { print_section(&document, §ion, opts.ignore_first_heading); diff --git a/src/document.rs b/src/document.rs index e1fe664..806b909 100644 --- a/src/document.rs +++ b/src/document.rs @@ -11,7 +11,7 @@ pub struct Section { /// Raw markdown body. /// Does not include any child sections. See the `children` property for child content. - pub body: String, + pub body: Vec, /// An optional pointer to a parent section. /// This property should always be `Some`, unless the section is located at the root of the @@ -28,7 +28,7 @@ impl Section { Section { level: 0, title: String::new(), - body: String::new(), + body: Vec::new(), parent: None, children: Vec::new(), } @@ -36,8 +36,7 @@ impl Section { /// Appends the given line to the section's body pub fn append_to_body(&mut self, line: String) { - self.body.push_str(&line); - self.body.push('\n'); + self.body.push(line); } /// Add a child to this section. diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 583ab7c..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod document; -pub mod parser; - -pub use document::{Document, Section}; -pub use parser::Parser; diff --git a/src/matchers/mod.rs b/src/matchers/mod.rs index ce0e5d5..7152769 100644 --- a/src/matchers/mod.rs +++ b/src/matchers/mod.rs @@ -1,11 +1,12 @@ mod regex; mod simple; -pub use regex::RegexMatcher; -pub use simple::SimpleMatcher; +pub use self::regex::RegexMatcher; +use crate::document::Document; use crate::Opts; +pub use simple::SimpleMatcher; pub trait Matcher { /// Find any sections within the document that match - pub fn match(document: &Document, options: &Opts) -> Document; + fn get_matches(document: &Document, opts: &Opts) -> Document; } diff --git a/src/matchers/regex.rs b/src/matchers/regex.rs new file mode 100644 index 0000000..ac8b75d --- /dev/null +++ b/src/matchers/regex.rs @@ -0,0 +1,24 @@ +use super::Matcher; +use crate::document::Document; +use crate::Opts; +use regex::RegexBuilder; + +pub struct RegexMatcher; + +impl Matcher for RegexMatcher { + /// Compile the pattern as a regular expression + fn get_matches(document: &Document, opts: &Opts) -> Document { + // Compile regex for provided pattern + let re = RegexBuilder::new(&opts.pattern) + .case_insensitive(!opts.case_sensitive) + .size_limit(1024 * 100) // 100 kb + .build() + .unwrap(); + + document + .iter() + .filter(|section| re.is_match(§ion.title)) + .cloned() + .collect() + } +} diff --git a/src/matchers/simple.rs b/src/matchers/simple.rs new file mode 100644 index 0000000..ef4ab73 --- /dev/null +++ b/src/matchers/simple.rs @@ -0,0 +1,22 @@ +use super::Matcher; +use crate::document::Document; +use crate::Opts; + +pub struct SimpleMatcher; + +impl Matcher for SimpleMatcher { + /// Performs a simple pattern == title match + fn get_matches(document: &Document, opts: &Opts) -> Document { + document + .iter() + .filter(|section| { + if opts.case_sensitive { + section.title.trim() == opts.pattern.trim() + } else { + section.title.to_lowercase().trim() == opts.pattern.to_lowercase().trim() + } + }) + .cloned() + .collect() + } +} From a2806ee9040d996097408ab572e8765d49e23e1e Mon Sep 17 00:00:00 2001 From: Sean Bailey Date: Thu, 30 Jan 2020 19:25:56 +1100 Subject: [PATCH 5/5] Bump version to 1.0.0 --- CHANGELOG.md | 4 +++- Cargo.toml | 2 +- README.md | 18 ++++++++++-------- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6aec808..00497a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,9 @@ Patch notes are automatically extracted from this changelog whenever a tag is pushed to the GitHub repository. The tag name must match a heading exactly. -## Next Release +## v1.0.0 + +The first proper release of `markdown-extract`! :tada: - Add `--regex` flag, which enables the use of regular expressions to search for section titles. diff --git a/Cargo.toml b/Cargo.toml index e2b4df7..99af3ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "markdown-extract" description = "Extract sections of a markdown file." -version = "0.1.1" +version = "1.0.0" authors = ["Sean Bailey "] license = "MIT" repository = "https://github.com/sean0x42/markdown-extract" diff --git a/README.md b/README.md index 3dd46f0..2c54d38 100644 --- a/README.md +++ b/README.md @@ -17,28 +17,30 @@ View the help guide if you like. ```console $ markdown-extract -h -markdown-extract 0.1.1 -Extracts sections of a markdown file +markdown-extract 1.0.0 +Extract sections of a markdown file USAGE: - markdown-extract [FLAGS] <path> + markdown-extract [FLAGS] <pattern> <path> FLAGS: - -s, --case-sensitive Title is case sensitive + -s, --case-sensitive Treat pattern as case sensitive + -f, --first Only return the first match -h, --help Prints help information -i, --ignore-first-heading Do not include the top level section heading + -r, --regex Compile pattern as a regular expression -V, --version Prints version information ARGS: - <title> A title to find in section headings - <path> Path to markdown file + <pattern> Pattern to match against section headings + <path> Path to markdown file ``` Then extract matching sections in a markdown file. ```console -$ markdown-extract v0.1.1 CHANGELOG.md -## v0.1.1 +$ markdown-extract --fr "^v1" CHANGELOG.md +## v1.0.0 ... ```