From ce10aae9f8cd2edc9ead2628da2c3f620617f92a Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Thu, 15 Aug 2024 20:19:40 +0200 Subject: [PATCH 1/8] Next version --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b446f55c3d..dd9e84c390 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## 0.20.0 (unreleased) + ## 0.19.2 (2024-08-15) - Fix some of YAML date parsing From 00981192debeeb57ef8c753750490caaaf404730 Mon Sep 17 00:00:00 2001 From: Pranit Bauva Date: Mon, 26 Aug 2024 14:51:17 +0530 Subject: [PATCH 2/8] exclude paginated pages in sitemap (#2555) This fixes #2527. --- components/config/src/config/mod.rs | 18 ++++++++++++++++++ components/site/src/sitemap.rs | 12 +++++++----- .../getting-started/configuration.md | 3 +++ .../documentation/templates/pagination.md | 6 ++++++ 4 files changed, 34 insertions(+), 5 deletions(-) diff --git a/components/config/src/config/mod.rs b/components/config/src/config/mod.rs index 88d949fc21..a1f1cfaf41 100644 --- a/components/config/src/config/mod.rs +++ b/components/config/src/config/mod.rs @@ -29,6 +29,13 @@ pub enum Mode { Check, } +#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum ExcludePaginatedPagesInSitemap { + None, + All, +} + #[derive(Clone, Debug, Deserialize)] #[serde(default, deny_unknown_fields)] pub struct Config { @@ -102,6 +109,8 @@ pub struct Config { pub generate_sitemap: bool, /// Enables the generation of robots.txt pub generate_robots_txt: bool, + /// Whether to exclude paginated pages in sitemap; can take values "none", "all" + pub exclude_paginated_pages_in_sitemap: ExcludePaginatedPagesInSitemap, } #[derive(Serialize)] @@ -123,6 +132,7 @@ pub struct SerializedConfig<'a> { search: search::SerializedSearch<'a>, generate_sitemap: bool, generate_robots_txt: bool, + exclude_paginated_pages_in_sitemap: ExcludePaginatedPagesInSitemap, } impl Config { @@ -287,6 +297,10 @@ impl Config { self.mode == Mode::Check } + pub fn should_exclude_paginated_pages_in_sitemap(&self) -> bool { + self.exclude_paginated_pages_in_sitemap == ExcludePaginatedPagesInSitemap::All + } + pub fn enable_serve_mode(&mut self) { self.mode = Mode::Serve; } @@ -340,6 +354,7 @@ impl Config { search: self.search.serialize(), generate_sitemap: self.generate_sitemap, generate_robots_txt: self.generate_robots_txt, + exclude_paginated_pages_in_sitemap: self.exclude_paginated_pages_in_sitemap, } } } @@ -405,6 +420,7 @@ impl Default for Config { extra: HashMap::new(), generate_sitemap: true, generate_robots_txt: true, + exclude_paginated_pages_in_sitemap: ExcludePaginatedPagesInSitemap::None, } } } @@ -1066,4 +1082,6 @@ base_url = "example.com" let config = Config::parse(config).unwrap(); assert!(config.generate_robots_txt); } + + // TODO: add a test for excluding paginated pages } diff --git a/components/site/src/sitemap.rs b/components/site/src/sitemap.rs index 2d9960741b..d6c94ecd5a 100644 --- a/components/site/src/sitemap.rs +++ b/components/site/src/sitemap.rs @@ -83,10 +83,12 @@ pub fn find_entries<'a>( } if let Some(paginate_by) = s.paginate_by() { - let number_pagers = (s.pages.len() as f64 / paginate_by as f64).ceil() as isize; - for i in 1..=number_pagers { - let permalink = format!("{}{}/{}/", s.permalink, s.meta.paginate_path, i); - entries.insert(SitemapEntry::new(Cow::Owned(permalink), &None)); + if !config.should_exclude_paginated_pages_in_sitemap() { + let number_pagers = (s.pages.len() as f64 / paginate_by as f64).ceil() as isize; + for i in 1..=number_pagers { + let permalink = format!("{}{}/{}/", s.permalink, s.meta.paginate_path, i); + entries.insert(SitemapEntry::new(Cow::Owned(permalink), &None)); + } } } } @@ -100,7 +102,7 @@ pub fn find_entries<'a>( for item in &taxonomy.items { entries.insert(SitemapEntry::new(Cow::Borrowed(&item.permalink), &None)); - if taxonomy.kind.is_paginated() { + if taxonomy.kind.is_paginated() && !config.should_exclude_paginated_pages_in_sitemap() { let number_pagers = (item.pages.len() as f64 / taxonomy.kind.paginate_by.unwrap() as f64) .ceil() as isize; diff --git a/docs/content/documentation/getting-started/configuration.md b/docs/content/documentation/getting-started/configuration.md index 2565e0913f..7b5f439bf6 100644 --- a/docs/content/documentation/getting-started/configuration.md +++ b/docs/content/documentation/getting-started/configuration.md @@ -67,6 +67,9 @@ ignored_static = [] # When set to "true", a feed is automatically generated. generate_feeds = false +# When set to "all", paginated pages are not a part of the sitemap, default is "none" +exclude_paginated_pages_in_sitemap = "none" + # The filenames to use for the feeds. Used as the template filenames, too. # Defaults to ["atom.xml"], which has a built-in template that renders an Atom 1.0 feed. # There is also a built-in template "rss.xml" that renders an RSS 2.0 feed. diff --git a/docs/content/documentation/templates/pagination.md b/docs/content/documentation/templates/pagination.md index e1851bdce4..ad4c956cca 100644 --- a/docs/content/documentation/templates/pagination.md +++ b/docs/content/documentation/templates/pagination.md @@ -52,6 +52,12 @@ A paginated taxonomy gets two variables aside from the `paginator` variable: See the [taxonomies page](@/documentation/templates/taxonomies.md) for a detailed version of the types. +## SEO + +It is preferable to not include paginated pages in sitemap since they are non-canonical pages. +To exclude paginated pages in sitemap, set the +`exclude_paginated_pages_in_sitemap` as `all` in `config.toml`. + ## Example Here is an example from a theme on how to use pagination on a page (`index.html` in this case): From 481135f93a950a8e89f97e2192c5860895a03b77 Mon Sep 17 00:00:00 2001 From: Henrik Gerdes Date: Fri, 13 Sep 2024 09:29:54 +0200 Subject: [PATCH 3/8] feat: build relases on gh-actions & support linux arm64 (#2629) * feat: port zola release build to gh actions & support linux arm64 Signed-off-by: Henrik Gerdes * fix: add buildx support for multi-arch docker images Signed-off-by: Henrik Gerdes * fix: pin gh release action to sha & allow pre-releases Signed-off-by: Henrik Gerdes * fix: use env in gh action for linux arm build Signed-off-by: Henrik Gerdes * chore: switch to dtolnay/rust-toolchain action for rust setup Signed-off-by: Henrik Gerdes * fix: windows archive step Signed-off-by: Henrik Gerdes --------- Signed-off-by: Henrik Gerdes --- .github/workflows/build-and-test.yml | 66 +++++++++++ .github/workflows/cd-workflow.yml | 50 -------- .github/workflows/docs.yml | 2 +- .github/workflows/release.yml | 165 +++++++++++++++++++++++++++ Dockerfile | 21 +++- 5 files changed, 248 insertions(+), 56 deletions(-) create mode 100644 .github/workflows/build-and-test.yml delete mode 100644 .github/workflows/cd-workflow.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml new file mode 100644 index 0000000000..784fc28593 --- /dev/null +++ b/.github/workflows/build-and-test.yml @@ -0,0 +1,66 @@ +name: Build & Test + +on: + push: + branches: ["*"] + +env: + # Cross-compilation for aarch64 requires a different linker + CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc + +permissions: + contents: read + +jobs: + Tests: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + target: + - x86_64-unknown-linux-gnu + - aarch64-unknown-linux-gnu + - x86_64-pc-windows-msvc + - x86_64-apple-darwin + - aarch64-apple-darwin + rustup_toolchain: [stable] + include: + - os: windows-2022 + target: x86_64-pc-windows-msvc + - os: ubuntu-20.04 + target: x86_64-unknown-linux-gnu + - os: ubuntu-20.04 + target: aarch64-unknown-linux-gnu + - os: macos-13 + target: x86_64-apple-darwin + - os: macos-14 + target: aarch64-apple-darwin + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ matrix.rustup_toolchain }} + + - name: Install Rust crosscompile tools + if: ${{ contains(matrix.target, 'aarch64-unknown-linux-gnu') }} + run: | + sudo apt-get update -y + sudo apt-get install -y make g++ libssl-dev gcc-aarch64-linux-gnu + rustup target add aarch64-unknown-linux-gnu + + - name: Cargo build (Native TLS) + run: | + cargo build --all --no-default-features --features=native-tls + cargo clean + + - name: Cargo build (Rust TLS) + run: cargo build --all + + - name: Cargo test + run: cargo test --all + + - name: Cargo fmt + run: cargo fmt --check diff --git a/.github/workflows/cd-workflow.yml b/.github/workflows/cd-workflow.yml deleted file mode 100644 index 6d20ea7664..0000000000 --- a/.github/workflows/cd-workflow.yml +++ /dev/null @@ -1,50 +0,0 @@ -# Mostly copied from https://docs.github.com/en/packages/managing-github-packages-using-github-actions-workflows/publishing-and-installing-a-package-with-github-actions#publishing-a-package-using-an-action -# Main difference is the push filter on the tag. -# -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -name: Create and publish a Docker image - -on: - push: - tags: [ 'v*.*.*' ] - -env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} - -jobs: - build-and-push-image: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - - name: Log in to the Container registry - uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - flavor: latest=false - - - name: Build and push Docker image - uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc - with: - context: . - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} \ No newline at end of file diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 1f1db2fb77..ba06fdeccf 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -20,7 +20,7 @@ jobs: env: BUILD_DIR: docs/ BUILD_ONLY: true - + build_and_deploy: runs-on: ubuntu-latest if: github.ref == 'refs/heads/master' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..ae79c850ba --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,165 @@ +name: Release + +on: + push: + tags: ["v*.*.*"] + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + # Cross-compilation for aarch64 requires a different linker + CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc + +permissions: + contents: read + +jobs: + Release-Build: + runs-on: ${{ matrix.os }} + permissions: + contents: read + attestations: write + id-token: write + strategy: + matrix: + target: + - x86_64-unknown-linux-gnu + - aarch64-unknown-linux-gnu + - x86_64-pc-windows-msvc + - x86_64-apple-darwin + - aarch64-apple-darwin + rustup_toolchain: [stable] + include: + - os: windows-2022 + target: x86_64-pc-windows-msvc + - os: ubuntu-20.04 + target: x86_64-unknown-linux-gnu + - os: ubuntu-20.04 + target: aarch64-unknown-linux-gnu + - os: macos-13 + target: x86_64-apple-darwin + - os: macos-14 + target: aarch64-apple-darwin + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ matrix.rustup_toolchain }} + + - name: Install Rust crosscompile tools + if: ${{ contains(matrix.target, 'aarch64-unknown-linux-gnu') }} + run: | + sudo apt-get update -y + sudo apt-get install -y make g++ libssl-dev gcc-aarch64-linux-gnu + rustup target add aarch64-unknown-linux-gnu + + - name: Cargo build + run: cargo build --release --target ${{ matrix.target }} + + - name: Archive (UNIX) + run: | + mkdir -p artifacts + cp -av target/${{ matrix.target }}/release/zola . + tar -czf ${{ github.event.repository.name }}-${{ github.ref_name }}-${{ matrix.target }}.tar.gz zola + if: ${{ ! startsWith(matrix.os, 'windows') }} + + - name: Archive (Windows) + run: | + mkdir -p artifacts + cp target/${{ matrix.target }}/release/zola.exe . + Compress-Archive zola.exe ${{ github.event.repository.name }}-${{ github.ref_name }}-${{ matrix.target }}.zip + if: ${{ startsWith(matrix.os, 'windows') }} + + - name: Attest Build Provenance + uses: actions/attest-build-provenance@v1 + continue-on-error: true + with: + subject-path: ${{ github.event.repository.name }}-${{ github.ref_name }}-${{ matrix.target }}.* + + - uses: actions/upload-artifact@v4 + with: + name: ${{ github.event.repository.name }}-${{ github.ref_name }}-${{ matrix.target }} + path: ${{ github.event.repository.name }}-${{ github.ref_name }}-${{ matrix.target }}.* + if-no-files-found: error + retention-days: 7 + + Release: + needs: [Release-Build] + runs-on: ubuntu-22.04 + permissions: + contents: write + + steps: + - name: Ensure artifacts dir exists + run: mkdir -p artifacts + + - name: Download Artifact + uses: actions/download-artifact@v4 + with: + path: artifacts + merge-multiple: true + + - name: Release + uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191 + with: + name: ${{ github.ref_name }} + tag_name: ${{ github.ref_name }} + generate_release_notes: true + fail_on_unmatched_files: true + body: | + Welcome to this new release of Zola ${{ github.ref_name }}! + + All artifacts are signed with this repos identity using Sigstore. + You can verify the signatures using the `GitHub` CLI. + + ```shell + gh attestation verify --owner ${{ github.repository_owner }} + ``` + token: ${{ secrets.GITHUB_TOKEN }} + prerelease: ${{ contains(github.ref, '-pre') }} + files: artifacts/* + + Release-Container-Image: + needs: [Release] + runs-on: ubuntu-22.04 + permissions: + contents: read + packages: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf + + - name: Setup Docker buildx + uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db + + - name: Log in to the Container registry + uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + flavor: latest=false + + - name: Build and push Docker image + uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc + with: + context: . + push: true + build-args: | + USE_GH_RELEASE=true + ZOLA_RELEASE_VERSION=${{ github.ref_name }} + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/Dockerfile b/Dockerfile index 87b3ec247b..0741cc93c4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,26 @@ FROM rust:slim-bookworm AS builder +ARG USE_GH_RELEASE=false +ARG ZOLA_RELEASE_VERSION=latest RUN apt-get update -y && \ - apt-get install -y make g++ libssl-dev && \ - rustup target add x86_64-unknown-linux-gnu + apt-get install -y pkg-config make g++ libssl-dev curl jq tar gzip WORKDIR /app COPY . . -RUN cargo build --release --target x86_64-unknown-linux-gnu - +RUN if [ "${USE_GH_RELEASE}" = "true" ]; then \ + if [ "${ZOLA_RELEASE_VERSION}" = "latest" ]; then \ + export ZOLA_VERSION=$(curl -sL https://api.github.com/repos/getzola/zola/releases/latest | jq -r .name); \ + else \ + export ZOLA_VERSION="${ZOLA_RELEASE_VERSION}"; \ + fi && \ + curl -sL --fail --output zola.tar.gz https://github.com/getzola/zola/releases/download/${ZOLA_VERSION}/zola-${ZOLA_VERSION}-$(uname -m)-unknown-linux-gnu.tar.gz && \ + tar -xzvf zola.tar.gz zola; \ + else \ + cargo build --release && \ + cp target/$(uname -m)-unknown-linux-gnu/release/zola zola; \ + fi && ./zola --version FROM gcr.io/distroless/cc-debian12 -COPY --from=builder /app/target/x86_64-unknown-linux-gnu/release/zola /bin/zola +COPY --from=builder /app/zola /bin/zola ENTRYPOINT [ "/bin/zola" ] From 45c558dec12c22cb526f431418abbae36399aa87 Mon Sep 17 00:00:00 2001 From: xixishidibei Date: Wed, 18 Sep 2024 23:11:40 +0800 Subject: [PATCH 4/8] chore: add missing symbol (#2641) Signed-off-by: xixishidibei --- components/markdown/src/shortcode/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/markdown/src/shortcode/mod.rs b/components/markdown/src/shortcode/mod.rs index 4239ed4d37..92ca6641df 100644 --- a/components/markdown/src/shortcode/mod.rs +++ b/components/markdown/src/shortcode/mod.rs @@ -19,7 +19,7 @@ pub fn extract_shortcodes( if let Some(def) = definitions.get(&sc.name) { sc.tera_name = def.tera_name.clone(); } else { - return Err(Error::msg(format!("Found usage of a shortcode named `{}` but we do not know about. Make sure it's not a typo and that a field name `{}.{{html,md}} exists in the `templates/shortcodes` directory.", sc.name, sc.name))); + return Err(Error::msg(format!("Found usage of a shortcode named `{}` but we do not know about. Make sure it's not a typo and that a field name `{}.{{html,md}}` exists in the `templates/shortcodes` directory.", sc.name, sc.name))); } } From 6798b6eadfa263ab74509aedf87d04ccf6a87165 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Sun, 22 Sep 2024 15:13:06 +0200 Subject: [PATCH 5/8] remove square brackets --- components/markdown/src/markdown.rs | 4 ++-- .../markdown__markdown__tests__def_before_use.snap | 2 +- ..._markdown__tests__footnote_inside_footnote.snap | 4 ++-- .../markdown__markdown__tests__multiple_refs.snap | 2 +- ...down__markdown__tests__reordered_footnotes.snap | 2 +- ...markdown__markdown__tests__single_footnote.snap | 2 +- .../markdown__github_style_footnotes.snap | 14 +++++++------- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/components/markdown/src/markdown.rs b/components/markdown/src/markdown.rs index 8bdc778e22..41a59a7795 100644 --- a/components/markdown/src/markdown.rs +++ b/components/markdown/src/markdown.rs @@ -277,7 +277,7 @@ fn convert_footnotes_to_github_style(old_events: &mut Vec) { // nr is a number of references to this footnote let (n, nr) = footnote_numbers.entry(name.clone()).or_insert((n, 0usize)); *nr += 1; - let reference = Event::Html(format!(r##"[{n}]"##).into()); + let reference = Event::Html(format!(r##"{n}"##).into()); if footnote_bodies_stack.is_empty() { // we are in the main text, just output the reference @@ -341,7 +341,7 @@ fn convert_footnotes_to_github_style(old_events: &mut Vec) { // // HTML: // - //

five [1].

+ //

five 1.

// //
    //
  1. diff --git a/components/markdown/src/snapshots/markdown__markdown__tests__def_before_use.snap b/components/markdown/src/snapshots/markdown__markdown__tests__def_before_use.snap index 57e3a922ad..698d689fcd 100644 --- a/components/markdown/src/snapshots/markdown__markdown__tests__def_before_use.snap +++ b/components/markdown/src/snapshots/markdown__markdown__tests__def_before_use.snap @@ -2,7 +2,7 @@ source: components/markdown/src/markdown.rs expression: html --- -

    There is footnote definition?[1]

    +

    There is footnote definition?1


    1. It's before the reference.

      diff --git a/components/markdown/src/snapshots/markdown__markdown__tests__footnote_inside_footnote.snap b/components/markdown/src/snapshots/markdown__markdown__tests__footnote_inside_footnote.snap index 6b5e28d476..58a2d9b224 100644 --- a/components/markdown/src/snapshots/markdown__markdown__tests__footnote_inside_footnote.snap +++ b/components/markdown/src/snapshots/markdown__markdown__tests__footnote_inside_footnote.snap @@ -2,10 +2,10 @@ source: components/markdown/src/markdown.rs expression: html --- -

      This text has a footnote[1]

      +

      This text has a footnote1


      1. -

        But the footnote has another footnote[2].

        +

        But the footnote has another footnote2.

      2. That's it.

        diff --git a/components/markdown/src/snapshots/markdown__markdown__tests__multiple_refs.snap b/components/markdown/src/snapshots/markdown__markdown__tests__multiple_refs.snap index 1f7eaff186..d53e9ee335 100644 --- a/components/markdown/src/snapshots/markdown__markdown__tests__multiple_refs.snap +++ b/components/markdown/src/snapshots/markdown__markdown__tests__multiple_refs.snap @@ -2,7 +2,7 @@ source: components/markdown/src/markdown.rs expression: html --- -

        This text has two[1] identical footnotes[1]

        +

        This text has two1 identical footnotes1


        1. So one is present. ↩2

          diff --git a/components/markdown/src/snapshots/markdown__markdown__tests__reordered_footnotes.snap b/components/markdown/src/snapshots/markdown__markdown__tests__reordered_footnotes.snap index 865a344e5c..9fddfea392 100644 --- a/components/markdown/src/snapshots/markdown__markdown__tests__reordered_footnotes.snap +++ b/components/markdown/src/snapshots/markdown__markdown__tests__reordered_footnotes.snap @@ -2,7 +2,7 @@ source: components/markdown/src/markdown.rs expression: html --- -

          This text has two[1] footnotes[2]

          +

          This text has two1 footnotes2


          1. But they are

            diff --git a/components/markdown/src/snapshots/markdown__markdown__tests__single_footnote.snap b/components/markdown/src/snapshots/markdown__markdown__tests__single_footnote.snap index 34a7f2d3e6..604489ab7f 100644 --- a/components/markdown/src/snapshots/markdown__markdown__tests__single_footnote.snap +++ b/components/markdown/src/snapshots/markdown__markdown__tests__single_footnote.snap @@ -2,7 +2,7 @@ source: components/markdown/src/markdown.rs expression: html --- -

            This text has a footnote[1]

            +

            This text has a footnote1


            1. But it is meaningless.

              diff --git a/components/markdown/tests/snapshots/markdown__github_style_footnotes.snap b/components/markdown/tests/snapshots/markdown__github_style_footnotes.snap index de55e63fb7..07d7fc5f63 100644 --- a/components/markdown/tests/snapshots/markdown__github_style_footnotes.snap +++ b/components/markdown/tests/snapshots/markdown__github_style_footnotes.snap @@ -2,12 +2,12 @@ source: components/markdown/tests/markdown.rs expression: body --- -

              This text has a footnote[1]

              -

              This text has two[2] footnotes[3].

              -

              There is footnote definition?[4]

              -

              This text has two[5] identical footnotes[5]

              -

              This text has a footnote[6]

              -

              Footnotes can also be referenced with identifiers[8].

              +

              This text has a footnote1

              +

              This text has two2 footnotes3.

              +

              There is footnote definition?4

              +

              This text has two5 identical footnotes5

              +

              This text has a footnote6

              +

              Footnotes can also be referenced with identifiers8.


              1. But it is meaningless.

                @@ -25,7 +25,7 @@ expression: body

                So one is present. ↩2

              2. -

                But the footnote has another footnote[7].

                +

                But the footnote has another footnote7.

              3. That's it.

                From ead17d0a3a20bfb67043a076c061b35ae6b6ddea Mon Sep 17 00:00:00 2001 From: Sam Wilson <57262657+SamWilsn@users.noreply.github.com> Date: Wed, 25 Sep 2024 13:55:49 -0400 Subject: [PATCH 6/8] Allow treating a missing highlight language as error (#2642) --- components/config/src/config/markup.rs | 3 +++ components/markdown/src/codeblock/mod.rs | 18 ++++++++++++------ components/markdown/src/markdown.rs | 8 +++++++- .../getting-started/configuration.md | 3 +++ 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/components/config/src/config/markup.rs b/components/config/src/config/markup.rs index 580a665ae6..9ea5b2337b 100644 --- a/components/config/src/config/markup.rs +++ b/components/config/src/config/markup.rs @@ -27,6 +27,8 @@ pub struct ThemeCss { pub struct Markdown { /// Whether to highlight all code blocks found in markdown files. Defaults to false pub highlight_code: bool, + /// Emit an error for missing highlight languages. Defaults to false + pub error_on_missing_highlight: bool, /// Which themes to use for code highlighting. See Readme for supported themes /// Defaults to "base16-ocean-dark" pub highlight_theme: String, @@ -198,6 +200,7 @@ impl Default for Markdown { fn default() -> Markdown { Markdown { highlight_code: false, + error_on_missing_highlight: false, highlight_theme: DEFAULT_HIGHLIGHT_THEME.to_owned(), highlight_themes_css: Vec::new(), render_emoji: false, diff --git a/components/markdown/src/codeblock/mod.rs b/components/markdown/src/codeblock/mod.rs index 5e297926ff..fd0c1d93f6 100644 --- a/components/markdown/src/codeblock/mod.rs +++ b/components/markdown/src/codeblock/mod.rs @@ -3,6 +3,7 @@ mod highlight; use std::ops::RangeInclusive; +use errors::{bail, Result}; use libs::syntect::util::LinesWithEndings; use crate::codeblock::highlight::SyntaxHighlighter; @@ -75,14 +76,19 @@ impl<'config> CodeBlock<'config> { config: &'config Config, // path to the current file if there is one, to point where the error is path: Option<&'config str>, - ) -> (Self, String) { + ) -> Result<(Self, String)> { let syntax_and_theme = resolve_syntax_and_theme(fence.language, config); if syntax_and_theme.source == HighlightSource::NotFound && config.markdown.highlight_code { let lang = fence.language.unwrap(); - if let Some(p) = path { - eprintln!("Warning: Highlight language {} not found in {}", lang, p); + let msg = if let Some(p) = path { + format!("Highlight language {} not found in {}", lang, p) } else { - eprintln!("Warning: Highlight language {} not found", lang); + format!("Highlight language {} not found", lang) + }; + if config.markdown.error_on_missing_highlight { + bail!(msg); + } else { + eprintln!("Warning: {}", msg); } } let highlighter = SyntaxHighlighter::new(config.markdown.highlight_code, syntax_and_theme); @@ -93,7 +99,7 @@ impl<'config> CodeBlock<'config> { highlighter.pre_class(), fence.line_numbers, ); - ( + Ok(( Self { highlighter, line_numbers: fence.line_numbers, @@ -102,7 +108,7 @@ impl<'config> CodeBlock<'config> { hide_lines: fence.hide_lines, }, html_start, - ) + )) } pub fn highlight(&mut self, content: &str) -> String { diff --git a/components/markdown/src/markdown.rs b/components/markdown/src/markdown.rs index 41a59a7795..16210b3841 100644 --- a/components/markdown/src/markdown.rs +++ b/components/markdown/src/markdown.rs @@ -559,7 +559,13 @@ pub fn markdown_to_html( cmark::CodeBlockKind::Fenced(fence_info) => FenceSettings::new(fence_info), _ => FenceSettings::new(""), }; - let (block, begin) = CodeBlock::new(fence, context.config, path); + let (block, begin) = match CodeBlock::new(fence, context.config, path) { + Ok(cb) => cb, + Err(e) => { + error = Some(e); + break; + } + }; code_block = Some(block); events.push(Event::Html(begin.into())); } diff --git a/docs/content/documentation/getting-started/configuration.md b/docs/content/documentation/getting-started/configuration.md index 7b5f439bf6..0591b021d5 100644 --- a/docs/content/documentation/getting-started/configuration.md +++ b/docs/content/documentation/getting-started/configuration.md @@ -114,6 +114,9 @@ generate_robots_txt = true # When set to "true", all code blocks are highlighted. highlight_code = false +# When set to "true", missing highlight languages are treated as errors. Defaults to false. +error_on_missing_highlight = false + # A list of directories used to search for additional `.sublime-syntax` and `.tmTheme` files. extra_syntaxes_and_themes = [] From 896a80572baec5b2bebc5ab90bb1c1b6da5402ba Mon Sep 17 00:00:00 2001 From: ltdk Date: Sat, 20 Jul 2024 20:03:27 -0400 Subject: [PATCH 7/8] Include unrendered pages in sections, add render property to templates --- components/content/src/library.rs | 6 +++--- components/content/src/ser.rs | 4 ++++ docs/content/documentation/templates/pages-sections.md | 3 +++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/components/content/src/library.rs b/components/content/src/library.rs index 990de93eac..95bbf6e867 100644 --- a/components/content/src/library.rs +++ b/components/content/src/library.rs @@ -99,6 +99,9 @@ impl Library { .get_mut(&self.taxo_name_to_slug[taxa_name]) .expect("taxa not found"); + if !page.meta.render { + continue; + } if !taxa_def.contains_key(term) { taxa_def.insert(term.to_string(), Vec::new()); } @@ -295,9 +298,6 @@ impl Library { // Then once we took care of the sections, we find the pages of each section for (path, page) in self.pages.iter_mut() { - if !page.meta.render { - continue; - } let parent_filename = &index_filename_by_lang[&page.lang]; add_translation(&page.file.canonical, path); let mut parent_section_path = page.file.parent.join(parent_filename); diff --git a/components/content/src/ser.rs b/components/content/src/ser.rs index 919d0d1bb6..bdc5ff401c 100644 --- a/components/content/src/ser.rs +++ b/components/content/src/ser.rs @@ -64,6 +64,7 @@ pub struct SerializingPage<'a> { word_count: Option, reading_time: Option, assets: &'a [String], + render: bool, draft: bool, lang: &'a str, lower: Option>>, @@ -128,6 +129,7 @@ impl<'a> SerializingPage<'a> { word_count: page.word_count, reading_time: page.reading_time, assets: &page.serialized_assets, + render: page.meta.render, draft: page.meta.draft, lang: &page.lang, lower, @@ -144,6 +146,7 @@ pub struct SerializingSection<'a> { colocated_path: &'a Option, content: &'a str, permalink: &'a str, + render: bool, draft: bool, ancestors: &'a [String], title: &'a Option, @@ -207,6 +210,7 @@ impl<'a> SerializingSection<'a> { relative_path: §ion.file.relative, colocated_path: §ion.file.colocated_path, ancestors: §ion.ancestors, + render: section.meta.render, draft: section.meta.draft, content: §ion.content, permalink: §ion.permalink, diff --git a/docs/content/documentation/templates/pages-sections.md b/docs/content/documentation/templates/pages-sections.md index 97f9559614..1d5f123cfc 100644 --- a/docs/content/documentation/templates/pages-sections.md +++ b/docs/content/documentation/templates/pages-sections.md @@ -23,6 +23,7 @@ updated: String?; slug: String; path: String; authors: Array; +render: Bool; draft: Bool; // the path, split on '/' components: Array; @@ -77,6 +78,8 @@ content: String; title: String?; description: String?; path: String; +render: Bool; +draft: Bool; // the path, split on '/' components: Array; permalink: String; From 4dc73537a199fec94f1e12581c831dbeee81f2b2 Mon Sep 17 00:00:00 2001 From: ltdk Date: Wed, 24 Jul 2024 18:04:18 -0400 Subject: [PATCH 8/8] Add additional tests for unrendered pages/sections: * They can be accessed through templates directly and through sections * Unrendered pages are not added to sitemaps * Unrendered pages are not added to taxonomies --- components/site/tests/common.rs | 17 ++++- components/site/tests/site.rs | 93 +++++++++++++++++------- test_site/content/posts/access-render.md | 9 +++ test_site/content/posts/render.md | 4 + test_site/templates/access_render.html | 15 ++++ 5 files changed, 108 insertions(+), 30 deletions(-) create mode 100644 test_site/content/posts/access-render.md create mode 100644 test_site/templates/access_render.html diff --git a/components/site/tests/common.rs b/components/site/tests/common.rs index f5caaf1824..267bdf8019 100644 --- a/components/site/tests/common.rs +++ b/components/site/tests/common.rs @@ -22,7 +22,7 @@ macro_rules! file_exists { #[macro_export] macro_rules! file_contains { - ($root: expr, $path: expr, $text: expr) => {{ + (@impl $root: expr, $path: expr) => {{ use std::io::prelude::*; let mut path = $root.clone(); for component in $path.split('/') { @@ -31,11 +31,24 @@ macro_rules! file_contains { let mut file = std::fs::File::open(&path).expect(&format!("Failed to open {:?}", $path)); let mut s = String::new(); file.read_to_string(&mut s).unwrap(); - println!("{}", s); + println!("{path:?} {s}"); + s + }}; + ($root: expr, $path: expr, $text: expr) => {{ + let s = file_contains!(@impl $root, $path); s.contains($text) }}; } +#[macro_export] +macro_rules! file_contains_regex { + ($root: expr, $path: expr, $pat: expr) => {{ + let s = file_contains!(@impl $root, $path); + let re = libs::regex::Regex::new($pat).unwrap(); + re.is_match(&s) + }}; +} + /// We return the tmpdir otherwise it would get out of scope and be deleted /// The tests can ignore it if they dont need it by prefixing it with a `_` pub fn build_site(name: &str) -> (Site, TempDir, PathBuf) { diff --git a/components/site/tests/site.rs b/components/site/tests/site.rs index bf0811e15a..f86181caab 100644 --- a/components/site/tests/site.rs +++ b/components/site/tests/site.rs @@ -21,7 +21,7 @@ fn can_parse_site() { let library = site.library.read().unwrap(); // Correct number of pages (sections do not count as pages, draft are ignored) - assert_eq!(library.pages.len(), 36); + assert_eq!(library.pages.len(), 37); let posts_path = path.join("content").join("posts"); // Make sure the page with a url doesn't have any sections @@ -44,7 +44,7 @@ fn can_parse_site() { let posts_section = library.sections.get(&posts_path.join("_index.md")).unwrap(); assert_eq!(posts_section.subsections.len(), 2); - assert_eq!(posts_section.pages.len(), 10); // 11 with 1 draft == 10 + assert_eq!(posts_section.pages.len(), 12); // 13 with 1 draft == 12 assert_eq!(posts_section.ancestors, vec![index_section.file.relative.clone()]); // Make sure we remove all the pwd + content from the sections @@ -155,6 +155,33 @@ fn can_build_site_without_live_reload() { "posts/tutorials/devops/nix.md" )); + assert!(file_exists!(public, "posts/access-render/index.html")); + + // render = false pages can still be accessed directly + assert!(file_contains!( + public, + "posts/access-render/index.html", + "Path of unrendered page: /posts/render/" + )); + // render = false pages can still be accessed through sections + assert!(file_contains_regex!( + public, + "posts/access-render/index.html", + r#"Pages in section with unrendered page:
                  (
                • [^>]+
                • )*
                • /posts/render/
                • "# + )); + // render = false sections can still be accessed directly + assert!(file_contains!( + public, + "posts/access-render/index.html", + "Pages in unrendered section:
                  • " + )); + // render = false pages don't show up in taxonomies + assert!(!file_contains!(public, "podcast-authors/some-person/atom.xml", "/posts/render/")); + assert!(!file_contains!(public, "categories/a-category/atom.xml", "/posts/render/")); + // render = false pages don't even add terms to taxonomies + assert!(!file_exists!(public, "podcast-authors/other-person/atom.xml")); + assert!(!file_exists!(public, "categories/b-category/atom.xml")); + // aliases work assert!(file_exists!(public, "an-old-url/old-page/index.html")); assert!(file_contains!(public, "an-old-url/old-page/index.html", "something-else")); @@ -216,6 +243,8 @@ fn can_build_site_without_live_reload() { assert!(!file_contains!(public, "sitemap.xml", "draft")); // render: false sections are not in the sitemap either assert!(!file_contains!(public, "sitemap.xml", "posts/2018/")); + // render: false pages are not in the sitemap either + assert!(!file_contains!(public, "sitemap.xml", "posts/render/")); // robots.txt has been rendered from the template assert!(file_contains!(public, "robots.txt", "User-agent: zola")); @@ -299,15 +328,19 @@ fn can_build_site_with_taxonomies() { let mut pages = vec![]; let pages_data = std::mem::replace(&mut library.pages, AHashMap::new()); - for (i, (_, mut page)) in pages_data.into_iter().enumerate() { - page.meta.taxonomies = { - let mut taxonomies = HashMap::new(); - taxonomies.insert( - "categories".to_string(), - vec![if i % 2 == 0 { "A" } else { "B" }.to_string()], - ); - taxonomies - }; + let mut i = 0; + for (_, mut page) in pages_data.into_iter() { + if page.meta.render { + page.meta.taxonomies = { + let mut taxonomies = HashMap::new(); + taxonomies.insert( + "categories".to_string(), + vec![if i % 2 == 0 { "A" } else { "B" }.to_string()], + ); + taxonomies + }; + i += 1; + } pages.push(page); } for p in pages { @@ -417,7 +450,7 @@ fn can_build_site_with_pagination_for_section() { "posts/page/1/index.html", "http-equiv=\"refresh\" content=\"0; url=https://replace-this-with-your-url.com/posts/\"" )); - assert!(file_contains!(public, "posts/index.html", "Num pagers: 5")); + assert!(file_contains!(public, "posts/index.html", "Num pagers: 6")); assert!(file_contains!(public, "posts/index.html", "Page size: 2")); assert!(file_contains!(public, "posts/index.html", "Current index: 1")); assert!(!file_contains!(public, "posts/index.html", "has_prev")); @@ -430,12 +463,12 @@ fn can_build_site_with_pagination_for_section() { assert!(file_contains!( public, "posts/index.html", - "Last: https://replace-this-with-your-url.com/posts/page/5/" + "Last: https://replace-this-with-your-url.com/posts/page/6/" )); assert!(!file_contains!(public, "posts/index.html", "has_prev")); assert!(file_exists!(public, "posts/page/2/index.html")); - assert!(file_contains!(public, "posts/page/2/index.html", "Num pagers: 5")); + assert!(file_contains!(public, "posts/page/2/index.html", "Num pagers: 6")); assert!(file_contains!(public, "posts/page/2/index.html", "Page size: 2")); assert!(file_contains!(public, "posts/page/2/index.html", "Current index: 2")); assert!(file_contains!(public, "posts/page/2/index.html", "has_prev")); @@ -448,11 +481,11 @@ fn can_build_site_with_pagination_for_section() { assert!(file_contains!( public, "posts/page/2/index.html", - "Last: https://replace-this-with-your-url.com/posts/page/5/" + "Last: https://replace-this-with-your-url.com/posts/page/6/" )); assert!(file_exists!(public, "posts/page/3/index.html")); - assert!(file_contains!(public, "posts/page/3/index.html", "Num pagers: 5")); + assert!(file_contains!(public, "posts/page/3/index.html", "Num pagers: 6")); assert!(file_contains!(public, "posts/page/3/index.html", "Page size: 2")); assert!(file_contains!(public, "posts/page/3/index.html", "Current index: 3")); assert!(file_contains!(public, "posts/page/3/index.html", "has_prev")); @@ -465,11 +498,11 @@ fn can_build_site_with_pagination_for_section() { assert!(file_contains!( public, "posts/page/3/index.html", - "Last: https://replace-this-with-your-url.com/posts/page/5/" + "Last: https://replace-this-with-your-url.com/posts/page/6/" )); assert!(file_exists!(public, "posts/page/4/index.html")); - assert!(file_contains!(public, "posts/page/4/index.html", "Num pagers: 5")); + assert!(file_contains!(public, "posts/page/4/index.html", "Num pagers: 6")); assert!(file_contains!(public, "posts/page/4/index.html", "Page size: 2")); assert!(file_contains!(public, "posts/page/4/index.html", "Current index: 4")); assert!(file_contains!(public, "posts/page/4/index.html", "has_prev")); @@ -482,7 +515,7 @@ fn can_build_site_with_pagination_for_section() { assert!(file_contains!( public, "posts/page/4/index.html", - "Last: https://replace-this-with-your-url.com/posts/page/5/" + "Last: https://replace-this-with-your-url.com/posts/page/6/" )); // sitemap contains the pager pages @@ -589,15 +622,19 @@ fn can_build_site_with_pagination_for_taxonomy() { let mut pages = vec![]; let pages_data = std::mem::replace(&mut library.pages, AHashMap::new()); - for (i, (_, mut page)) in pages_data.into_iter().enumerate() { - page.meta.taxonomies = { - let mut taxonomies = HashMap::new(); - taxonomies.insert( - "tags".to_string(), - vec![if i % 2 == 0 { "A" } else { "B" }.to_string()], - ); - taxonomies - }; + let mut i = 0; + for (_, mut page) in pages_data.into_iter() { + if page.meta.render { + page.meta.taxonomies = { + let mut taxonomies = HashMap::new(); + taxonomies.insert( + "tags".to_string(), + vec![if i % 2 == 0 { "A" } else { "B" }.to_string()], + ); + taxonomies + }; + i += 1; + } pages.push(page); } for p in pages { diff --git a/test_site/content/posts/access-render.md b/test_site/content/posts/access-render.md new file mode 100644 index 0000000000..580b327f94 --- /dev/null +++ b/test_site/content/posts/access-render.md @@ -0,0 +1,9 @@ ++++ +title = 'render = false tests' +slug = 'access-render' +template = 'access_render.html' +date = 2000-01-01 ++++ + +This post exists to test that unrendered sections and pages are still accessible +via templates. diff --git a/test_site/content/posts/render.md b/test_site/content/posts/render.md index 63af6b7568..178b783eea 100644 --- a/test_site/content/posts/render.md +++ b/test_site/content/posts/render.md @@ -3,6 +3,10 @@ title = "Page with content but without generated folder" description = "" date = 2017-04-01 render = false + +[taxonomies] +categories = ["a-category", "b-category"] +podcast_authors = ["Some Person", "Other Person"] +++ Don't generate a folder for this page diff --git a/test_site/templates/access_render.html b/test_site/templates/access_render.html new file mode 100644 index 0000000000..e311637aa9 --- /dev/null +++ b/test_site/templates/access_render.html @@ -0,0 +1,15 @@ +{% extends "index.html" %} + +{% block content %} + {% set unrendered_page = get_page(path='posts/render.md') %} + {% set section_with_unrendered = get_section(path='posts/_index.md') %} + {% set unrendered_section = get_section(path='posts/2018/_index.md') %} + Path of unrendered page: {{ unrendered_page.path | safe }} + Pages in section with unrendered page:
                      {% for page in section_with_unrendered.pages -%} +
                    • {{ page.path | safe }}
                    • + {%- endfor %}
                    + Pages in unrendered section:
                      {% for page in unrendered_section.pages -%} +
                    • {{ page.path | safe }}
                    • + {%- endfor %}
                    +{% endblock content %} +