diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..664ae88 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,15 @@ +### What's changing? + + + +### See also + + + +### Checklist + +(For maintainers) + +- [ ] Published to crates.io +- [ ] Published to Docker Hub +- [ ] Release created on GitHub diff --git a/.github/workflows/action_test.yml b/.github/workflows/action_test.yml deleted file mode 100644 index 8b54381..0000000 --- a/.github/workflows/action_test.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Test the Github Action - -on: [workflow_dispatch] - -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - id: extract-changelog - uses: sean0x42/markdown-extract@v2 - with: - file: CHANGELOG.md - pattern: 'v2.0.0' - - name: Write output to file - run: | - printf '${{ steps.extract-changelog.outputs.markdown }}' > CHANGELOG-extracted.txt - - uses: actions/upload-artifact@v3 - with: - name: changelog - path: CHANGELOG-extracted.txt diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 7f6ef0b..fc3c551 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -1,15 +1,27 @@ -name: Build & Test - +name: Build and Test on: [push] - jobs: - build: + build-rust: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 + - name: Verify package builds + run: cargo build --verbose + - name: Run tests + run: cargo test --verbose - - name: Build - run: cargo build --verbose + build-docker: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Verify docker image builds + run: docker build . - - name: Run tests - run: cargo test --verbose + test-action: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./ + with: + file: crates/markdown-extract/CHANGELOG.md + pattern: "v2.0.0" diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml index f77032b..1aff681 100644 --- a/.github/workflows/create_release.yml +++ b/.github/workflows/create_release.yml @@ -10,20 +10,20 @@ jobs: runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags/v') steps: - - name: Checkout code - uses: actions/checkout@v3 + - name: Checkout code + uses: actions/checkout@v4 - - id: extract-changelog - uses: sean0x42/markdown-extract@v2 - with: - file: CHANGELOG.md - pattern: ${{ github.ref_name }} + - id: extract-changelog + uses: sean0x42/markdown-extract@v2 + with: + file: crates/markdown-extract/CHANGELOG.md + pattern: ${{ github.ref_name }} - - name: Create release - uses: softprops/action-gh-release@v1 - with: - name: Release ${{ github.ref_name }} - tag_name: ${{ github.ref }} - body: ${{ steps.extract-changelog.outputs.markdown }} - token: ${{ secrets.GITHUB_TOKEN }} - draft: true + - name: Create release + uses: softprops/action-gh-release@v1 + with: + name: Release ${{ github.ref_name }} + tag_name: ${{ github.ref }} + body: ${{ steps.extract-changelog.outputs.markdown }} + token: ${{ secrets.GITHUB_TOKEN }} + draft: true diff --git a/.gitignore b/.gitignore index ae52ee5..81c17b4 100644 --- a/.gitignore +++ b/.gitignore @@ -52,7 +52,7 @@ Temporary Items # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock +# Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..344a867 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,297 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" + +[[package]] +name = "clap" +version = "4.5.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "markdown-extract" +version = "2.0.0" +dependencies = [ + "regex", +] + +[[package]] +name = "markdown-extract-cli" +version = "2.1.0" +dependencies = [ + "anyhow", + "clap", + "markdown-extract", + "regex", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "proc-macro2" +version = "1.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml index f206762..f8d6850 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,22 +1,3 @@ -[package] -name = "markdown-extract" -description = "Extract sections of a markdown file." -version = "2.0.0" -authors = ["Sean Bailey "] -license = "MIT" -repository = "https://github.com/sean0x42/markdown-extract" -keywords = ["markdown", "extract"] -edition = "2018" -exclude = [".github/*"] -readme = "README.md" - -[lib] -path = "src/lib.rs" - -[[bin]] -name = "markdown-extract" -path = "src/bin.rs" - -[dependencies] -regex = "1.3" -structopt = "0.3" +[workspace] +members = ["crates/markdown-extract", "crates/markdown-extract-cli"] +resolver = "2" diff --git a/Dockerfile b/Dockerfile index 2303a78..9bf29b4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,27 +1,23 @@ # Dockerfile for creating statically-linked Rust applications. -# See: https://www.artificialworlds.net/blog/2020/04/22/creating-a-tiny-docker-image-of-a-rust-project/ -# See: https://alexbrand.dev/post/how-to-package-rust-applications-into-minimal-docker-containers/ -# 1: Build the exe -FROM rust:1.57 as builder +# Step 0: Initialise builder +FROM rust:1.81 as builder WORKDIR /usr/src +RUN rustup target add x86_64-unknown-linux-musl +RUN apt-get update +RUN apt-get install -y musl-tools gcc-x86-64-linux-gnu +ENV RUSTFLAGS='-C linker=x86_64-linux-gnu-gcc' -# 1a: Prepare for static linking -RUN apt-get update && \ - apt-get dist-upgrade -y && \ - apt-get install -y musl-tools && \ - rustup target add x86_64-unknown-linux-musl - -# 1b: Download and compile Rust dependencies (and store as a separate Docker layer) -RUN USER=root cargo new markdown-extract +# Step 1: Build and install binary WORKDIR /usr/src/markdown-extract COPY Cargo.toml Cargo.lock ./ -COPY src ./src -RUN cargo install --target x86_64-unknown-linux-musl --path . +COPY crates ./crates +RUN cargo install \ + --target x86_64-unknown-linux-musl \ + --path ./crates/markdown-extract-cli -# 2: Copy the exe and extra files ("static") to an empty Docker image +# Step 2: Copy bin to an empty Docker image FROM scratch COPY --from=builder /usr/local/cargo/bin/markdown-extract . -# COPY static . USER 1000 ENTRYPOINT ["./markdown-extract"] diff --git a/README.md b/README.md index e5748a0..eadeed1 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ -Extract sections of a markdown file with a regular expression! Great for changelogs ;) +Extract sections of a markdown file with a regular expression. ## Usage @@ -39,7 +39,7 @@ If you've got Rust installed on your system, you can simply install `markdown-extract` with Cargo. ```console -$ cargo install markdown-extract +$ cargo install markdown-extract-cli ``` ### Docker @@ -74,9 +74,9 @@ Here is a sample workflow usage: ```yaml - id: extract-changelog - uses: sean0x42/markdown-extract@v2 + uses: sean0x42/markdown-extract@v4 with: - file: CHANGELOG.md + file: crates/markdown-extract/CHANGELOG.md pattern: 'v2.0.0' - name: Write output to file run: | @@ -93,7 +93,7 @@ The action version corresponds to the version of the tool. ## Use Cases -There aren't many, to be honest. +There aren't many, to be honest. 1. Extract patch notes from a `CHANGELOG.md` by version. 2. The talented folks at HashiCorp are using `markdown-extract` to extract API diff --git a/action.yml b/action.yml index 2ed86eb..52efda8 100644 --- a/action.yml +++ b/action.yml @@ -1,31 +1,31 @@ -name: 'Extract Markdown Section' -description: 'Extract sections of a markdown file with a regular expression! Great for changelogs ;)' +name: "Extract Markdown Section" +description: "Extract sections of a markdown file with a regular expression" branding: icon: crosshair color: purple inputs: file: - description: 'The input file' + description: "The input file" required: true pattern: - description: 'Pattern to match against headings' + description: "Pattern to match against headings" required: true case-sensitive: - description: 'Treat pattern as case sensitive' - default: 'false' + description: "Treat pattern as case sensitive" + default: "false" include-all: description: > 'Print all matching sections (don't quit after first match)' - default: 'false' + default: "false" no-print-matched-heading: - description: 'Do not include the matched heading in the output' - default: 'false' + description: "Do not include the matched heading in the output" + default: "false" outputs: markdown: description: The extracted Markdown section body. runs: - using: 'docker' - image: './.github/action/Dockerfile' + using: "docker" + image: "./.github/action/Dockerfile" env: FLAG_CASE_SENSITIVE: ${{ inputs.case-sensitive }} FLAG_INCLUDE_ALL: ${{ inputs.include-all }} diff --git a/crates/markdown-extract-cli/.gitignore b/crates/markdown-extract-cli/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/crates/markdown-extract-cli/.gitignore @@ -0,0 +1 @@ +/target diff --git a/crates/markdown-extract-cli/CHANGELOG.md b/crates/markdown-extract-cli/CHANGELOG.md new file mode 100644 index 0000000..29615b5 --- /dev/null +++ b/crates/markdown-extract-cli/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog + +## v2.1.0 (October 2024) + +- Error messages will now be printed to stderr. This fixes #20 (thanks @zhimsel) +- Updated CLI format + +**Note**: `markdown-extract-cli` has been decoupled from the markdown-extract +Rust library in this release. Change notes prior to this release are available +in the CHANGELOG for `markdown-extract`. diff --git a/crates/markdown-extract-cli/Cargo.toml b/crates/markdown-extract-cli/Cargo.toml new file mode 100644 index 0000000..450f972 --- /dev/null +++ b/crates/markdown-extract-cli/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "markdown-extract-cli" +description = "Extract sections of a markdown file with a regular expression" +version = "2.1.0" +edition = "2021" +authors = ["Sean Bailey "] +license = "MIT" +repository = "https://github.com/sean0x42/markdown-extract" +keywords = ["markdown", "extract"] +readme = "README.md" + +[[bin]] +name = "markdown-extract" +path = "src/main.rs" + +[dependencies] +anyhow = "1.0.89" +clap = { version = "4.5.20", features = ["derive"] } +markdown-extract = { version = "2.0.0", path = "../markdown-extract" } +regex = "1.3" diff --git a/crates/markdown-extract-cli/README.md b/crates/markdown-extract-cli/README.md new file mode 100644 index 0000000..eb7b5e4 --- /dev/null +++ b/crates/markdown-extract-cli/README.md @@ -0,0 +1,43 @@ +
+ +# Markdown Extract CLI + +[![Crates.io](https://img.shields.io/crates/v/markdown-extract)](https://crates.io/crates/markdown-extract) +[![Docker Pulls](https://img.shields.io/docker/pulls/sean0x42/markdown-extract)](https://hub.docker.com/r/sean0x42/markdown-extract) +[![Build & Test](https://github.com/sean0x42/markdown-extract/actions/workflows/build_and_test.yml/badge.svg)](https://github.com/sean0x42/markdown-extract/actions/workflows/build_and_test.yml) + +
+ +Extract sections of a markdown file with a regular expression. + +## Usage + +Given a document called `my-document.md`: + +```markdown +# Welcome + +This is my amazing markdown document. + +## Extract me! + +This section should be pulled out. +``` + +You can extract the second section with the following command: + +```console +$ markdown-extract "Extract me!" my-document.md +## Extract me! + +This section should be pulled out. +``` + +## Installation + +If you've got Rust installed on your system, you can simply install +`markdown-extract` with Cargo. + +```console +$ cargo install markdown-extract-cli +``` diff --git a/crates/markdown-extract-cli/src/main.rs b/crates/markdown-extract-cli/src/main.rs new file mode 100644 index 0000000..d1a3409 --- /dev/null +++ b/crates/markdown-extract-cli/src/main.rs @@ -0,0 +1,74 @@ +use anyhow::{anyhow, bail, Context, Result}; +use clap::Parser; +use markdown_extract::{extract_from_path, MarkdownSection}; +use regex::RegexBuilder; +use std::{ + io::{self, Write}, + path::PathBuf, +}; + +#[derive(Parser)] +#[command(version, about, long_about = None)] +pub struct Cli { + /// Print all matching sections (don't quit after first match) + #[arg(short, long)] + all: bool, + + /// Treat pattern as case sensitive + #[arg(short = 's', long)] + case_sensitive: bool, + + /// Do not include the matched heading in the output + #[arg(short, long)] + no_print_matched_heading: bool, + + /// Pattern to match against headings + #[arg(value_name = "PATTERN")] + pattern: String, + + /// Path to markdown file + #[arg(value_name = "FILE")] + path: PathBuf, +} + +fn main() -> Result<()> { + let cli = Cli::parse(); + + let regex = RegexBuilder::new(&cli.pattern) + .case_insensitive(!cli.case_sensitive) + .size_limit(1024 * 100) // 100 kb + .build() + .unwrap(); + + let matches = extract_from_path(&cli.path, ®ex) + .with_context(|| format!("Unable to extract at path: {}", cli.path.display()))?; + + if matches.len() == 0 { + bail!("No matches found for pattern: {}", cli.pattern); + } + + if !cli.all { + return print_section(&matches[0], cli.no_print_matched_heading); + } + + // match is a reserved keyword + for m in matches { + print_section(&m, cli.no_print_matched_heading)?; + } + + Ok(()) +} + +fn print_section(section: &MarkdownSection, skip_printing_matched_heading: bool) -> Result<()> { + let stdout = io::stdout(); + let mut handle = stdout.lock(); + + for line in section + .iter() + .skip(if skip_printing_matched_heading { 1 } else { 0 }) + { + writeln!(handle, "{}", line).with_context(|| format!("Failed to print line: {}", line))?; + } + + handle.flush().map_err(|err| anyhow!(err)) +} diff --git a/CHANGELOG.md b/crates/markdown-extract/CHANGELOG.md similarity index 89% rename from CHANGELOG.md rename to crates/markdown-extract/CHANGELOG.md index 386703f..3752c17 100644 --- a/CHANGELOG.md +++ b/crates/markdown-extract/CHANGELOG.md @@ -1,8 +1,5 @@ # Changelog -Patch notes are automatically extracted from this changelog whenever a tag is -pushed to the GitHub repository. The heading must start with the tag name. - ## v2.0.0 (January 2021) In this release, `markdown-extract` has been dramatically simplified, and comes diff --git a/crates/markdown-extract/Cargo.toml b/crates/markdown-extract/Cargo.toml new file mode 100644 index 0000000..1952d7e --- /dev/null +++ b/crates/markdown-extract/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "markdown-extract" +description = "Extract sections of a markdown file with a regular expression" +version = "2.0.0" +edition = "2021" +authors = ["Sean Bailey "] +license = "MIT" +repository = "https://github.com/sean0x42/markdown-extract" +keywords = ["markdown", "extract"] +readme = "README.md" + +[lib] +name = "markdown_extract" +crate-type = ["lib"] + +[dependencies] +regex = "1.3" diff --git a/crates/markdown-extract/README.md b/crates/markdown-extract/README.md new file mode 100644 index 0000000..c6f13e7 --- /dev/null +++ b/crates/markdown-extract/README.md @@ -0,0 +1,30 @@ +# markdown-extract + +Extract sections of a markdown file with a regular expression. + +## Usage + +Given a document called `my-document.md`: + +```markdown +# Welcome + +This is my amazing markdown document. + +## Extract me! + +This section should be pulled out. +``` + +You can extract the second section with the following: + +```rust +use markdown_extract::extract_from_path; +use regex::Regex; + +fn main() { + let regex = Regex::new(r"Extract me!").unwrap(); + let extracted = extract_from_path("my-document.md", ®ex).unwrap(); + println!("{}", extracted); +} +``` diff --git a/src/heading.rs b/crates/markdown-extract/src/heading.rs similarity index 100% rename from src/heading.rs rename to crates/markdown-extract/src/heading.rs diff --git a/src/lib.rs b/crates/markdown-extract/src/lib.rs similarity index 93% rename from src/lib.rs rename to crates/markdown-extract/src/lib.rs index b87469c..6ff770b 100644 --- a/src/lib.rs +++ b/crates/markdown-extract/src/lib.rs @@ -4,10 +4,11 @@ mod state; use heading::try_parse_heading; use regex::Regex; use state::State; -use std::fs::File; -use std::io::prelude::*; -use std::io::BufReader; -use std::path::PathBuf; +use std::{ + fs::File, + io::{prelude::*, BufReader}, + path::PathBuf, +}; pub type MarkdownSection = Vec; diff --git a/src/state.rs b/crates/markdown-extract/src/state.rs similarity index 100% rename from src/state.rs rename to crates/markdown-extract/src/state.rs diff --git a/tests/extract.rs b/crates/markdown-extract/tests/extract.rs similarity index 100% rename from tests/extract.rs rename to crates/markdown-extract/tests/extract.rs diff --git a/tests/markdown/heading_in_code_block.md b/crates/markdown-extract/tests/markdown/heading_in_code_block.md similarity index 100% rename from tests/markdown/heading_in_code_block.md rename to crates/markdown-extract/tests/markdown/heading_in_code_block.md diff --git a/tests/markdown/multiple_matches.md b/crates/markdown-extract/tests/markdown/multiple_matches.md similarity index 100% rename from tests/markdown/multiple_matches.md rename to crates/markdown-extract/tests/markdown/multiple_matches.md diff --git a/src/bin.rs b/src/bin.rs deleted file mode 100644 index b2c9193..0000000 --- a/src/bin.rs +++ /dev/null @@ -1,78 +0,0 @@ -mod error; - -use error::NoMatchesError; -use markdown_extract::{extract_from_path, MarkdownSection}; -use regex::RegexBuilder; -use std::error::Error; -use std::path::PathBuf; -use structopt::StructOpt; - -/// Extract sections of a markdown file according to a regular expression. -#[derive(StructOpt)] -#[structopt(name = "markdown-extract")] -pub struct Opts { - /// Print all matching sections (don't quit after first match) - #[structopt(short, long)] - all: bool, - - /// Treat pattern as case sensitive - #[structopt(short = "s", long)] - case_sensitive: bool, - - /// Do not include the matched heading in the output - #[structopt(short, long)] - no_print_matched_heading: bool, - - /// Pattern to match against headings - pattern: String, - - /// Path to markdown file - #[structopt(parse(from_os_str))] - path: PathBuf, -} - -fn print_section(section: &MarkdownSection, no_print_matched_heading: bool) { - let iterator = section - .iter() - .skip(if no_print_matched_heading { 1 } else { 0 }); - - for line in iterator { - println!("{}", line); - } -} - -fn run() -> Result<(), Box> { - let opts = Opts::from_args(); - - let regex = RegexBuilder::new(&opts.pattern) - .case_insensitive(!opts.case_sensitive) - .size_limit(1024 * 100) // 100 kb - .build() - .unwrap(); - - let matches = extract_from_path(&opts.path, ®ex)?; - - if matches.len() == 0 { - return Err(Box::new(NoMatchesError::new())); - } - - if !opts.all { - print_section(&matches[0], opts.no_print_matched_heading); - } else { - for m in matches.iter() { - print_section(&m, opts.no_print_matched_heading); - } - } - - Ok(()) -} - -fn main() { - std::process::exit(match run() { - Ok(_) => 0, - Err(error) => { - println!("Error: {}", error); - 1 - } - }) -} diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index 9758c1f..0000000 --- a/src/error.rs +++ /dev/null @@ -1,20 +0,0 @@ -use std::error::Error; -use std::fmt; - -#[derive(Debug)] -pub struct NoMatchesError; - -impl NoMatchesError { - /// Constructs a new `NoMatchesError` - pub fn new() -> NoMatchesError { - NoMatchesError {} - } -} - -impl fmt::Display for NoMatchesError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "No matches.") - } -} - -impl Error for NoMatchesError {}