├── .cargo-rdme.toml ├── .codecov.yml ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ ├── release.yml │ └── release_pre_build.sh ├── .gitignore ├── .rustfmt.toml ├── .taplo.toml ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE.md ├── README.md ├── release-notes.md ├── src ├── console.rs ├── extract_doc.rs ├── inject_doc.rs ├── lib.rs ├── main.rs ├── markdown.rs ├── options.rs ├── transform │ ├── intralinks │ │ ├── links.rs │ │ ├── mod.rs │ │ └── module_walker.rs │ ├── mod.rs │ ├── rust_markdown_tag.rs │ ├── rust_remove_comments.rs │ └── utils.rs └── utils.rs ├── tests ├── .cargo-rdme.toml ├── .gitignore ├── avoid_overwrite_uncommitted_readme │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── crate_procmacro │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ └── lib.rs ├── custom_lib_path │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ └── l.rs ├── custom_readme_path │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ └── lib.rs ├── entrypoint_bin_crate │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ └── main.rs ├── entrypoint_bin_lib_crate_lib_wins │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ ├── lib.rs │ │ └── main.rs ├── heading_level_auto_bump │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ └── lib.rs ├── line_terminator_crlf │ ├── .gitattributes │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ └── lib.rs ├── marker_inside_doc │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ └── lib.rs ├── multiline_doc │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ └── lib.rs ├── option_cmd_check_fail │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── lib.rs ├── option_cmd_check_fail_because_warnings │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── lib.rs ├── option_cmd_check_fail_line_terminator │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── lib.rs ├── option_cmd_check_no_fail_on_warnings │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── lib.rs ├── option_cmd_check_ok │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── lib.rs ├── option_cmd_entrypoint_bin │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ ├── lib.rs │ │ └── main.rs ├── option_cmd_entrypoint_select_bin │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ ├── bin │ │ └── foo.rs │ │ └── main.rs ├── option_cmd_entrypoint_select_bin_custom_path │ ├── .cargo-rdme.toml │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ ├── foo-main.rs │ │ └── main.rs ├── option_cmd_entrypoint_select_bin_single │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ └── my-only-bin.rs ├── option_cmd_heading_base_level │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ └── lib.rs ├── option_cmd_intralinks_strip_links │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ └── main.rs ├── option_cmd_line_terminator_crlf │ ├── .gitattributes │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ └── lib.rs ├── option_cmd_line_terminator_lf │ ├── .gitattributes │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ └── lib.rs ├── option_cmd_override_readme_path │ ├── .gitignore │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ └── lib.rs ├── option_cmd_workspace │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ ├── myproj │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ └── otherproj │ │ ├── Cargo.toml │ │ └── src │ │ └── lib.rs ├── option_cmd_workspace_dependency_collision │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ ├── cargo-rdme │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ └── dependent │ │ ├── Cargo.toml │ │ └── src │ │ └── lib.rs ├── option_conf_file_entrypoint_bin │ ├── .cargo-rdme.toml │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ ├── lib.rs │ │ └── main.rs ├── option_conf_file_entrypoint_select_bin │ ├── .cargo-rdme.toml │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ ├── bin │ │ └── foo.rs │ │ └── main.rs ├── option_conf_file_entrypoint_select_bin_custom_path │ ├── .cargo-rdme.toml │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ ├── foo-main.rs │ │ └── main.rs ├── option_conf_file_heading_base_level │ ├── .cargo-rdme.toml │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ └── lib.rs ├── option_conf_file_intralinks_docs_rs_base_url │ ├── .cargo-rdme.toml │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ └── lib.rs ├── option_conf_file_intralinks_docs_rs_version │ ├── .cargo-rdme.toml │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ └── lib.rs ├── option_conf_file_intralinks_strip_links │ ├── .cargo-rdme.toml │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ └── main.rs ├── option_conf_file_line_terminator_crlf │ ├── .cargo-rdme.toml │ ├── .gitattributes │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ └── lib.rs ├── option_conf_file_line_terminator_lf │ ├── .cargo-rdme.toml │ ├── .gitattributes │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ └── lib.rs ├── option_conf_file_override_readme_path │ ├── .cargo-rdme.toml │ ├── .gitignore │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ └── lib.rs ├── option_conf_file_workspace │ ├── .cargo-rdme.toml │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ ├── myproj │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ └── otherproj │ │ ├── Cargo.toml │ │ └── src │ │ └── lib.rs ├── separate_bin_and_lib │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ ├── bin │ │ └── the-binary │ │ │ └── main.rs │ │ └── lib │ │ └── lib.rs ├── simple_single_marker │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ └── lib.rs ├── simple_single_marker_no_footer │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ └── lib.rs ├── simple_start_end_marker │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ └── lib.rs ├── testing.rs ├── tests.rs ├── transform_intralinks_ambiguous_module │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ └── main.rs ├── transform_intralinks_backticked │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ └── main.rs ├── transform_intralinks_crate_name_hyphen │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ └── main.rs ├── transform_intralinks_impl_items │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ └── main.rs ├── transform_intralinks_module_walk │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ ├── amodule.rs │ │ ├── foo │ │ ├── bar.rs │ │ └── mod.rs │ │ └── main.rs ├── transform_intralinks_reference_links │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ └── main.rs ├── transform_intralinks_simple │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ └── main.rs ├── transform_intralinks_stdlib_links │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ └── main.rs ├── transform_rust_code_block_add_markdown_tag │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ └── lib.rs ├── transform_rust_code_block_remove_comments │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ │ └── lib.rs └── transform_rust_code_nested_fenced_blocks │ ├── Cargo.toml │ ├── README-expected.md │ ├── README-template.md │ └── src │ └── lib.rs └── tools ├── check.sh ├── codecov.sh ├── release.sh └── utils.sh /.cargo-rdme.toml: -------------------------------------------------------------------------------- 1 | line-terminator = "lf" 2 | 3 | [entrypoint] 4 | type = "bin" 5 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | notify: 3 | require_ci_to_pass: yes 4 | 5 | coverage: 6 | precision: 2 7 | round: down 8 | range: "30...100" 9 | 10 | status: 11 | project: 12 | default: 13 | target: auto 14 | threshold: 10% 15 | patch: no 16 | changes: no 17 | 18 | ignore: 19 | - "tests/" 20 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "cargo" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | schedule: 9 | - cron: '0 19 * * 3' 10 | 11 | env: 12 | CARGO_TERM_COLOR: always 13 | 14 | jobs: 15 | check: 16 | runs-on: ${{ matrix.os }} 17 | 18 | strategy: 19 | matrix: 20 | os: [ubuntu-latest, macos-latest, windows-latest] 21 | 22 | steps: 23 | - name: Install rust 24 | uses: dtolnay/rust-toolchain@stable 25 | with: 26 | # We need to install the source of the standard library for the integration tests to check that links 27 | # to the standard library are correctly generated. 28 | components: rust-src, rustfmt 29 | 30 | - name: Install cargo plugins 31 | run: | 32 | cargo install cargo-deadlinks 33 | cargo install cargo-machete 34 | cargo install taplo-cli 35 | 36 | - name: Checkout repository 37 | uses: actions/checkout@v4 38 | 39 | - name: Check everything 40 | run: bash ./tools/check.sh basic doc_url_links unused_deps packaging fmt toml_fmt readme 41 | 42 | - name: Code coverage 43 | if: ${{ runner.os == 'Linux' }} 44 | run: | 45 | cargo install cargo-tarpaulin 46 | ./tools/codecov.sh --xml 47 | bash <(curl -s https://codecov.io/bash) 48 | 49 | msrv: 50 | runs-on: ubuntu-latest 51 | 52 | steps: 53 | - name: Install rust 54 | uses: dtolnay/rust-toolchain@stable 55 | 56 | - name: Install cargo plugins 57 | run: cargo install cargo-msrv 58 | 59 | - name: Checkout repository 60 | uses: actions/checkout@v4 61 | 62 | - name: Check the minimum supported rust version 63 | run: bash ./tools/check.sh msrv 64 | 65 | clippy: 66 | runs-on: ubuntu-latest 67 | 68 | steps: 69 | - name: Install rust 70 | uses: dtolnay/rust-toolchain@stable 71 | 72 | - name: Checkout repository 73 | uses: actions/checkout@v4 74 | 75 | - name: Run clippy 76 | run: bash ./tools/check.sh clippy 77 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | release: 9 | name: release ${{ matrix.target }} 10 | 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | include: 17 | - target: x86_64-unknown-linux-musl 18 | archive: tar.zst tar.bz2 19 | - target: x86_64-apple-darwin 20 | archive: tar.zst zip 21 | - target: x86_64-pc-windows-gnu 22 | archive: tar.zst zip 23 | 24 | steps: 25 | - uses: actions/checkout@v4 26 | 27 | - name: Build release binary 28 | uses: rust-build/rust-build.action@v1.4.4 29 | env: 30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | with: 32 | TOOLCHAIN_VERSION: stable 33 | RUSTTARGET: ${{ matrix.target }} 34 | ARCHIVE_TYPES: ${{ matrix.archive }} 35 | EXTRA_FILES: "README.md LICENSE.md" 36 | PRE_BUILD: ".github/workflows/release_pre_build.sh" 37 | -------------------------------------------------------------------------------- /.github/workflows/release_pre_build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # See https://github.com/rust-build/rust-build.action/pull/99. 6 | apk add --no-cache zlib-static 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | 3 | /target/ 4 | *.rs.bk 5 | 6 | /tarpaulin-report.html 7 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | use_field_init_shorthand = true 2 | use_try_shorthand = true 3 | use_small_heuristics = "max" 4 | 5 | # TODO Awaiting stabilization 6 | # comment_width = 100 7 | # match_arm_blocks = false 8 | # struct_field_align_threshold = 4 9 | -------------------------------------------------------------------------------- /.taplo.toml: -------------------------------------------------------------------------------- 1 | include = ["**/*.toml"] 2 | exclude = ["tests/**", "target/**"] 3 | 4 | [formatting] 5 | column_width = 100 6 | indent_string = ' ' 7 | allowed_blank_lines = 1 8 | 9 | [[rule]] 10 | formatting = { reorder_keys = true } 11 | keys = [ 12 | "workspace.dependencies", 13 | "dependencies", 14 | "dev-dependencies", 15 | "build-dependencies", 16 | "lints.clippy", 17 | ] 18 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to `cargo-rdme` 2 | 3 | Thank you for your interest in contributing to `cargo-rdme`. We appreciate it! 4 | 5 | It is a good idea for you to open an issue before you start working on non-trivial features. This way we can discuss if 6 | the feature is desirable and how to best design and implement it. 7 | 8 | ## Useful tools 9 | 10 | If you are contributing with a pull request you probably want to know about these scripts: 11 | 12 | * [`./tools/check.sh`](tools/check.sh) — Checks that everything is fine. This includes checking that everything 13 | builds, the unit tests pass, and the code is correctly formatted. If you need to format the code run 14 | `cargo fmt`. 15 | * [`./tools/codecov.sh`](tools/codecov.sh) — Creates a code coverage report. There is not a strict code coverage 16 | threshold, but we do want pretty much everything tested. 17 | * [`cargo rdme`](https://crates.io/crates/cargo-rdme) — Of course we use `cargo-rdme` to update our README. 18 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cargo-rdme" 3 | description = "Cargo command to create your `README.md` from your crate's documentation" 4 | version = "1.4.9-pre" 5 | authors = ["Diogo Sousa "] 6 | 7 | edition = "2024" 8 | rust-version = "1.85.0" 9 | 10 | homepage = "https://github.com/orium/cargo-rdme" 11 | repository = "https://github.com/orium/cargo-rdme" 12 | documentation = "https://docs.rs/cargo-rdme" 13 | readme = "README.md" 14 | 15 | keywords = ["readme", "cargo", "documentation"] 16 | categories = ["development-tools::cargo-plugins"] 17 | 18 | license = "MPL-2.0" 19 | 20 | # What to include when packaging. 21 | include = [ 22 | "/src/**/*.rs", 23 | "/Cargo.toml", 24 | "/Cargo.lock", 25 | "/LICENSE.md", 26 | "/README.md", 27 | "/release-notes.md", 28 | ] 29 | 30 | [badges] 31 | codecov = { repository = "orium/cargo-rdme", branch = "main", service = "github" } 32 | 33 | [dependencies] 34 | cargo_metadata = "0.19.1" 35 | clap = "4.5.30" 36 | clap-cargo = "0.15.2" 37 | # Disable ssh support in git2 to avoid depending on openssl (which fails to build if an unsupported version is found). 38 | git2 = { version = "0.20.0", default-features = false } 39 | indoc = "2.0.5" 40 | itertools = "0.14.0" 41 | pulldown-cmark = "0.13.0" 42 | syn = { version = "2.0.98", features = ["full", "extra-traits"] } 43 | termcolor = "1.4.1" 44 | thiserror = "2.0.11" 45 | toml = "0.8.20" 46 | unicase = "2.8.1" 47 | 48 | [dev-dependencies] 49 | pretty_assertions = "1.4.1" 50 | 51 | [features] 52 | fatal-warnings = [] 53 | 54 | [lints.clippy] 55 | all = { level = "warn", priority = -2 } 56 | correctness = { level = "deny", priority = -1 } 57 | pedantic = { level = "warn", priority = -2 } 58 | 59 | enum-variant-names = "allow" 60 | if-not-else = "allow" 61 | inline-always = "allow" 62 | match-bool = "allow" 63 | match-same-arms = "allow" 64 | missing-errors-doc = "allow" 65 | module-name-repetitions = "allow" 66 | needless-raw-string-hashes = "allow" 67 | new-without-default = "allow" 68 | non-ascii-literal = "allow" 69 | partialeq-ne-impl = "allow" 70 | similar-names = "allow" 71 | single-match-else = "allow" 72 | struct-excessive-bools = "allow" 73 | use-self = "allow" 74 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | ### 1. Definitions 5 | 6 | **1.1. “Contributor”** 7 | means each individual or legal entity that creates, contributes to 8 | the creation of, or owns Covered Software. 9 | 10 | **1.2. “Contributor Version”** 11 | means the combination of the Contributions of others (if any) used 12 | by a Contributor and that particular Contributor's Contribution. 13 | 14 | **1.3. “Contribution”** 15 | means Covered Software of a particular Contributor. 16 | 17 | **1.4. “Covered Software”** 18 | means Source Code Form to which the initial Contributor has attached 19 | the notice in Exhibit A, the Executable Form of such Source Code 20 | Form, and Modifications of such Source Code Form, in each case 21 | including portions thereof. 22 | 23 | **1.5. “Incompatible With Secondary Licenses”** 24 | means 25 | 26 | * **(a)** that the initial Contributor has attached the notice described 27 | in Exhibit B to the Covered Software; or 28 | * **(b)** that the Covered Software was made available under the terms of 29 | version 1.1 or earlier of the License, but not also under the 30 | terms of a Secondary License. 31 | 32 | **1.6. “Executable Form”** 33 | means any form of the work other than Source Code Form. 34 | 35 | **1.7. “Larger Work”** 36 | means a work that combines Covered Software with other material, in 37 | a separate file or files, that is not Covered Software. 38 | 39 | **1.8. “License”** 40 | means this document. 41 | 42 | **1.9. “Licensable”** 43 | means having the right to grant, to the maximum extent possible, 44 | whether at the time of the initial grant or subsequently, any and 45 | all of the rights conveyed by this License. 46 | 47 | **1.10. “Modifications”** 48 | means any of the following: 49 | 50 | * **(a)** any file in Source Code Form that results from an addition to, 51 | deletion from, or modification of the contents of Covered 52 | Software; or 53 | * **(b)** any new file in Source Code Form that contains any Covered 54 | Software. 55 | 56 | **1.11. “Patent Claims” of a Contributor** 57 | means any patent claim(s), including without limitation, method, 58 | process, and apparatus claims, in any patent Licensable by such 59 | Contributor that would be infringed, but for the grant of the 60 | License, by the making, using, selling, offering for sale, having 61 | made, import, or transfer of either its Contributions or its 62 | Contributor Version. 63 | 64 | **1.12. “Secondary License”** 65 | means either the GNU General Public License, Version 2.0, the GNU 66 | Lesser General Public License, Version 2.1, the GNU Affero General 67 | Public License, Version 3.0, or any later versions of those 68 | licenses. 69 | 70 | **1.13. “Source Code Form”** 71 | means the form of the work preferred for making modifications. 72 | 73 | **1.14. “You” (or “Your”)** 74 | means an individual or a legal entity exercising rights under this 75 | License. For legal entities, “You” includes any entity that 76 | controls, is controlled by, or is under common control with You. For 77 | purposes of this definition, “control” means **(a)** the power, direct 78 | or indirect, to cause the direction or management of such entity, 79 | whether by contract or otherwise, or **(b)** ownership of more than 80 | fifty percent (50%) of the outstanding shares or beneficial 81 | ownership of such entity. 82 | 83 | 84 | ### 2. License Grants and Conditions 85 | 86 | #### 2.1. Grants 87 | 88 | Each Contributor hereby grants You a world-wide, royalty-free, 89 | non-exclusive license: 90 | 91 | * **(a)** under intellectual property rights (other than patent or trademark) 92 | Licensable by such Contributor to use, reproduce, make available, 93 | modify, display, perform, distribute, and otherwise exploit its 94 | Contributions, either on an unmodified basis, with Modifications, or 95 | as part of a Larger Work; and 96 | * **(b)** under Patent Claims of such Contributor to make, use, sell, offer 97 | for sale, have made, import, and otherwise transfer either its 98 | Contributions or its Contributor Version. 99 | 100 | #### 2.2. Effective Date 101 | 102 | The licenses granted in Section 2.1 with respect to any Contribution 103 | become effective for each Contribution on the date the Contributor first 104 | distributes such Contribution. 105 | 106 | #### 2.3. Limitations on Grant Scope 107 | 108 | The licenses granted in this Section 2 are the only rights granted under 109 | this License. No additional rights or licenses will be implied from the 110 | distribution or licensing of Covered Software under this License. 111 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 112 | Contributor: 113 | 114 | * **(a)** for any code that a Contributor has removed from Covered Software; 115 | or 116 | * **(b)** for infringements caused by: **(i)** Your and any other third party's 117 | modifications of Covered Software, or **(ii)** the combination of its 118 | Contributions with other software (except as part of its Contributor 119 | Version); or 120 | * **(c)** under Patent Claims infringed by Covered Software in the absence of 121 | its Contributions. 122 | 123 | This License does not grant any rights in the trademarks, service marks, 124 | or logos of any Contributor (except as may be necessary to comply with 125 | the notice requirements in Section 3.4). 126 | 127 | #### 2.4. Subsequent Licenses 128 | 129 | No Contributor makes additional grants as a result of Your choice to 130 | distribute the Covered Software under a subsequent version of this 131 | License (see Section 10.2) or under the terms of a Secondary License (if 132 | permitted under the terms of Section 3.3). 133 | 134 | #### 2.5. Representation 135 | 136 | Each Contributor represents that the Contributor believes its 137 | Contributions are its original creation(s) or it has sufficient rights 138 | to grant the rights to its Contributions conveyed by this License. 139 | 140 | #### 2.6. Fair Use 141 | 142 | This License is not intended to limit any rights You have under 143 | applicable copyright doctrines of fair use, fair dealing, or other 144 | equivalents. 145 | 146 | #### 2.7. Conditions 147 | 148 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 149 | in Section 2.1. 150 | 151 | 152 | ### 3. Responsibilities 153 | 154 | #### 3.1. Distribution of Source Form 155 | 156 | All distribution of Covered Software in Source Code Form, including any 157 | Modifications that You create or to which You contribute, must be under 158 | the terms of this License. You must inform recipients that the Source 159 | Code Form of the Covered Software is governed by the terms of this 160 | License, and how they can obtain a copy of this License. You may not 161 | attempt to alter or restrict the recipients' rights in the Source Code 162 | Form. 163 | 164 | #### 3.2. Distribution of Executable Form 165 | 166 | If You distribute Covered Software in Executable Form then: 167 | 168 | * **(a)** such Covered Software must also be made available in Source Code 169 | Form, as described in Section 3.1, and You must inform recipients of 170 | the Executable Form how they can obtain a copy of such Source Code 171 | Form by reasonable means in a timely manner, at a charge no more 172 | than the cost of distribution to the recipient; and 173 | 174 | * **(b)** You may distribute such Executable Form under the terms of this 175 | License, or sublicense it under different terms, provided that the 176 | license for the Executable Form does not attempt to limit or alter 177 | the recipients' rights in the Source Code Form under this License. 178 | 179 | #### 3.3. Distribution of a Larger Work 180 | 181 | You may create and distribute a Larger Work under terms of Your choice, 182 | provided that You also comply with the requirements of this License for 183 | the Covered Software. If the Larger Work is a combination of Covered 184 | Software with a work governed by one or more Secondary Licenses, and the 185 | Covered Software is not Incompatible With Secondary Licenses, this 186 | License permits You to additionally distribute such Covered Software 187 | under the terms of such Secondary License(s), so that the recipient of 188 | the Larger Work may, at their option, further distribute the Covered 189 | Software under the terms of either this License or such Secondary 190 | License(s). 191 | 192 | #### 3.4. Notices 193 | 194 | You may not remove or alter the substance of any license notices 195 | (including copyright notices, patent notices, disclaimers of warranty, 196 | or limitations of liability) contained within the Source Code Form of 197 | the Covered Software, except that You may alter any license notices to 198 | the extent required to remedy known factual inaccuracies. 199 | 200 | #### 3.5. Application of Additional Terms 201 | 202 | You may choose to offer, and to charge a fee for, warranty, support, 203 | indemnity or liability obligations to one or more recipients of Covered 204 | Software. However, You may do so only on Your own behalf, and not on 205 | behalf of any Contributor. You must make it absolutely clear that any 206 | such warranty, support, indemnity, or liability obligation is offered by 207 | You alone, and You hereby agree to indemnify every Contributor for any 208 | liability incurred by such Contributor as a result of warranty, support, 209 | indemnity or liability terms You offer. You may include additional 210 | disclaimers of warranty and limitations of liability specific to any 211 | jurisdiction. 212 | 213 | 214 | ### 4. Inability to Comply Due to Statute or Regulation 215 | 216 | If it is impossible for You to comply with any of the terms of this 217 | License with respect to some or all of the Covered Software due to 218 | statute, judicial order, or regulation then You must: **(a)** comply with 219 | the terms of this License to the maximum extent possible; and **(b)** 220 | describe the limitations and the code they affect. Such description must 221 | be placed in a text file included with all distributions of the Covered 222 | Software under this License. Except to the extent prohibited by statute 223 | or regulation, such description must be sufficiently detailed for a 224 | recipient of ordinary skill to be able to understand it. 225 | 226 | 227 | ### 5. Termination 228 | 229 | **5.1.** The rights granted under this License will terminate automatically 230 | if You fail to comply with any of its terms. However, if You become 231 | compliant, then the rights granted under this License from a particular 232 | Contributor are reinstated **(a)** provisionally, unless and until such 233 | Contributor explicitly and finally terminates Your grants, and **(b)** on an 234 | ongoing basis, if such Contributor fails to notify You of the 235 | non-compliance by some reasonable means prior to 60 days after You have 236 | come back into compliance. Moreover, Your grants from a particular 237 | Contributor are reinstated on an ongoing basis if such Contributor 238 | notifies You of the non-compliance by some reasonable means, this is the 239 | first time You have received notice of non-compliance with this License 240 | from such Contributor, and You become compliant prior to 30 days after 241 | Your receipt of the notice. 242 | 243 | **5.2.** If You initiate litigation against any entity by asserting a patent 244 | infringement claim (excluding declaratory judgment actions, 245 | counter-claims, and cross-claims) alleging that a Contributor Version 246 | directly or indirectly infringes any patent, then the rights granted to 247 | You by any and all Contributors for the Covered Software under Section 248 | 2.1 of this License shall terminate. 249 | 250 | **5.3.** In the event of termination under Sections 5.1 or 5.2 above, all 251 | end user license agreements (excluding distributors and resellers) which 252 | have been validly granted by You or Your distributors under this License 253 | prior to termination shall survive termination. 254 | 255 | 256 | ### 6. Disclaimer of Warranty 257 | 258 | > Covered Software is provided under this License on an “as is” 259 | > basis, without warranty of any kind, either expressed, implied, or 260 | > statutory, including, without limitation, warranties that the 261 | > Covered Software is free of defects, merchantable, fit for a 262 | > particular purpose or non-infringing. The entire risk as to the 263 | > quality and performance of the Covered Software is with You. 264 | > Should any Covered Software prove defective in any respect, You 265 | > (not any Contributor) assume the cost of any necessary servicing, 266 | > repair, or correction. This disclaimer of warranty constitutes an 267 | > essential part of this License. No use of any Covered Software is 268 | > authorized under this License except under this disclaimer. 269 | 270 | ### 7. Limitation of Liability 271 | 272 | > Under no circumstances and under no legal theory, whether tort 273 | > (including negligence), contract, or otherwise, shall any 274 | > Contributor, or anyone who distributes Covered Software as 275 | > permitted above, be liable to You for any direct, indirect, 276 | > special, incidental, or consequential damages of any character 277 | > including, without limitation, damages for lost profits, loss of 278 | > goodwill, work stoppage, computer failure or malfunction, or any 279 | > and all other commercial damages or losses, even if such party 280 | > shall have been informed of the possibility of such damages. This 281 | > limitation of liability shall not apply to liability for death or 282 | > personal injury resulting from such party's negligence to the 283 | > extent applicable law prohibits such limitation. Some 284 | > jurisdictions do not allow the exclusion or limitation of 285 | > incidental or consequential damages, so this exclusion and 286 | > limitation may not apply to You. 287 | 288 | 289 | ### 8. Litigation 290 | 291 | Any litigation relating to this License may be brought only in the 292 | courts of a jurisdiction where the defendant maintains its principal 293 | place of business and such litigation shall be governed by laws of that 294 | jurisdiction, without reference to its conflict-of-law provisions. 295 | Nothing in this Section shall prevent a party's ability to bring 296 | cross-claims or counter-claims. 297 | 298 | 299 | ### 9. Miscellaneous 300 | 301 | This License represents the complete agreement concerning the subject 302 | matter hereof. If any provision of this License is held to be 303 | unenforceable, such provision shall be reformed only to the extent 304 | necessary to make it enforceable. Any law or regulation which provides 305 | that the language of a contract shall be construed against the drafter 306 | shall not be used to construe this License against a Contributor. 307 | 308 | 309 | ### 10. Versions of the License 310 | 311 | #### 10.1. New Versions 312 | 313 | Mozilla Foundation is the license steward. Except as provided in Section 314 | 10.3, no one other than the license steward has the right to modify or 315 | publish new versions of this License. Each version will be given a 316 | distinguishing version number. 317 | 318 | #### 10.2. Effect of New Versions 319 | 320 | You may distribute the Covered Software under the terms of the version 321 | of the License under which You originally received the Covered Software, 322 | or under the terms of any subsequent version published by the license 323 | steward. 324 | 325 | #### 10.3. Modified Versions 326 | 327 | If you create software not governed by this License, and you want to 328 | create a new license for such software, you may create and use a 329 | modified version of this License if you rename the license and remove 330 | any references to the name of the license steward (except to note that 331 | such modified license differs from this License). 332 | 333 | #### 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses 334 | 335 | If You choose to distribute Source Code Form that is Incompatible With 336 | Secondary Licenses under the terms of this version of the License, the 337 | notice described in Exhibit B of this License must be attached. 338 | 339 | ## Exhibit A - Source Code Form License Notice 340 | 341 | This Source Code Form is subject to the terms of the Mozilla Public 342 | License, v. 2.0. If a copy of the MPL was not distributed with this 343 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 344 | 345 | If it is not possible or desirable to put the notice in a particular 346 | file, then You may include the notice in a location (such as a LICENSE 347 | file in a relevant directory) where a recipient would be likely to look 348 | for such a notice. 349 | 350 | You may add additional accurate notices of copyright ownership. 351 | 352 | ## Exhibit B - “Incompatible With Secondary Licenses” Notice 353 | 354 | This Source Code Form is "Incompatible With Secondary Licenses", as 355 | defined by the Mozilla Public License, v. 2.0. 356 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://github.com/orium/cargo-rdme/workflows/CI/badge.svg)](https://github.com/orium/cargo-rdme/actions?query=workflow%3ACI) 2 | [![Code Coverage](https://codecov.io/gh/orium/cargo-rdme/branch/main/graph/badge.svg)](https://codecov.io/gh/orium/cargo-rdme) 3 | [![Dependency status](https://deps.rs/repo/github/orium/cargo-rdme/status.svg)](https://deps.rs/repo/github/orium/cargo-rdme) 4 | [![crates.io](https://img.shields.io/crates/v/cargo-rdme.svg)](https://crates.io/crates/cargo-rdme) 5 | [![Downloads crates.io](https://img.shields.io/crates/d/cargo-rdme.svg?label=crates.io%20downloads)](https://crates.io/crates/cargo-rdme) 6 | [![Downloads github](https://img.shields.io/github/downloads/orium/cargo-rdme/total.svg?label=github%20downloads)](https://github.com/orium/cargo-rdme/releases) 7 | [![Github stars](https://img.shields.io/github/stars/orium/cargo-rdme.svg?logo=github)](https://github.com/orium/cargo-rdme/stargazers) 8 | [![License](https://img.shields.io/crates/l/cargo-rdme.svg)](./LICENSE.md) 9 | 10 | # Cargo rdme 11 | 12 | 13 | 14 | Cargo command to create your README from your crate’s documentation. 15 | 16 | ## Installation 17 | 18 | You can install cargo rdme with cargo by running `cargo install cargo-rdme`. 19 | 20 | ## Usage 21 | 22 | Cargo rdme will insert your crate’s documentation in your README file. To control where the 23 | documentation will be inserted you need to insert a marker: ``. For example, 24 | you can start your README with some glorious badges and follow up with the rustdoc 25 | documentation: 26 | 27 | ```markdown 28 | [![Build Status](https://example.org/badge.svg)](https://example.org/link-to-ci) 29 | 30 | 31 | ``` 32 | 33 | After running `cargo rdme` you will find your README to be something like: 34 | 35 | ```markdown 36 | [![Build Status](https://example.org/badge.svg)](https://example.org/link-to-ci) 37 | 38 | 39 | 40 | 41 | 42 | 43 | ``` 44 | 45 | Whenever change your crate’s documentation you just need to run `cargo rdme` to update your 46 | README file. 47 | 48 | ## Automatic transformations 49 | 50 | The documentation of your crate doesn’t always map directly to a good README. For example, 51 | rust code blocks can have hidden lines. Those should not be shown in the README file. 52 | 53 | This section covers the transformation cargo rdme automatically apply to generate a better 54 | README. 55 | 56 | ### Rust code block 57 | 58 | Rust code block are transformed in two ways by cargo rdme: 59 | 60 | 1. Rust code blocks with lines starting with `#` will be omitted, just like in `rustdoc`. 61 | 2. Rust code blocks get annotated with the `rust` markdown tag so it gets proper syntax 62 | highlighting. We also remove tags that only concern `rustdoc` such as `should_panic`. 63 | 64 | In the table below you can see an example of these modification. The code block now is 65 | tagged with `rust` and hidden lines were removed: 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 93 | 108 | 109 |
Crate’s rustdoc
README.md
77 | 78 | ```rust 79 | //! To check if a number is prime do: 80 | //! 81 | //! ``` 82 | //! # fn main() { 83 | //! for i in 2.. { 84 | //! if is_prime(i) { 85 | //! println!("{i}"); 86 | //! } 87 | //! } 88 | //! # } 89 | //! ``` 90 | ``` 91 | 92 | 94 | 95 | ````markdown 96 | To check if a number is prime do: 97 | 98 | ```rust 99 | for i in 2.. { 100 | if is_prime(i) { 101 | println!("{i}"); 102 | } 103 | } 104 | ``` 105 | ```` 106 | 107 |
110 | 111 | ### Intralinks 112 | 113 | Rust documentation can contain [links to items defined in the crate](https://doc.rust-lang.org/stable/rustdoc/linking-to-items-by-name.html). 114 | This links would not make sense in your README file, so cargo rdme automatically generate 115 | links to [docs.rs](https://docs.rs) for these intralinks. 116 | 117 | Currently we only support links of the form `[⋯](crate::⋯)`, so be sure to use that format. 118 | Links to the standard library are also supported, and they must be of the form 119 | `[⋯](::::⋯)`, where `` is a crate that is part of the standard library, such as 120 | `std`, `core`, or `alloc`. Reference-style links are also supported. 121 | 122 | Take a look at the example below: 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 141 | 149 | 150 |
Crate’s rustdoc
README.md
134 | 135 | ```rust 136 | //! To check if a number is prime use 137 | //! [`is_prime`](crate::is_prime). 138 | ``` 139 | 140 | 142 | 143 | ```markdown 144 | To check if a number is prime use 145 | [`is_prime`](https://docs.rs/prime/latest/prime/fn.is_prime.html). 146 | ``` 147 | 148 |
151 | 152 | Note that there is some limitations in intralink support. This is a complex feature: cargo rdme 153 | needs to do some work to be able to create the link to docs.rs. This is because the link 154 | includes the kind of item the intralink points to, in the case of `is_prime` we need to discover 155 | that is a function to generate a link that ends in `fn.is_prime.html`. Therefore, intralink 156 | support should be considered "best effort" (for instance, don’t expect items generated by macros 157 | to be resolved). If cargo rdme is unable to generate the link it will still generate the README 158 | file, but a warning will be emitted. 159 | 160 | ### Heading levels 161 | 162 | The heading levels in the crate’s documentation will, by default, be nested under the level 163 | of the section of the README where it is inserted into. This behavior can be changed with 164 | the `--heading-base-level` command line flag, or in the configuration file (see example 165 | below). 166 | 167 | ## Configuration file 168 | 169 | If the default behavior of `cargo rdme` is not appropriate for your project you can crate a 170 | configuration file `.cargo-rdme.toml` in the root of your project. This is how that 171 | configuration file can look like: 172 | 173 | ```toml 174 | # Override the README file path. When this is not set cargo rdme will use the file path defined 175 | # in the project’s `Cargo.toml`. 176 | readme-path = "MY-README.md" 177 | 178 | # What line terminator to use when generating the README file. This can be "lf" or "crlf". 179 | line-terminator = "lf" 180 | 181 | # If you are using a workspace to hold multiple projects, use this to select the project from 182 | # which to extract the documentation from. It can be useful to also set `readme-path` to create 183 | # the README file in the root of the project. 184 | workspace-project = "subproject" 185 | 186 | # Defines the base heading level to use when inserting the crate’s documentation in the 187 | # README. If this is not set the crate’s documentation will be inserted with its sections 188 | # belonging to the README section where the insertion happens. 189 | heading-base-level = 0 190 | 191 | # The default entrypoint will be `src/lib.rs`. You can change that in the `entrypoint` table. 192 | [entrypoint] 193 | # The entrypoint type can be "lib" or "bin". 194 | type = "bin" 195 | # When you set type to "bin" the entrypoint default to `src/main.rs`. If you have binary targets 196 | # specified in your cargo manifest you can select them by name with `bin-name`. 197 | bin-name = "my-bin-name" 198 | 199 | [intralinks] 200 | # Defines the base url to use in intralinks urls. The default value is `https://docs.rs`. 201 | docs-rs-base-url = "https://mydocs.rs" 202 | # Defines the version to use in intralinks urls. The default value is `latest`. 203 | docs-rs-version = "1.0.0" 204 | # If this is set the intralinks will be stripping in the README file. 205 | strip-links = false 206 | ``` 207 | 208 | These setting can be overridden with command line flags. Run `cargo rdme --help` for more 209 | information. 210 | 211 | ## Integration with CI 212 | 213 | To verify that your README is up to date with your crate’s documentation you can run 214 | `cargo rdme --check`. The exit code will be `0` if the README is up to date, `3` if it’s 215 | not, or `4` if there were warnings. 216 | 217 | If you use GitHub Actions you can add this step to verify if the README is up to date: 218 | 219 | ```yaml 220 | - name: Check if the README is up to date. 221 | run: | 222 | cargo install cargo-rdme 223 | cargo rdme --check 224 | ``` 225 | 226 | 227 | -------------------------------------------------------------------------------- /release-notes.md: -------------------------------------------------------------------------------- 1 | # Release notes 2 | 3 | ## 1.4.8 4 | 5 | * Use cargo styles for cli help output. 6 | * Update dependencies. 7 | 8 | ## 1.4.7 9 | 10 | * Another attempt at fixing github build action. 11 | 12 | ## 1.4.6 13 | 14 | * Update dependencies. 15 | * Fix github build action: we should be back to publishing binaries. 16 | 17 | ## 1.4.5 18 | 19 | * Update dependencies. 20 | 21 | ## 1.4.4 22 | 23 | * Fix selection of packages in workspaces when there's a package name conflict with a dependency. 24 | * Update dependencies. 25 | 26 | ## 1.4.3 27 | 28 | * Update dependencies. 29 | 30 | ## 1.4.2 31 | 32 | * It turns out we might need the network to get the project metadata. See release notes of version 1.4.1. 33 | 34 | ## 1.4.1 35 | 36 | * Ask `cargo metadata` to not access the network. All the info we need is in `Cargo.toml`. 37 | * Update dependencies. 38 | 39 | ## 1.4.0 40 | 41 | * Support intralinks to methods and other `impl` items. 42 | * Update dependencies. 43 | 44 | ## 1.3.0 45 | 46 | * Support intralinks in reference-style links. 47 | * Update dependencies. 48 | 49 | ## 1.2.0 50 | 51 | * Allow the heading base level to be configurable. 52 | * Update dependencies. 53 | 54 | ## 1.1.0 55 | 56 | * Support backticked intralinks. 57 | * Print a helpful message when the README is lacking a cargo-rdme marker. 58 | * Update dependencies. 59 | 60 | ## 1.0.2 61 | 62 | * Update dependencies. 63 | 64 | ## 1.0.1 65 | 66 | * Do not depend on openssl (which is a transitive dependency of git2). This improves the building speed, but most 67 | importantly we don't fail to build if the host system openssl is not supported by rust-openssl. 68 | 69 | ## 1.0.0 70 | 71 | * Change the status code for the "check mode" (flag `--check`): 72 | * Exit code `3` means the README does not match the documentation. In previous releases the exit code was `4`. 73 | * Exit code `4` means warnings were emitted. In previous releases the exit code was `3`. 74 | * Update dependencies. 75 | 76 | ## 0.7.3 77 | 78 | * Update dependencies, in particular we are now using clap 4. 79 | 80 | ## 0.7.2 81 | 82 | * Support these lib crate types: `proc-macro`, `dylib`, `staticlib`, `cdylib`, and `rlib`. 83 | * Update dependencies. 84 | 85 | ## 0.7.1 86 | 87 | * Update dependencies. 88 | 89 | ## 0.7.0 90 | 91 | * Automatically bump the heading level in the injected rustdoc based on the README current heading level. 92 | 93 | ## 0.6.0 94 | 95 | * Added option for intralinks to be stripped. 96 | * Base url and version in intralinks urls can now be configured. 97 | * Strip intralinks when we cannot generate a correct link. 98 | * Fix warning and error output when not writing to a tty. 99 | 100 | ## 0.5.0 101 | 102 | * Fail on `--check` if there were warnings. 103 | * Add command line option to not fail on warnigs. 104 | 105 | ## 0.4.0 106 | 107 | * Add support for intralinks! 108 | * Intralinks are not converted to links to docs.rs in your README file. 109 | * Add support for workspaces. 110 | * Add option to override the README file path (command line flag and configuration file). 111 | * Support code blocks with more than three backticks. 112 | * Use nice color output for errors and warnings. 113 | 114 | ## 0.3.0 115 | 116 | * Add `rust` markdown tag to rust code blocks. 117 | * Avoid overwriting README files with uncommitted changes. 118 | 119 | ## 0.2.0 120 | 121 | * Rust code blocks starting with `#` are now omitted, just like in `rustdoc`. 122 | * Support default bin entrypoint paths. 123 | * Fix command line parsing bug when cargo-rdme was invoked as a cargo subcommand. 124 | 125 | ## 0.1.0 126 | 127 | * Initial version. 128 | * Basic README syncronization. 129 | * Command line flags to control line terminator and entrypoint. 130 | * Support for configuration file. 131 | * Allow `cargo rdme` to be used easily integrated with CIs with the `--check` command line flag. 132 | 133 | ## 0.0.0 134 | 135 | * Initial release to reserve the crate's name in crates.io. 136 | -------------------------------------------------------------------------------- /src/console.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | */ 5 | 6 | use std::fmt::Display; 7 | use std::io::{IsTerminal, Write}; 8 | use termcolor::ColorChoice; 9 | use termcolor::WriteColor; 10 | use termcolor::{ColorSpec, StandardStream}; 11 | 12 | pub use termcolor::Color; 13 | 14 | fn is_stderr_terminal() -> bool { 15 | std::io::stderr().is_terminal() 16 | } 17 | 18 | fn print_color( 19 | stream: &mut StandardStream, 20 | level: impl Display, 21 | message: impl Display, 22 | color: Color, 23 | ) -> std::io::Result<()> { 24 | stream.reset()?; 25 | stream.set_color(ColorSpec::new().set_bold(true).set_fg(Some(color)))?; 26 | write!(stream, "{level}")?; 27 | stream.set_color(ColorSpec::new().set_bold(true))?; 28 | write!(stream, ":")?; 29 | stream.reset()?; 30 | writeln!(stream, " {message}")?; 31 | stream.flush() 32 | } 33 | 34 | fn print_nocolor( 35 | stream: &mut StandardStream, 36 | level: impl Display, 37 | message: impl Display, 38 | ) -> std::io::Result<()> { 39 | stream.reset()?; 40 | writeln!(stream, "{level}: {message}")?; 41 | stream.flush() 42 | } 43 | 44 | pub fn print_stderr(level: impl Display, message: impl Display, color: Color) { 45 | let mut stream = StandardStream::stderr(ColorChoice::Auto); 46 | 47 | match is_stderr_terminal() { 48 | true => { 49 | print_color(&mut stream, level, message, color).expect("error writing to stderr"); 50 | } 51 | false => { 52 | print_nocolor(&mut stream, level, message).expect("error writing to stderr"); 53 | } 54 | } 55 | } 56 | 57 | macro_rules! print_error { 58 | ($f:literal, $($arg:tt)*) => {{ 59 | $crate::console::print_stderr( 60 | "error", 61 | ::std::format!($f, $($arg)*), 62 | $crate::console::Color::Red, 63 | ); 64 | }}; 65 | } 66 | 67 | macro_rules! print_warning { 68 | ($f:literal, $($arg:tt)*) => {{ 69 | $crate::console::print_stderr( 70 | "warning", 71 | ::std::format!($f, $($arg)*), 72 | $crate::console::Color::Yellow, 73 | ); 74 | }}; 75 | } 76 | 77 | macro_rules! print_info { 78 | ($f:literal, $($arg:tt)*) => {{ 79 | println!($f, $($arg)*); 80 | }}; 81 | } 82 | -------------------------------------------------------------------------------- /src/extract_doc.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | */ 5 | 6 | use crate::Doc; 7 | use crate::markdown::Markdown; 8 | use std::path::{Path, PathBuf}; 9 | use syn::Expr; 10 | use thiserror::Error; 11 | 12 | #[derive(Error, Debug)] 13 | pub enum ExtractDocError { 14 | #[error("cannot open source file \"{0}\"")] 15 | ErrorReadingSourceFile(PathBuf), 16 | #[error("cannot parse source file: {0}")] 17 | ErrorParsingSourceFile(syn::Error), 18 | } 19 | 20 | pub fn extract_doc_from_source_file( 21 | file_path: impl AsRef, 22 | ) -> Result, ExtractDocError> { 23 | let source: String = std::fs::read_to_string(file_path.as_ref()) 24 | .map_err(|_| ExtractDocError::ErrorReadingSourceFile(file_path.as_ref().to_path_buf()))?; 25 | 26 | extract_doc_from_source_str(&source) 27 | } 28 | 29 | pub fn extract_doc_from_source_str(source: &str) -> Result, ExtractDocError> { 30 | use syn::{ExprLit, Lit, Meta, MetaNameValue, parse_str}; 31 | 32 | let ast: syn::File = parse_str(source).map_err(ExtractDocError::ErrorParsingSourceFile)?; 33 | let mut lines: Vec = Vec::with_capacity(1024); 34 | 35 | for attr in &ast.attrs { 36 | if Doc::is_toplevel_doc(attr) { 37 | if let Meta::NameValue(MetaNameValue { 38 | value: Expr::Lit(ExprLit { lit: Lit::Str(lstr), .. }), 39 | .. 40 | }) = &attr.meta 41 | { 42 | let string: String = lstr.value(); 43 | 44 | match string.lines().count() { 45 | 0 => lines.push(String::new()), 46 | 1 => { 47 | let line = 48 | string.strip_prefix(' ').map(ToOwned::to_owned).unwrap_or(string); 49 | lines.push(line); 50 | } 51 | 52 | // Multiline comment. 53 | _ => { 54 | fn empty_line(str: &str) -> bool { 55 | str.chars().all(char::is_whitespace) 56 | } 57 | 58 | let comment_lines = string 59 | .lines() 60 | .enumerate() 61 | .filter(|(i, l)| !(*i == 0 && empty_line(l))) 62 | .map(|(_, l)| l.to_owned()); 63 | 64 | lines.extend(comment_lines); 65 | } 66 | } 67 | } 68 | } 69 | } 70 | 71 | match lines.is_empty() { 72 | true => Ok(None), 73 | false => Ok(Some(Doc { markdown: Markdown::from_lines(&lines) })), 74 | } 75 | } 76 | 77 | #[cfg(test)] 78 | mod tests { 79 | use super::*; 80 | use indoc::indoc; 81 | use pretty_assertions::assert_eq; 82 | 83 | #[test] 84 | fn test_doc_from_source_str_no_doc() { 85 | let str = indoc! { r#" 86 | use std::fs; 87 | 88 | struct Nothing {} 89 | "# 90 | }; 91 | 92 | assert!(extract_doc_from_source_str(str).unwrap().is_none()); 93 | } 94 | 95 | #[test] 96 | fn test_doc_from_source_str_single_line_comment() { 97 | let str = indoc! { r#" 98 | #![cfg_attr(not(feature = "std"), no_std)] 99 | // normal comment 100 | 101 | //! This is the doc for the crate. 102 | //!This line doesn't start with space. 103 | //! 104 | //! And a nice empty line above us. 105 | //! Also a line ending in " 106 | 107 | struct Nothing {} 108 | "# 109 | }; 110 | 111 | let doc = extract_doc_from_source_str(str).unwrap().unwrap(); 112 | let lines: Vec<&str> = doc.lines().collect(); 113 | 114 | let expected = vec![ 115 | "This is the doc for the crate.", 116 | "This line doesn't start with space.", 117 | "", 118 | "And a nice empty line above us.", 119 | "Also a line ending in \"", 120 | ]; 121 | 122 | assert_eq!(lines, expected); 123 | } 124 | 125 | #[test] 126 | fn test_doc_from_source_str_multi_line_comment() { 127 | let str = indoc! { r#" 128 | #![cfg_attr(not(feature = "std"), no_std)] 129 | /* normal comment */ 130 | 131 | /*! 132 | This is the doc for the crate. 133 | This line start with space. 134 | 135 | And a nice empty line above us. 136 | */ 137 | 138 | struct Nothing {} 139 | "# 140 | }; 141 | 142 | let doc = extract_doc_from_source_str(str).unwrap().unwrap(); 143 | let lines: Vec<&str> = doc.lines().collect(); 144 | 145 | let expected = vec![ 146 | "This is the doc for the crate.", 147 | " This line start with space.", 148 | "", 149 | "And a nice empty line above us.", 150 | ]; 151 | 152 | assert_eq!(lines, expected); 153 | } 154 | 155 | #[test] 156 | fn test_doc_from_source_str_single_line_keep_indentation() { 157 | let str = indoc! { r#" 158 | #![cfg_attr(not(feature = "std"), no_std)] 159 | // normal comment 160 | 161 | //! This is the doc for the crate. This crate does: 162 | //! 163 | //! 1. nothing. 164 | //! 2. niente. 165 | 166 | struct Nothing {} 167 | "# 168 | }; 169 | 170 | let doc = extract_doc_from_source_str(str).unwrap().unwrap(); 171 | let lines: Vec<&str> = doc.lines().collect(); 172 | 173 | let expected = vec![ 174 | "This is the doc for the crate. This crate does:", 175 | "", 176 | " 1. nothing.", 177 | " 2. niente.", 178 | ]; 179 | 180 | assert_eq!(lines, expected); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | */ 5 | 6 | #![cfg_attr(feature = "fatal-warnings", deny(warnings))] 7 | 8 | use crate::markdown::{Markdown, MarkdownError}; 9 | use cargo_metadata::TargetKind; 10 | use std::collections::HashMap; 11 | use std::path::{Path, PathBuf}; 12 | use thiserror::Error; 13 | 14 | mod extract_doc; 15 | mod inject_doc; 16 | mod markdown; 17 | pub mod transform; 18 | pub mod utils; 19 | 20 | pub use extract_doc::{ExtractDocError, extract_doc_from_source_file}; 21 | pub use inject_doc::{InjectDocError, MARKER_RDME, inject_doc_in_readme}; 22 | 23 | #[derive(Error, Debug)] 24 | pub enum ProjectError { 25 | #[error("failed to get cargo metadata: {0}")] 26 | CargoMetadataError(cargo_metadata::Error), 27 | #[error("project has no root package")] 28 | ProjectHasNoRootPackage, 29 | #[error("project has no package \"{0}\"")] 30 | ProjectHasNoPackage(String), 31 | } 32 | 33 | impl From for ProjectError { 34 | fn from(e: cargo_metadata::Error) -> ProjectError { 35 | ProjectError::CargoMetadataError(e) 36 | } 37 | } 38 | 39 | pub fn find_first_file_in_ancestors(dir_path: impl AsRef, filename: &str) -> Option { 40 | for ancestor_dir in dir_path.as_ref().ancestors() { 41 | let file = ancestor_dir.join(filename); 42 | if file.is_file() { 43 | return Some(file); 44 | } 45 | } 46 | 47 | None 48 | } 49 | 50 | #[derive(PartialEq, Eq, Debug)] 51 | pub struct Project { 52 | package_name: String, 53 | readme_path: Option, 54 | lib_path: Option, 55 | bin_path: HashMap, 56 | directory: PathBuf, 57 | } 58 | 59 | impl Project { 60 | /// Creates a [`Project`] the current directory. It will search ancestor paths until it finds 61 | /// the root of the project. 62 | pub fn from_current_dir() -> Result { 63 | let metadata = Project::get_cargo_metadata()?; 64 | let package = metadata.root_package().ok_or(ProjectError::ProjectHasNoRootPackage)?; 65 | 66 | Ok(Project::from_package(package)) 67 | } 68 | 69 | fn get_cargo_metadata() -> Result { 70 | Ok(cargo_metadata::MetadataCommand::new().exec()?) 71 | } 72 | 73 | fn select_package<'a>( 74 | metadata: &'a cargo_metadata::Metadata, 75 | package_name: &str, 76 | ) -> Option<&'a cargo_metadata::Package> { 77 | metadata.packages.iter().find(|package| { 78 | package.name == package_name && metadata.workspace_members.contains(&package.id) 79 | }) 80 | } 81 | 82 | pub fn from_current_dir_workspace_project(project_name: &str) -> Result { 83 | let metadata = Project::get_cargo_metadata()?; 84 | 85 | let package = Project::select_package(&metadata, project_name) 86 | .ok_or_else(|| ProjectError::ProjectHasNoPackage(project_name.to_owned()))?; 87 | 88 | Ok(Project::from_package(package)) 89 | } 90 | 91 | fn from_package(package: &cargo_metadata::Package) -> Project { 92 | const LIB_CRATE_KINDS: [TargetKind; 6] = [ 93 | TargetKind::Lib, 94 | TargetKind::DyLib, 95 | TargetKind::StaticLib, 96 | TargetKind::CDyLib, 97 | TargetKind::RLib, 98 | TargetKind::ProcMacro, 99 | ]; 100 | let lib_packages: Vec<&cargo_metadata::Target> = package 101 | .targets 102 | .iter() 103 | .filter(|target| target.kind.iter().any(|k| LIB_CRATE_KINDS.contains(k))) 104 | .collect(); 105 | 106 | assert!(lib_packages.len() <= 1, "more than one lib target"); 107 | 108 | let lib_package = lib_packages.first(); 109 | 110 | let bin_packages = 111 | package.targets.iter().filter(|target| target.kind.contains(&TargetKind::Bin)); 112 | 113 | let directory = package 114 | .manifest_path 115 | .clone() 116 | .into_std_path_buf() 117 | .parent() 118 | .expect("error getting the parent path of the manifest file") 119 | .to_path_buf(); 120 | 121 | Project { 122 | package_name: package.name.clone(), 123 | readme_path: package.readme.as_ref().map(|p| p.clone().into_std_path_buf()), 124 | lib_path: lib_package.map(|t| t.src_path.clone().into_std_path_buf()), 125 | bin_path: bin_packages 126 | .map(|t| (t.name.clone(), t.src_path.clone().into_std_path_buf())) 127 | .collect(), 128 | directory, 129 | } 130 | } 131 | 132 | #[must_use] 133 | pub fn get_lib_entryfile_path(&self) -> Option<&Path> { 134 | self.lib_path.as_ref().filter(|p| p.is_file()).map(PathBuf::as_path) 135 | } 136 | 137 | #[must_use] 138 | pub fn get_bin_default_entryfile_path(&self) -> Option<&Path> { 139 | match self.bin_path.len() { 140 | 1 => self 141 | .bin_path 142 | .keys() 143 | .next() 144 | .and_then(|bin_name| self.get_bin_entryfile_path(bin_name)), 145 | _ => None, 146 | } 147 | } 148 | 149 | #[must_use] 150 | pub fn get_bin_entryfile_path(&self, name: &str) -> Option<&Path> { 151 | self.bin_path.get(name).filter(|p| p.is_file()).map(PathBuf::as_path) 152 | } 153 | 154 | #[must_use] 155 | pub fn get_readme_path(&self) -> Option { 156 | self.readme_path 157 | .clone() 158 | .or_else(|| Some(Path::new("README.md").to_path_buf())) 159 | .map(|p| self.directory.join(p)) 160 | .filter(|p| p.is_file()) 161 | } 162 | 163 | #[must_use] 164 | pub fn get_package_name(&self) -> &str { 165 | &self.package_name 166 | } 167 | } 168 | 169 | fn project_package_name(manifest_path: impl AsRef) -> Option { 170 | let str: String = std::fs::read_to_string(&manifest_path).ok()?; 171 | let toml: toml::Value = toml::from_str(&str).ok()?; 172 | let package_name = 173 | toml.get("package").and_then(|v| v.get("name")).and_then(toml::Value::as_str)?; 174 | 175 | Some(package_name.to_owned()) 176 | } 177 | 178 | #[derive(Eq, PartialEq, Clone, Debug)] 179 | pub struct Doc { 180 | pub markdown: Markdown, 181 | } 182 | 183 | impl Doc { 184 | #[must_use] 185 | pub fn from_markdown(markdown: Markdown) -> Doc { 186 | Doc { markdown } 187 | } 188 | 189 | // TODO implement FromStr when ! type is stable. 190 | #[allow(clippy::should_implement_trait)] 191 | pub fn from_str(str: impl Into) -> Doc { 192 | Doc { markdown: Markdown::from_str(str) } 193 | } 194 | 195 | fn is_toplevel_doc(attr: &syn::Attribute) -> bool { 196 | use syn::AttrStyle; 197 | use syn::token::Not; 198 | 199 | attr.style == AttrStyle::Inner(Not::default()) && attr.path().is_ident("doc") 200 | } 201 | 202 | pub fn lines(&self) -> impl Iterator { 203 | self.markdown.lines() 204 | } 205 | 206 | // Return the markdown as a string. Note that the line terminator will always be a line feed. 207 | #[must_use] 208 | pub fn as_string(&self) -> &str { 209 | self.markdown.as_string() 210 | } 211 | } 212 | 213 | #[derive(Error, Debug)] 214 | pub enum ReadmeError { 215 | #[error("failed to read README file \"{0}\"")] 216 | ErrorReadingReadmeFromFile(PathBuf), 217 | #[error("failed to write README file \"{0}\"")] 218 | ErrorWritingMarkdownToFile(PathBuf), 219 | #[error("failed to write README")] 220 | ErrorWritingMarkdown, 221 | } 222 | 223 | impl From for ReadmeError { 224 | fn from(e: MarkdownError) -> ReadmeError { 225 | match e { 226 | MarkdownError::ErrorReadingMarkdownFromFile(p) => { 227 | ReadmeError::ErrorReadingReadmeFromFile(p) 228 | } 229 | MarkdownError::ErrorWritingMarkdownToFile(p) => { 230 | ReadmeError::ErrorWritingMarkdownToFile(p) 231 | } 232 | MarkdownError::ErrorWritingMarkdown => ReadmeError::ErrorWritingMarkdown, 233 | } 234 | } 235 | } 236 | 237 | #[derive(Eq, PartialEq, Debug, Copy, Clone)] 238 | pub enum LineTerminator { 239 | Lf, 240 | CrLf, 241 | } 242 | 243 | pub struct Readme { 244 | pub markdown: Markdown, 245 | } 246 | 247 | impl Readme { 248 | pub fn from_file(file_path: impl AsRef) -> Result { 249 | Ok(Readme { markdown: Markdown::from_file(file_path)? }) 250 | } 251 | 252 | // TODO implement FromStr when ! type is stable. 253 | #[allow(clippy::should_implement_trait)] 254 | pub fn from_str(str: impl Into) -> Readme { 255 | Readme { markdown: Markdown::from_str(str) } 256 | } 257 | 258 | pub fn from_lines(lines: &[impl AsRef]) -> Readme { 259 | Readme { markdown: Markdown::from_lines(lines) } 260 | } 261 | 262 | pub fn lines(&self) -> impl Iterator { 263 | self.markdown.lines() 264 | } 265 | 266 | pub fn write_to_file( 267 | &self, 268 | file: impl AsRef, 269 | line_terminator: LineTerminator, 270 | ) -> Result<(), ReadmeError> { 271 | Ok(self.markdown.write_to_file(file, line_terminator)?) 272 | } 273 | 274 | pub fn write( 275 | &self, 276 | writer: impl std::io::Write, 277 | line_terminator: LineTerminator, 278 | ) -> Result<(), ReadmeError> { 279 | Ok(self.markdown.write(writer, line_terminator)?) 280 | } 281 | 282 | // Return the markdown as a string. Note that the line terminator will always be a line feed. 283 | #[must_use] 284 | pub fn as_string(&self) -> &str { 285 | self.markdown.as_string() 286 | } 287 | } 288 | 289 | pub fn infer_line_terminator(file_path: impl AsRef) -> std::io::Result { 290 | let content: String = std::fs::read_to_string(file_path.as_ref())?; 291 | 292 | let crlf_lines: usize = content.matches("\r\n").count(); 293 | let lf_lines: usize = content.matches('\n').count() - crlf_lines; 294 | 295 | if crlf_lines > lf_lines { Ok(LineTerminator::CrLf) } else { Ok(LineTerminator::Lf) } 296 | } 297 | -------------------------------------------------------------------------------- /src/markdown.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | */ 5 | 6 | use crate::LineTerminator; 7 | use itertools::Itertools; 8 | use std::fmt::Formatter; 9 | use std::fs::File; 10 | use std::path::{Path, PathBuf}; 11 | use thiserror::Error; 12 | 13 | #[derive(Error, Debug)] 14 | pub enum MarkdownError { 15 | #[error("failed to read markdown file \"{0}\"")] 16 | ErrorReadingMarkdownFromFile(PathBuf), 17 | #[error("failed to write markdown file \"{0}\"")] 18 | ErrorWritingMarkdownToFile(PathBuf), 19 | #[error("failed to write markdown")] 20 | ErrorWritingMarkdown, 21 | } 22 | 23 | #[derive(Eq, PartialEq, Clone)] 24 | pub struct Markdown { 25 | /// Content of the markdown. The line terminator is always `\n`. 26 | content: String, 27 | } 28 | 29 | impl Markdown { 30 | pub fn from_file(file_path: impl AsRef) -> Result { 31 | let content: String = std::fs::read_to_string(file_path.as_ref()).map_err(|_| { 32 | MarkdownError::ErrorReadingMarkdownFromFile(file_path.as_ref().to_path_buf()) 33 | })?; 34 | 35 | Ok(Markdown::from_str(content)) 36 | } 37 | 38 | pub fn from_str(str: impl Into) -> Markdown { 39 | let mut content = str.into().replace("\r\n", "\n"); 40 | 41 | // Lines must always end in newlines. 42 | if !content.ends_with('\n') { 43 | content.push('\n'); 44 | } 45 | 46 | Markdown { content } 47 | } 48 | 49 | pub fn from_lines(lines: &[impl AsRef]) -> Markdown { 50 | let str = lines.iter().map(AsRef::as_ref).join("\n"); 51 | 52 | Markdown::from_str(str) 53 | } 54 | 55 | pub fn lines(&self) -> impl Iterator { 56 | self.content.lines() 57 | } 58 | 59 | pub fn write_to_file( 60 | &self, 61 | file: impl AsRef, 62 | line_terminator: LineTerminator, 63 | ) -> Result<(), MarkdownError> { 64 | File::create(&file) 65 | .map_err(|_| MarkdownError::ErrorWritingMarkdownToFile(file.as_ref().to_path_buf())) 66 | .and_then(|f| self.write(f, line_terminator)) 67 | } 68 | 69 | pub fn write( 70 | &self, 71 | mut writer: impl std::io::Write, 72 | line_terminator: LineTerminator, 73 | ) -> Result<(), MarkdownError> { 74 | let mut write_line = |line: &str| -> std::io::Result<()> { 75 | writer.write_all(line.as_bytes())?; 76 | match line_terminator { 77 | LineTerminator::Lf => writer.write_all("\n".as_bytes()), 78 | LineTerminator::CrLf => writer.write_all("\r\n".as_bytes()), 79 | } 80 | }; 81 | 82 | for line in self.lines() { 83 | write_line(line).map_err(|_| MarkdownError::ErrorWritingMarkdown)?; 84 | } 85 | 86 | Ok(()) 87 | } 88 | 89 | // Return the markdown as a string. Note that the line terminator will always be a line feed. 90 | pub fn as_string(&self) -> &str { 91 | &self.content 92 | } 93 | } 94 | 95 | impl std::fmt::Debug for Markdown { 96 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 97 | use std::fmt::Write; 98 | 99 | for line in self.lines() { 100 | f.write_str(line)?; 101 | f.write_char('\n')?; 102 | } 103 | 104 | Ok(()) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/transform/intralinks/module_walker.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | */ 5 | 6 | use std::path::{Path, PathBuf}; 7 | use syn::Ident; 8 | use syn::Item; 9 | use thiserror::Error; 10 | 11 | use super::ItemPath; 12 | 13 | #[derive(Error, Debug)] 14 | pub enum ModuleWalkError { 15 | #[error("IO error: {0}")] 16 | IOError(std::io::Error), 17 | #[error("failed to parse rust file: {0}")] 18 | ParseError(syn::Error), 19 | } 20 | 21 | impl From for ModuleWalkError { 22 | fn from(err: std::io::Error) -> Self { 23 | ModuleWalkError::IOError(err) 24 | } 25 | } 26 | 27 | impl From for ModuleWalkError { 28 | fn from(err: syn::Error) -> Self { 29 | ModuleWalkError::ParseError(err) 30 | } 31 | } 32 | 33 | fn file_ast>(filepath: P) -> Result { 34 | let src = std::fs::read_to_string(filepath)?; 35 | 36 | Ok(syn::parse_file(&src)?) 37 | } 38 | 39 | /// Determines the module filename, which can be `.rs` or `/mod.rs`. 40 | fn module_filename(dir: &Path, module: &Ident) -> Option { 41 | let mod_file = dir.join(format!("{module}.rs")); 42 | 43 | if mod_file.is_file() { 44 | return Some(mod_file); 45 | } 46 | 47 | let mod_file = dir.join(module.to_string()).join("mod.rs"); 48 | 49 | if mod_file.is_file() { 50 | return Some(mod_file); 51 | } 52 | 53 | None 54 | } 55 | 56 | pub(super) fn walk_module_items( 57 | ast: &[Item], 58 | dir: &Path, 59 | mod_symbol: &ItemPath, 60 | visit: &mut impl FnMut(&ItemPath, &Item), 61 | explore_module: &mut impl FnMut(&ItemPath, &syn::ItemMod) -> bool, 62 | emit_warning: &impl Fn(&str), 63 | ) -> Result<(), ModuleWalkError> { 64 | for item in ast { 65 | visit(mod_symbol, item); 66 | 67 | if let Item::Mod(module) = item { 68 | let child_module_symbol: ItemPath = mod_symbol.clone().join(&module.ident.to_string()); 69 | 70 | if explore_module(&child_module_symbol, module) { 71 | match &module.content { 72 | Some((_, items)) => { 73 | walk_module_items( 74 | items, 75 | dir, 76 | &child_module_symbol, 77 | visit, 78 | explore_module, 79 | emit_warning, 80 | )?; 81 | } 82 | None => match module_filename(dir, &module.ident) { 83 | None => emit_warning(&format!( 84 | "Unable to find module file for module {} in directory \"{}\"", 85 | child_module_symbol, 86 | dir.display() 87 | )), 88 | Some(mod_filename) => walk_module_file( 89 | mod_filename, 90 | &child_module_symbol, 91 | visit, 92 | explore_module, 93 | emit_warning, 94 | )?, 95 | }, 96 | } 97 | } 98 | } 99 | } 100 | 101 | Ok(()) 102 | } 103 | 104 | pub(super) fn walk_module_file>( 105 | file: P, 106 | mod_symbol: &ItemPath, 107 | visit: &mut impl FnMut(&ItemPath, &Item), 108 | explore_module: &mut impl FnMut(&ItemPath, &syn::ItemMod) -> bool, 109 | emit_warning: &impl Fn(&str), 110 | ) -> Result<(), ModuleWalkError> { 111 | let dir: &Path = file 112 | .as_ref() 113 | .parent() 114 | .unwrap_or_else(|| panic!("failed to get directory of \"{}\"", file.as_ref().display())); 115 | let ast: syn::File = file_ast(&file)?; 116 | 117 | walk_module_items(&ast.items, dir, mod_symbol, visit, explore_module, emit_warning) 118 | } 119 | -------------------------------------------------------------------------------- /src/transform/mod.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | */ 5 | 6 | use crate::Doc; 7 | 8 | mod intralinks; 9 | mod rust_markdown_tag; 10 | mod rust_remove_comments; 11 | mod utils; 12 | 13 | pub use intralinks::{ 14 | DocTransformIntralinks, IntralinkError, IntralinksConfig, IntralinksDocsRsConfig, 15 | }; 16 | pub use rust_markdown_tag::DocTransformRustMarkdownTag; 17 | pub use rust_remove_comments::DocTransformRustRemoveComments; 18 | 19 | pub trait DocTransform { 20 | type E; 21 | 22 | fn transform(&self, doc: &Doc) -> Result; 23 | } 24 | -------------------------------------------------------------------------------- /src/transform/rust_markdown_tag.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | */ 5 | 6 | use crate::Doc; 7 | use crate::transform::DocTransform; 8 | use crate::transform::utils::rust_code_block_iterator; 9 | use crate::utils::ItemOrOther; 10 | use std::convert::Infallible; 11 | 12 | pub struct DocTransformRustMarkdownTag; 13 | 14 | impl DocTransformRustMarkdownTag { 15 | #[must_use] 16 | pub fn new() -> DocTransformRustMarkdownTag { 17 | DocTransformRustMarkdownTag 18 | } 19 | } 20 | 21 | fn process_code_block(new_doc_str: &mut String, code_block: &str) { 22 | let fenced = code_block.starts_with("```"); 23 | let mut base_indent = 0; 24 | 25 | if !fenced { 26 | while !new_doc_str.ends_with('\n') && !new_doc_str.is_empty() { 27 | new_doc_str.pop(); 28 | base_indent += 1; 29 | } 30 | 31 | new_doc_str.push_str("```rust\n"); 32 | } 33 | 34 | for (i, line) in code_block.split('\n').enumerate() { 35 | match i { 36 | 0 if fenced => { 37 | assert!( 38 | line.starts_with("```"), 39 | "fenced code block does not start with triple backtick" 40 | ); 41 | 42 | // A fence can have more than three backticks. We need to preserve that, since 43 | // it can be used to escape triple fences inside the code block itself. 44 | // See https://stackoverflow.com/a/31834381. 45 | line.chars().take_while(|c| *c == '`').for_each(|c| new_doc_str.push(c)); 46 | 47 | new_doc_str.push_str("rust"); 48 | } 49 | 0 => { 50 | new_doc_str.push_str(line); 51 | } 52 | _ => { 53 | new_doc_str.push('\n'); 54 | 55 | if line.len() > base_indent { 56 | new_doc_str.push_str(&line[base_indent..]); 57 | } 58 | } 59 | } 60 | } 61 | 62 | if !fenced { 63 | new_doc_str.push_str("```\n"); 64 | } 65 | } 66 | 67 | impl DocTransform for DocTransformRustMarkdownTag { 68 | type E = Infallible; 69 | 70 | fn transform(&self, doc: &Doc) -> Result { 71 | let mut new_doc_str = String::new(); 72 | 73 | for item_or_other in rust_code_block_iterator(&doc.markdown).complete() { 74 | match item_or_other { 75 | ItemOrOther::Item(code_block) => { 76 | process_code_block(&mut new_doc_str, code_block); 77 | } 78 | ItemOrOther::Other(other) => { 79 | new_doc_str.push_str(other); 80 | } 81 | } 82 | } 83 | 84 | Ok(Doc::from_str(new_doc_str)) 85 | } 86 | } 87 | 88 | #[cfg(test)] 89 | mod tests { 90 | use super::*; 91 | use indoc::indoc; 92 | use pretty_assertions::assert_eq; 93 | 94 | #[test] 95 | fn test_markdown_tag_no_code_block() { 96 | let doc_str = indoc! { r#" 97 | # The crate 98 | 99 | Look a this code: 100 | 101 | That's all! Have a nice day! 102 | "# 103 | }; 104 | 105 | let expected_str = indoc! { r#" 106 | # The crate 107 | 108 | Look a this code: 109 | 110 | That's all! Have a nice day! 111 | "# 112 | }; 113 | 114 | let doc = Doc::from_str(doc_str); 115 | let expected = Doc::from_str(expected_str); 116 | 117 | let transform = DocTransformRustMarkdownTag::new(); 118 | 119 | assert_eq!(transform.transform(&doc).unwrap(), expected); 120 | } 121 | 122 | #[test] 123 | fn test_markdown_tag_fenced_code_block() { 124 | let doc_str = indoc! { r#" 125 | # The crate 126 | 127 | Look a this code: 128 | 129 | ``` 130 | if true { 131 | println!("Hi"); 132 | } 133 | println!("There"); 134 | ``` 135 | 136 | A second one: 137 | 138 | ```rust 139 | println!("Hi"); 140 | ``` 141 | 142 | And so one: 143 | 144 | ```text 145 | A bit of text here. 146 | ``` 147 | 148 | And so forth: 149 | 150 | ```should_panic 151 | println!("Hi"); 152 | ``` 153 | 154 | That's all! Have a nice day! 155 | "# 156 | }; 157 | 158 | let expected_str = indoc! { r#" 159 | # The crate 160 | 161 | Look a this code: 162 | 163 | ```rust 164 | if true { 165 | println!("Hi"); 166 | } 167 | println!("There"); 168 | ``` 169 | 170 | A second one: 171 | 172 | ```rust 173 | println!("Hi"); 174 | ``` 175 | 176 | And so one: 177 | 178 | ```text 179 | A bit of text here. 180 | ``` 181 | 182 | And so forth: 183 | 184 | ```rust 185 | println!("Hi"); 186 | ``` 187 | 188 | That's all! Have a nice day! 189 | "# 190 | }; 191 | 192 | let doc = Doc::from_str(doc_str); 193 | let expected = Doc::from_str(expected_str); 194 | 195 | let transform = DocTransformRustMarkdownTag::new(); 196 | 197 | assert_eq!(transform.transform(&doc).unwrap(), expected); 198 | } 199 | 200 | #[test] 201 | fn test_markdown_tag_indent_code_block() { 202 | let doc_str = indoc! { r#" 203 | # The crate 204 | 205 | Look a this code: 206 | 207 | println!("Hi"); 208 | if true { 209 | println!("There"); 210 | } 211 | 212 | A second one: 213 | 214 | println!("Hi"); 215 | 216 | println!("There"); 217 | 218 | A second one: 219 | 220 | println!("Hi"); 221 | 222 | That's all! Have a nice day! 223 | "# 224 | }; 225 | 226 | let expected_str = indoc! { r#" 227 | # The crate 228 | 229 | Look a this code: 230 | 231 | ```rust 232 | println!("Hi"); 233 | if true { 234 | println!("There"); 235 | } 236 | ``` 237 | 238 | A second one: 239 | 240 | ```rust 241 | println!("Hi"); 242 | 243 | println!("There"); 244 | ``` 245 | 246 | A second one: 247 | 248 | ```rust 249 | println!("Hi"); 250 | ``` 251 | 252 | That's all! Have a nice day! 253 | "# 254 | }; 255 | 256 | let doc = Doc::from_str(doc_str); 257 | let expected = Doc::from_str(expected_str); 258 | 259 | let transform = DocTransformRustMarkdownTag::new(); 260 | 261 | assert_eq!(transform.transform(&doc).unwrap(), expected); 262 | } 263 | 264 | #[test] 265 | fn test_markdown_tag_indent_code_beginning_file() { 266 | let doc_str = indoc! { r#" 267 | println!("Hi"); 268 | 269 | That's all! Have a nice day! 270 | "# 271 | }; 272 | 273 | assert!(doc_str.starts_with(" println!"), "Ensure file starts correctly"); 274 | 275 | let expected_str = indoc! { r#" 276 | ```rust 277 | println!("Hi"); 278 | ``` 279 | 280 | That's all! Have a nice day! 281 | "# 282 | }; 283 | 284 | let doc = Doc::from_str(doc_str); 285 | let expected = Doc::from_str(expected_str); 286 | 287 | let transform = DocTransformRustMarkdownTag::new(); 288 | 289 | assert_eq!(transform.transform(&doc).unwrap(), expected); 290 | } 291 | 292 | #[test] 293 | fn test_markdown_tag_nested_fenced_block() { 294 | let doc_str = indoc! { r#" 295 | ```` 296 | ``` 297 | println!("Hi"); 298 | ``` 299 | ```` 300 | "# 301 | }; 302 | 303 | let expected_str = indoc! { r#" 304 | ````rust 305 | ``` 306 | println!("Hi"); 307 | ``` 308 | ```` 309 | "# 310 | }; 311 | 312 | let doc = Doc::from_str(doc_str); 313 | let expected = Doc::from_str(expected_str); 314 | 315 | let transform = DocTransformRustMarkdownTag::new(); 316 | 317 | assert_eq!(transform.transform(&doc).unwrap(), expected); 318 | } 319 | 320 | #[test] 321 | fn test_markdown_tag_for_known_code_block_tags() { 322 | let tags = [ 323 | "should_panic", 324 | "no_run", 325 | "ignore", 326 | "allow_fail", 327 | "rust", 328 | "test_harness", 329 | "compile_fail", 330 | "edition2018", 331 | "ignore-foo", 332 | ]; 333 | 334 | for tag in tags { 335 | let doc_str = 336 | format!("Foo:\n```{tag}\n# This is a comment.\nprintln!(\"#There\");\n```\nEnd\n"); 337 | 338 | let expected_str = 339 | "Foo:\n```rust\n# This is a comment.\nprintln!(\"#There\");\n```\nEnd\n"; 340 | 341 | let doc = Doc::from_str(doc_str); 342 | let expected = Doc::from_str(expected_str); 343 | 344 | let transform = DocTransformRustMarkdownTag::new(); 345 | 346 | assert_eq!(transform.transform(&doc).unwrap(), expected); 347 | } 348 | } 349 | } 350 | -------------------------------------------------------------------------------- /src/transform/rust_remove_comments.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | */ 5 | 6 | use crate::Doc; 7 | use crate::transform::DocTransform; 8 | use crate::transform::utils::rust_code_block_iterator; 9 | use crate::utils::ItemOrOther; 10 | use std::convert::Infallible; 11 | 12 | pub struct DocTransformRustRemoveComments; 13 | 14 | impl DocTransformRustRemoveComments { 15 | #[must_use] 16 | pub fn new() -> DocTransformRustRemoveComments { 17 | DocTransformRustRemoveComments 18 | } 19 | } 20 | 21 | fn is_line_commented(line: &str) -> bool { 22 | line.trim_start().starts_with("# ") || line.trim() == "#" 23 | } 24 | 25 | fn process_code_block(new_doc_str: &mut String, code_block: &str) { 26 | let mut first = true; 27 | 28 | for (i, line) in code_block.split('\n').enumerate() { 29 | // If we have an indent code block and we start with a comment we need to 30 | // drop any indent whitespace that started this indent block, since 31 | // pulldown-cmark doesn't consider it part of the code block. 32 | if i == 0 && is_line_commented(line) { 33 | while !new_doc_str.ends_with('\n') && !new_doc_str.is_empty() { 34 | new_doc_str.pop(); 35 | } 36 | } 37 | 38 | if !is_line_commented(line) { 39 | if !first { 40 | new_doc_str.push('\n'); 41 | } 42 | 43 | // Lines starting with `##` are not comments, that is a way to intentionally start a 44 | // line with `#`. See https://github.com/rust-lang/rust/pull/41785. 45 | match line.trim_start().starts_with("##") { 46 | true => new_doc_str.push_str(&line.replacen('#', "", 1)), 47 | false => new_doc_str.push_str(line), 48 | } 49 | 50 | first = false; 51 | } 52 | } 53 | } 54 | 55 | impl DocTransform for DocTransformRustRemoveComments { 56 | type E = Infallible; 57 | 58 | fn transform(&self, doc: &Doc) -> Result { 59 | let mut new_doc_str = String::new(); 60 | 61 | for item_or_other in rust_code_block_iterator(&doc.markdown).complete() { 62 | match item_or_other { 63 | ItemOrOther::Item(code_block) => { 64 | process_code_block(&mut new_doc_str, code_block); 65 | } 66 | ItemOrOther::Other(other) => { 67 | new_doc_str.push_str(other); 68 | } 69 | } 70 | } 71 | 72 | Ok(Doc::from_str(new_doc_str)) 73 | } 74 | } 75 | 76 | #[cfg(test)] 77 | mod tests { 78 | use super::*; 79 | use indoc::indoc; 80 | use pretty_assertions::assert_eq; 81 | 82 | #[test] 83 | fn test_remove_comments_no_code_block() { 84 | let doc_str = indoc! { r#" 85 | # The crate 86 | 87 | Look a this code: 88 | 89 | That's all! Have a nice day! 90 | "# 91 | }; 92 | 93 | let expected_str = indoc! { r#" 94 | # The crate 95 | 96 | Look a this code: 97 | 98 | That's all! Have a nice day! 99 | "# 100 | }; 101 | 102 | let doc = Doc::from_str(doc_str); 103 | let expected = Doc::from_str(expected_str); 104 | 105 | let transform = DocTransformRustRemoveComments::new(); 106 | 107 | assert_eq!(transform.transform(&doc).unwrap(), expected); 108 | } 109 | 110 | #[test] 111 | fn test_remove_comments_fenced_code_block() { 112 | let doc_str = indoc! { r#" 113 | # The crate 114 | 115 | Look a this code: 116 | 117 | ``` 118 | println!("Hi"); 119 | println!("There"); 120 | ``` 121 | 122 | A second one: 123 | 124 | ``` 125 | # A comment. 126 | println!("Hi"); 127 | println!("There"); 128 | ``` 129 | 130 | And so one: 131 | 132 | ``` 133 | println!("Hi"); 134 | # A comment. 135 | println!("There"); 136 | ``` 137 | 138 | And so forth: 139 | 140 | ``` 141 | println!("Hi"); 142 | println!("There"); 143 | # A comment. 144 | ``` 145 | 146 | That's all! Have a nice day! 147 | "# 148 | }; 149 | 150 | let expected_str = indoc! { r#" 151 | # The crate 152 | 153 | Look a this code: 154 | 155 | ``` 156 | println!("Hi"); 157 | println!("There"); 158 | ``` 159 | 160 | A second one: 161 | 162 | ``` 163 | println!("Hi"); 164 | println!("There"); 165 | ``` 166 | 167 | And so one: 168 | 169 | ``` 170 | println!("Hi"); 171 | println!("There"); 172 | ``` 173 | 174 | And so forth: 175 | 176 | ``` 177 | println!("Hi"); 178 | println!("There"); 179 | ``` 180 | 181 | That's all! Have a nice day! 182 | "# 183 | }; 184 | 185 | let doc = Doc::from_str(doc_str); 186 | let expected = Doc::from_str(expected_str); 187 | 188 | let transform = DocTransformRustRemoveComments::new(); 189 | 190 | assert_eq!(transform.transform(&doc).unwrap(), expected); 191 | } 192 | 193 | #[test] 194 | fn test_remove_comments_fenced_code_block_starting_with_whitespace() { 195 | let doc_str = indoc! { r#" 196 | # The crate 197 | 198 | Look a this code: 199 | 200 | ``` 201 | println!("Hi"); 202 | println!("There"); 203 | ``` 204 | 205 | A second one: 206 | 207 | ``` 208 | # A comment. 209 | println!("Hi"); 210 | println!("There"); 211 | ``` 212 | 213 | And so one: 214 | 215 | ``` 216 | println!("Hi"); 217 | # A comment. 218 | println!("There"); 219 | ``` 220 | 221 | And so forth: 222 | 223 | ``` 224 | println!("Hi"); 225 | println!("There"); 226 | # A comment. 227 | ``` 228 | 229 | That's all! Have a nice day! 230 | "# 231 | }; 232 | 233 | let expected_str = indoc! { r#" 234 | # The crate 235 | 236 | Look a this code: 237 | 238 | ``` 239 | println!("Hi"); 240 | println!("There"); 241 | ``` 242 | 243 | A second one: 244 | 245 | ``` 246 | println!("Hi"); 247 | println!("There"); 248 | ``` 249 | 250 | And so one: 251 | 252 | ``` 253 | println!("Hi"); 254 | println!("There"); 255 | ``` 256 | 257 | And so forth: 258 | 259 | ``` 260 | println!("Hi"); 261 | println!("There"); 262 | ``` 263 | 264 | That's all! Have a nice day! 265 | "# 266 | }; 267 | 268 | let doc = Doc::from_str(doc_str); 269 | let expected = Doc::from_str(expected_str); 270 | 271 | let transform = DocTransformRustRemoveComments::new(); 272 | 273 | assert_eq!(transform.transform(&doc).unwrap(), expected); 274 | } 275 | 276 | #[test] 277 | fn test_remove_comments_indent_code_block() { 278 | let doc_str = indoc! { r#" 279 | # The crate 280 | 281 | Look a this code: 282 | 283 | println!("Hi"); 284 | println!("There"); 285 | 286 | A second one: 287 | 288 | # A comment. 289 | println!("Hi"); 290 | println!("There"); 291 | 292 | And so one: 293 | 294 | println!("Hi"); 295 | # A comment. 296 | println!("There"); 297 | 298 | And so forth: 299 | 300 | println!("Hi"); 301 | println!("There"); 302 | # A comment. 303 | 304 | That's all! Have a nice day! 305 | "# 306 | }; 307 | 308 | let expected_str = indoc! { r#" 309 | # The crate 310 | 311 | Look a this code: 312 | 313 | println!("Hi"); 314 | println!("There"); 315 | 316 | A second one: 317 | 318 | println!("Hi"); 319 | println!("There"); 320 | 321 | And so one: 322 | 323 | println!("Hi"); 324 | println!("There"); 325 | 326 | And so forth: 327 | 328 | println!("Hi"); 329 | println!("There"); 330 | 331 | That's all! Have a nice day! 332 | "# 333 | }; 334 | 335 | let doc = Doc::from_str(doc_str); 336 | let expected = Doc::from_str(expected_str); 337 | 338 | let transform = DocTransformRustRemoveComments::new(); 339 | 340 | assert_eq!(transform.transform(&doc).unwrap(), expected); 341 | } 342 | 343 | #[test] 344 | fn test_remove_comments_indent_code_block_empty_lines() { 345 | let doc_str = indoc! { r#" 346 | # The crate 347 | 348 | Look a this code: 349 | 350 | println!("Hi"); 351 | 352 | println!("There"); 353 | 354 | That's all! Have a nice day! 355 | "# 356 | }; 357 | 358 | let expected_str = indoc! { r#" 359 | # The crate 360 | 361 | Look a this code: 362 | 363 | println!("Hi"); 364 | 365 | println!("There"); 366 | 367 | That's all! Have a nice day! 368 | "# 369 | }; 370 | 371 | let doc = Doc::from_str(doc_str); 372 | let expected = Doc::from_str(expected_str); 373 | 374 | let transform = DocTransformRustRemoveComments::new(); 375 | 376 | assert_eq!(transform.transform(&doc).unwrap(), expected); 377 | } 378 | 379 | #[test] 380 | fn test_remove_comments_indent_code_beginning_file_with_comment() { 381 | let doc_str = indoc! { r#" 382 | # Comment 383 | println!("Hi"); 384 | # x 385 | println!("There"); 386 | 387 | That's all! Have a nice day! 388 | "# 389 | }; 390 | 391 | assert!(doc_str.starts_with(" #"), "Ensure file starts correctly"); 392 | 393 | let expected_str = indoc! { r#" 394 | println!("Hi"); 395 | println!("There"); 396 | 397 | That's all! Have a nice day! 398 | "# 399 | }; 400 | 401 | let doc = Doc::from_str(doc_str); 402 | let expected = Doc::from_str(expected_str); 403 | 404 | let transform = DocTransformRustRemoveComments::new(); 405 | 406 | assert_eq!(transform.transform(&doc).unwrap(), expected); 407 | } 408 | 409 | #[test] 410 | fn test_remove_comments_indent_code_beginning_file_no_comment() { 411 | let doc_str = indoc! { r#" 412 | println!("Hi"); 413 | # x 414 | println!("There"); 415 | 416 | That's all! Have a nice day! 417 | "# 418 | }; 419 | 420 | assert!(doc_str.starts_with(" println!"), "Ensure file starts correctly"); 421 | 422 | let expected_str = indoc! { r#" 423 | println!("Hi"); 424 | println!("There"); 425 | 426 | That's all! Have a nice day! 427 | "# 428 | }; 429 | 430 | let doc = Doc::from_str(doc_str); 431 | let expected = Doc::from_str(expected_str); 432 | 433 | let transform = DocTransformRustRemoveComments::new(); 434 | 435 | assert_eq!(transform.transform(&doc).unwrap(), expected); 436 | } 437 | 438 | #[test] 439 | fn test_remove_comments_identify_comment() { 440 | let doc_str = indoc! { " 441 | # The crate 442 | 443 | Look a this code: 444 | 445 | ``` 446 | # This is a comment 447 | #This is not. 448 | println!(\"There\"); 449 | # 450 | # ↑ That line is a comment 451 | #\t 452 | # ↑ And so is that one. 453 | ``` 454 | " 455 | }; 456 | 457 | let expected_str = indoc! { r#" 458 | # The crate 459 | 460 | Look a this code: 461 | 462 | ``` 463 | #This is not. 464 | println!("There"); 465 | ``` 466 | "# 467 | }; 468 | 469 | let doc = Doc::from_str(doc_str); 470 | let expected = Doc::from_str(expected_str); 471 | 472 | let transform = DocTransformRustRemoveComments::new(); 473 | 474 | assert_eq!(transform.transform(&doc).unwrap(), expected); 475 | } 476 | 477 | #[test] 478 | fn test_remove_comments_double_hash_escape_comment() { 479 | let doc_str = indoc! { r#" 480 | ``` 481 | if true { 482 | ## This is not a comment. 483 | ##And neither is this. 484 | println!("There"); 485 | } 486 | ``` 487 | "# 488 | }; 489 | 490 | let expected_str = indoc! { r#" 491 | ``` 492 | if true { 493 | # This is not a comment. 494 | #And neither is this. 495 | println!("There"); 496 | } 497 | ``` 498 | "# 499 | }; 500 | 501 | let doc = Doc::from_str(doc_str); 502 | let expected = Doc::from_str(expected_str); 503 | 504 | let transform = DocTransformRustRemoveComments::new(); 505 | 506 | assert_eq!(transform.transform(&doc).unwrap(), expected); 507 | } 508 | 509 | #[test] 510 | fn test_remove_comments_for_known_code_block_tags() { 511 | let tags = [ 512 | "should_panic", 513 | "no_run", 514 | "ignore", 515 | "allow_fail", 516 | "rust", 517 | "test_harness", 518 | "compile_fail", 519 | "edition2018", 520 | "ignore-foo", 521 | ]; 522 | 523 | for tag in tags { 524 | let doc_str = format!("```{tag}\n# This is a comment.\nprintln!(\"#There\");\n```\n"); 525 | 526 | let expected_str = format!("```{tag}\nprintln!(\"#There\");\n```\n"); 527 | 528 | let doc = Doc::from_str(doc_str); 529 | let expected = Doc::from_str(expected_str); 530 | 531 | let transform = DocTransformRustRemoveComments::new(); 532 | 533 | assert_eq!(transform.transform(&doc).unwrap(), expected); 534 | } 535 | } 536 | 537 | #[test] 538 | fn test_remove_comments_for_unknown_code_block_tags_no_change() { 539 | let tags = ["text", "bash"]; 540 | 541 | for tag in tags { 542 | let doc_str = format!("```{tag}\n# This is a comment.\nprintln!(\"#There\");\n```\n"); 543 | let doc = Doc::from_str(doc_str); 544 | 545 | let transform = DocTransformRustRemoveComments::new(); 546 | 547 | assert_eq!(transform.transform(&doc).unwrap(), doc); 548 | } 549 | } 550 | 551 | #[test] 552 | fn test_remove_comments_nested_fenced_block() { 553 | let doc_str = indoc! { r#" 554 | ```` 555 | # Comment 1 556 | let s = " 557 | ``` 558 | "; 559 | # Comment 2 560 | println!("Hi"); 561 | let s = " 562 | ``` 563 | "; 564 | ```` 565 | "# 566 | }; 567 | 568 | let expected_str = indoc! { r#" 569 | ```` 570 | let s = " 571 | ``` 572 | "; 573 | println!("Hi"); 574 | let s = " 575 | ``` 576 | "; 577 | ```` 578 | "# 579 | }; 580 | 581 | let doc = Doc::from_str(doc_str); 582 | let expected = Doc::from_str(expected_str); 583 | 584 | let transform = DocTransformRustRemoveComments::new(); 585 | 586 | assert_eq!(transform.transform(&doc).unwrap(), expected); 587 | } 588 | } 589 | -------------------------------------------------------------------------------- /src/transform/utils.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | */ 5 | 6 | use crate::markdown::Markdown; 7 | use crate::utils::MarkdownItemIterator; 8 | 9 | pub fn is_rust_code_block(tags: &str) -> bool { 10 | tags.split(',').all(|tag| match tag { 11 | "should_panic" | "no_run" | "ignore" | "allow_fail" | "rust" | "test_harness" 12 | | "compile_fail" | "" => true, 13 | tag if tag.starts_with("ignore-") => true, 14 | tag if tag.starts_with("edition") => true, 15 | _ => false, 16 | }) 17 | } 18 | 19 | pub fn rust_code_block_iterator(markdown: &Markdown) -> MarkdownItemIterator<&str> { 20 | use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag}; 21 | 22 | let source = markdown.as_string(); 23 | let parser = Parser::new_ext(source, Options::all()); 24 | 25 | let iter = parser.into_offset_iter().filter_map(move |(event, range)| match event { 26 | Event::Start(Tag::CodeBlock(CodeBlockKind::Indented)) => { 27 | Some((range.clone().into(), &source[range])) 28 | } 29 | Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(tags))) if is_rust_code_block(&tags) => { 30 | Some((range.clone().into(), &source[range])) 31 | } 32 | _ => None, 33 | }); 34 | 35 | MarkdownItemIterator::new(source, iter) 36 | } 37 | 38 | #[cfg(test)] 39 | mod tests { 40 | use super::*; 41 | use indoc::indoc; 42 | use pretty_assertions::assert_eq; 43 | 44 | #[test] 45 | fn test_rust_code_block_iterator_items() { 46 | let doc = indoc! { r#" 47 | # The crate 48 | 49 | Look a this code: 50 | 51 | ``` 52 | println!("first"); 53 | ``` 54 | 55 | ```rust 56 | println!("second"); 57 | ``` 58 | 59 | ```text 60 | Just some text. 61 | ``` 62 | 63 | ```ignore,no_run 64 | println!("third"); 65 | ``` 66 | 67 | ```should_panic 68 | println!("fourth"); 69 | ``` 70 | 71 | That's ```all```! Have a nice `day`! 72 | "# 73 | }; 74 | let doc = Markdown::from_str(doc); 75 | 76 | let mut iter = rust_code_block_iterator(&doc).items(); 77 | 78 | assert_eq!(iter.next(), Some("```\nprintln!(\"first\");\n```")); 79 | assert_eq!(iter.next(), Some("```rust\nprintln!(\"second\");\n```")); 80 | assert_eq!(iter.next(), Some("```ignore,no_run\nprintln!(\"third\");\n```")); 81 | assert_eq!(iter.next(), Some("```should_panic\nprintln!(\"fourth\");\n```")); 82 | assert_eq!(iter.next(), None); 83 | } 84 | 85 | #[test] 86 | fn test_rust_code_block_iterator_items_known_code_block_tags() { 87 | let tags = [ 88 | "should_panic", 89 | "no_run", 90 | "ignore", 91 | "allow_fail", 92 | "rust", 93 | "test_harness", 94 | "compile_fail", 95 | "edition2018", 96 | "ignore-foo", 97 | ]; 98 | 99 | for tag in tags { 100 | let doc = format!("Foo:\n```{tag}\nprintln!(\"There\");\n```\nEnd\n"); 101 | let expected_str = format!("```{tag}\nprintln!(\"There\");\n```"); 102 | 103 | let doc = Markdown::from_str(doc); 104 | 105 | let mut iter = rust_code_block_iterator(&doc).items(); 106 | 107 | assert_eq!(iter.next(), Some(expected_str.as_str())); 108 | assert_eq!(iter.next(), None); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | */ 5 | 6 | use itertools::Itertools; 7 | use std::ops::Range; 8 | 9 | #[derive(Eq, PartialEq, Debug, Clone, Ord, PartialOrd)] 10 | pub struct Span { 11 | pub start: usize, 12 | pub end: usize, 13 | } 14 | 15 | impl From> for Span { 16 | fn from(range: Range) -> Self { 17 | Span { start: range.start, end: range.end } 18 | } 19 | } 20 | 21 | #[derive(PartialEq, Eq, Debug)] 22 | pub enum ItemOrOther<'a, T> { 23 | Item(T), 24 | Other(&'a str), 25 | } 26 | 27 | pub struct MarkdownItemIterator<'a, T> { 28 | source: &'a str, 29 | iter: Box + 'a>, 30 | } 31 | 32 | impl<'a, T> MarkdownItemIterator<'a, T> { 33 | pub fn new( 34 | source: &'a str, 35 | iter: impl Iterator + 'a, 36 | ) -> MarkdownItemIterator<'a, T> { 37 | MarkdownItemIterator { source, iter: Box::new(iter) } 38 | } 39 | 40 | #[allow(dead_code)] 41 | pub fn items(self) -> impl Iterator + 'a 42 | where 43 | T: 'a, 44 | { 45 | self.iter.map(|(_, item)| item) 46 | } 47 | 48 | #[allow(dead_code)] 49 | pub fn items_with_spans(self) -> impl Iterator + 'a 50 | where 51 | T: 'a, 52 | { 53 | self.iter 54 | } 55 | 56 | pub fn complete(self) -> impl Iterator> 57 | where 58 | T: Clone, 59 | { 60 | use std::iter::once; 61 | 62 | once(None) 63 | .chain(self.iter.map(Some)) 64 | .chain(once(None)) 65 | .tuple_windows() 66 | .flat_map(|(l, r)| match (l, r) { 67 | (None, Some((span, _))) => { 68 | [ItemOrOther::Other(&self.source[0..span.start]), ItemOrOther::Other("")] 69 | } 70 | (Some((span_1, v_1)), Some((span_2, _))) => [ 71 | ItemOrOther::Item(v_1), 72 | ItemOrOther::Other(&self.source[span_1.end..span_2.start]), 73 | ], 74 | (Some((span, v)), None) => { 75 | [ItemOrOther::Item(v), ItemOrOther::Other(&self.source[span.end..])] 76 | } 77 | (None, None) => [ItemOrOther::Other(self.source), ItemOrOther::Other("")], 78 | }) 79 | .filter(|e| !matches!(e, ItemOrOther::Other(""))) 80 | } 81 | } 82 | 83 | #[cfg(test)] 84 | mod tests { 85 | use super::*; 86 | use pretty_assertions::assert_eq; 87 | 88 | #[test] 89 | fn test_markdown_item_iterator_complete_no_item() { 90 | let str = "hello world"; 91 | 92 | let mut iter = MarkdownItemIterator::<()>::new(str, std::iter::empty()).complete(); 93 | 94 | assert_eq!(iter.next(), Some(ItemOrOther::Other("hello world"))); 95 | assert_eq!(iter.next(), None); 96 | } 97 | 98 | #[test] 99 | fn test_markdown_item_iterator_complete_item_start() { 100 | let str = "_hello world"; 101 | 102 | let items = [(Span::from(0..1), "x")]; 103 | 104 | let mut iter = MarkdownItemIterator::new(str, items.into_iter()).complete(); 105 | 106 | assert_eq!(iter.next(), Some(ItemOrOther::Item("x"))); 107 | assert_eq!(iter.next(), Some(ItemOrOther::Other("hello world"))); 108 | assert_eq!(iter.next(), None); 109 | } 110 | 111 | #[test] 112 | fn test_markdown_item_iterator_complete_item_end() { 113 | let str = "hello world_"; 114 | 115 | let items = [(Span::from(11..12), "x")]; 116 | 117 | let mut iter = MarkdownItemIterator::new(str, items.into_iter()).complete(); 118 | 119 | assert_eq!(iter.next(), Some(ItemOrOther::Other("hello world"))); 120 | assert_eq!(iter.next(), Some(ItemOrOther::Item("x"))); 121 | assert_eq!(iter.next(), None); 122 | } 123 | 124 | #[test] 125 | fn test_markdown_item_iterator_complete_item_middle() { 126 | let str = "hello _ world"; 127 | 128 | let items = [(Span::from(6..7), "x")]; 129 | 130 | let mut iter = MarkdownItemIterator::new(str, items.into_iter()).complete(); 131 | 132 | assert_eq!(iter.next(), Some(ItemOrOther::Other("hello "))); 133 | assert_eq!(iter.next(), Some(ItemOrOther::Item("x"))); 134 | assert_eq!(iter.next(), Some(ItemOrOther::Other(" world"))); 135 | assert_eq!(iter.next(), None); 136 | } 137 | 138 | #[test] 139 | fn test_markdown_item_iterator_complete_item_multiple() { 140 | let str = "hello _ world _ !"; 141 | 142 | let items = [(Span::from(6..7), "x"), (Span::from(14..15), "y")]; 143 | 144 | let mut iter = MarkdownItemIterator::new(str, items.into_iter()).complete(); 145 | 146 | assert_eq!(iter.next(), Some(ItemOrOther::Other("hello "))); 147 | assert_eq!(iter.next(), Some(ItemOrOther::Item("x"))); 148 | assert_eq!(iter.next(), Some(ItemOrOther::Other(" world "))); 149 | assert_eq!(iter.next(), Some(ItemOrOther::Item("y"))); 150 | assert_eq!(iter.next(), Some(ItemOrOther::Other(" !"))); 151 | assert_eq!(iter.next(), None); 152 | } 153 | 154 | #[test] 155 | fn test_markdown_item_iterator_complete_consecutive() { 156 | let str = "hello __ world!"; 157 | 158 | let items = [(Span::from(6..7), "x"), (Span::from(7..8), "y")]; 159 | 160 | let mut iter = MarkdownItemIterator::new(str, items.into_iter()).complete(); 161 | 162 | assert_eq!(iter.next(), Some(ItemOrOther::Other("hello "))); 163 | assert_eq!(iter.next(), Some(ItemOrOther::Item("x"))); 164 | assert_eq!(iter.next(), Some(ItemOrOther::Item("y"))); 165 | assert_eq!(iter.next(), Some(ItemOrOther::Other(" world!"))); 166 | assert_eq!(iter.next(), None); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /tests/.cargo-rdme.toml: -------------------------------------------------------------------------------- 1 | # This file is in `/tests` so the `/.cargo-rdme.toml` will never be picked up (we search the configuration file in 2 | # all ancerstor paths). 3 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | /*/Cargo.lock 2 | /*/target 3 | /*/README.md 4 | -------------------------------------------------------------------------------- /tests/avoid_overwrite_uncommitted_readme/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/avoid_overwrite_uncommitted_readme/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a crate that does nothing. 4 | 5 | fn foo() {} 6 | -------------------------------------------------------------------------------- /tests/crate_procmacro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | proc-macro = true 8 | -------------------------------------------------------------------------------- /tests/crate_procmacro/README-expected.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # My crate 4 | 5 | This is a crate that does nothing. 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/crate_procmacro/README-template.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/crate_procmacro/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a crate that does nothing. 4 | 5 | fn foo() {} 6 | -------------------------------------------------------------------------------- /tests/custom_lib_path/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | path = "src/l.rs" 8 | -------------------------------------------------------------------------------- /tests/custom_lib_path/README-expected.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # My crate 4 | 5 | This is a crate that does nothing. 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/custom_lib_path/README-template.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/custom_lib_path/src/l.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a crate that does nothing. 4 | 5 | fn main() {} 6 | -------------------------------------------------------------------------------- /tests/custom_readme_path/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | readme = "READ-ME.md" 6 | -------------------------------------------------------------------------------- /tests/custom_readme_path/README-expected.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # My crate 4 | 5 | This is a crate that does nothing. 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/custom_readme_path/README-template.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/custom_readme_path/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a crate that does nothing. 4 | 5 | fn foo() {} 6 | -------------------------------------------------------------------------------- /tests/entrypoint_bin_crate/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/entrypoint_bin_crate/README-expected.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # My crate 4 | 5 | This is a bin crate that does nothing. 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/entrypoint_bin_crate/README-template.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/entrypoint_bin_crate/src/main.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a bin crate that does nothing. 4 | 5 | fn main() {} 6 | -------------------------------------------------------------------------------- /tests/entrypoint_bin_lib_crate_lib_wins/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/entrypoint_bin_lib_crate_lib_wins/README-expected.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # My crate 4 | 5 | This is a lib crate that does nothing. 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/entrypoint_bin_lib_crate_lib_wins/README-template.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/entrypoint_bin_lib_crate_lib_wins/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a lib crate that does nothing. 4 | 5 | fn foo() {} 6 | -------------------------------------------------------------------------------- /tests/entrypoint_bin_lib_crate_lib_wins/src/main.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a bin crate that does nothing. 4 | 5 | fn main() {} 6 | -------------------------------------------------------------------------------- /tests/heading_level_auto_bump/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/heading_level_auto_bump/README-expected.md: -------------------------------------------------------------------------------- 1 | # My crate 2 | 3 | 4 | 5 | ## Foo 6 | 7 | This is a crate that does nothing. 8 | 9 | 10 | 11 | ## Bar 12 | 13 | bar! 14 | -------------------------------------------------------------------------------- /tests/heading_level_auto_bump/README-template.md: -------------------------------------------------------------------------------- 1 | # My crate 2 | 3 | 4 | 5 | ## Bar 6 | 7 | bar! 8 | -------------------------------------------------------------------------------- /tests/heading_level_auto_bump/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Foo 2 | //! 3 | //! This is a crate that does nothing. 4 | 5 | fn foo() {} 6 | -------------------------------------------------------------------------------- /tests/line_terminator_crlf/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=crlf 2 | -------------------------------------------------------------------------------- /tests/line_terminator_crlf/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/line_terminator_crlf/README-expected.md: -------------------------------------------------------------------------------- 1 | header 2 | 3 | 4 | 5 | # My crate 6 | 7 | This is a crate that does nothing. 8 | 9 | 10 | 11 | footer 12 | -------------------------------------------------------------------------------- /tests/line_terminator_crlf/README-template.md: -------------------------------------------------------------------------------- 1 | header 2 | 3 | 4 | 5 | Some old text here. 6 | 7 | 8 | 9 | footer 10 | -------------------------------------------------------------------------------- /tests/line_terminator_crlf/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a crate that does nothing. 4 | 5 | fn foo() {} 6 | -------------------------------------------------------------------------------- /tests/marker_inside_doc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/marker_inside_doc/README-expected.md: -------------------------------------------------------------------------------- 1 | header 2 | 3 | 4 | 5 | # My crate 6 | 7 | This is a crate that does nothing. 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | footer 18 | -------------------------------------------------------------------------------- /tests/marker_inside_doc/README-template.md: -------------------------------------------------------------------------------- 1 | header 2 | 3 | 4 | 5 | # My crate 6 | 7 | This is a crate that does nothing. 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | footer 18 | -------------------------------------------------------------------------------- /tests/marker_inside_doc/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a crate that does nothing. 4 | //! 5 | //! 6 | //! 7 | //! 8 | //! 9 | //! 10 | 11 | fn foo() {} 12 | -------------------------------------------------------------------------------- /tests/multiline_doc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/multiline_doc/README-expected.md: -------------------------------------------------------------------------------- 1 | header 2 | 3 | 4 | 5 | # My crate 6 | 7 | This is a crate that does nothing. 8 | 9 | 10 | 11 | footer 12 | -------------------------------------------------------------------------------- /tests/multiline_doc/README-template.md: -------------------------------------------------------------------------------- 1 | header 2 | 3 | 4 | 5 | footer 6 | -------------------------------------------------------------------------------- /tests/multiline_doc/src/lib.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | # My crate 3 | 4 | This is a crate that does nothing. 5 | */ 6 | fn foo() {} 7 | -------------------------------------------------------------------------------- /tests/option_cmd_check_fail/.gitignore: -------------------------------------------------------------------------------- 1 | !/README.md 2 | -------------------------------------------------------------------------------- /tests/option_cmd_check_fail/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/option_cmd_check_fail/README.md: -------------------------------------------------------------------------------- 1 | header 2 | 3 | 4 | 5 | # My crate 6 | 7 | This is a crate that does nothing. Really! 8 | 9 | 10 | 11 | footer 12 | -------------------------------------------------------------------------------- /tests/option_cmd_check_fail/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a crate that does nothing. 4 | 5 | fn foo() {} 6 | -------------------------------------------------------------------------------- /tests/option_cmd_check_fail_because_warnings/.gitignore: -------------------------------------------------------------------------------- 1 | !/README.md 2 | -------------------------------------------------------------------------------- /tests/option_cmd_check_fail_because_warnings/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/option_cmd_check_fail_because_warnings/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # My crate 4 | 5 | This is a crate that does nothing doesnotexist. 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/option_cmd_check_fail_because_warnings/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a crate that does nothing [doesnotexist](crate::doesnotexist). 4 | 5 | fn foo() {} 6 | -------------------------------------------------------------------------------- /tests/option_cmd_check_fail_line_terminator/.gitignore: -------------------------------------------------------------------------------- 1 | !/README.md 2 | -------------------------------------------------------------------------------- /tests/option_cmd_check_fail_line_terminator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/option_cmd_check_fail_line_terminator/README.md: -------------------------------------------------------------------------------- 1 | header 2 | 3 | 4 | 5 | # My crate 6 | 7 | This is a crate that does nothing. 8 | 9 | 10 | 11 | footer 12 | -------------------------------------------------------------------------------- /tests/option_cmd_check_fail_line_terminator/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a crate that does nothing. 4 | 5 | fn foo() {} 6 | -------------------------------------------------------------------------------- /tests/option_cmd_check_no_fail_on_warnings/.gitignore: -------------------------------------------------------------------------------- 1 | !/README.md 2 | -------------------------------------------------------------------------------- /tests/option_cmd_check_no_fail_on_warnings/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/option_cmd_check_no_fail_on_warnings/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # My crate 4 | 5 | This is a crate that does nothing doesnotexist. 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/option_cmd_check_no_fail_on_warnings/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a crate that does nothing [doesnotexist](crate::doesnotexist). 4 | 5 | fn foo() {} 6 | -------------------------------------------------------------------------------- /tests/option_cmd_check_ok/.gitignore: -------------------------------------------------------------------------------- 1 | !/README.md 2 | -------------------------------------------------------------------------------- /tests/option_cmd_check_ok/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/option_cmd_check_ok/README.md: -------------------------------------------------------------------------------- 1 | header 2 | 3 | 4 | 5 | # My crate 6 | 7 | This is a crate that does nothing. 8 | 9 | 10 | 11 | footer 12 | -------------------------------------------------------------------------------- /tests/option_cmd_check_ok/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a crate that does nothing. 4 | 5 | fn foo() {} 6 | -------------------------------------------------------------------------------- /tests/option_cmd_entrypoint_bin/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/option_cmd_entrypoint_bin/README-expected.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # My crate 4 | 5 | This is a bin crate that does nothing. 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/option_cmd_entrypoint_bin/README-template.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/option_cmd_entrypoint_bin/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a lib crate that does nothing. 4 | 5 | fn foo() {} 6 | -------------------------------------------------------------------------------- /tests/option_cmd_entrypoint_bin/src/main.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a bin crate that does nothing. 4 | 5 | fn main() {} 6 | -------------------------------------------------------------------------------- /tests/option_cmd_entrypoint_select_bin/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [[bin]] 7 | name = "foo" 8 | 9 | -------------------------------------------------------------------------------- /tests/option_cmd_entrypoint_select_bin/README-expected.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # My crate 4 | 5 | This is a foo crate that does nothing. 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/option_cmd_entrypoint_select_bin/README-template.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/option_cmd_entrypoint_select_bin/src/bin/foo.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a foo crate that does nothing. 4 | 5 | fn foo() {} 6 | -------------------------------------------------------------------------------- /tests/option_cmd_entrypoint_select_bin/src/main.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a main crate that does nothing. 4 | 5 | fn main() {} 6 | -------------------------------------------------------------------------------- /tests/option_cmd_entrypoint_select_bin_custom_path/.cargo-rdme.toml: -------------------------------------------------------------------------------- 1 | [entrypoint] 2 | type = "bin" 3 | bin-name = "foo" 4 | -------------------------------------------------------------------------------- /tests/option_cmd_entrypoint_select_bin_custom_path/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [[bin]] 7 | name = "foo" 8 | path = "src/foo-main.rs" 9 | -------------------------------------------------------------------------------- /tests/option_cmd_entrypoint_select_bin_custom_path/README-expected.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # My crate 4 | 5 | This is a foo crate that does nothing. 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/option_cmd_entrypoint_select_bin_custom_path/README-template.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/option_cmd_entrypoint_select_bin_custom_path/src/foo-main.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a foo crate that does nothing. 4 | 5 | fn foo() {} 6 | -------------------------------------------------------------------------------- /tests/option_cmd_entrypoint_select_bin_custom_path/src/main.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a main crate that does nothing. 4 | 5 | fn main() {} 6 | -------------------------------------------------------------------------------- /tests/option_cmd_entrypoint_select_bin_single/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [[bin]] 7 | path = "src/my-only-bin.rs" 8 | name = "foo" 9 | -------------------------------------------------------------------------------- /tests/option_cmd_entrypoint_select_bin_single/README-expected.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # My crate 4 | 5 | This crate does nothing! 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/option_cmd_entrypoint_select_bin_single/README-template.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/option_cmd_entrypoint_select_bin_single/src/my-only-bin.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This crate does nothing! 4 | 5 | fn main() {} 6 | -------------------------------------------------------------------------------- /tests/option_cmd_heading_base_level/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/option_cmd_heading_base_level/README-expected.md: -------------------------------------------------------------------------------- 1 | # My crate 2 | 3 | 4 | 5 | #### Foo 6 | 7 | This is a crate that does nothing. 8 | 9 | 10 | 11 | ## Bar 12 | 13 | bar! 14 | -------------------------------------------------------------------------------- /tests/option_cmd_heading_base_level/README-template.md: -------------------------------------------------------------------------------- 1 | # My crate 2 | 3 | 4 | 5 | ## Bar 6 | 7 | bar! 8 | -------------------------------------------------------------------------------- /tests/option_cmd_heading_base_level/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Foo 2 | //! 3 | //! This is a crate that does nothing. 4 | 5 | fn foo() {} 6 | -------------------------------------------------------------------------------- /tests/option_cmd_intralinks_strip_links/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/option_cmd_intralinks_strip_links/README-expected.md: -------------------------------------------------------------------------------- 1 | This will not be [modified](crate::amodule). 2 | 3 | 4 | 5 | This beautiful crate is cool because it contains modules and some 6 | other [stuff](https://en.wikipedia.org/wiki/Stuff) as well. 7 | 8 | This link is broken, but this should [wor\\k \[ju\]st](f\\i\(n\)e). 9 | 10 | Go ahead and check all the structs in foo specifically 11 | this one. Also, this is a nice function: copy. 12 | 13 | ![BestStruct doc](https://example.com/image.png) 14 | 15 | 16 | -------------------------------------------------------------------------------- /tests/option_cmd_intralinks_strip_links/README-template.md: -------------------------------------------------------------------------------- 1 | This will not be [modified](crate::amodule). 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /tests/option_cmd_intralinks_strip_links/src/main.rs: -------------------------------------------------------------------------------- 1 | //! This [beautiful crate](crate) is cool because it contains [modules](crate::amodule) and some 2 | //! other [stuff](https://en.wikipedia.org/wiki/Stuff) as well. 3 | //! 4 | //! This link is [broken](crate::broken), but this should [wor\\k \[ju\]st](f\\i\(n\)e). 5 | //! 6 | //! Go ahead and check all the [structs in foo](crate::foo#structs) specifically 7 | //! [this one](crate::foo::BestStruct). Also, this is a nice function: [copy](::std::fs::copy). 8 | //! 9 | //! [![BestStruct doc](https://example.com/image.png)](crate::foo::BestStruct) 10 | 11 | mod amodule {} 12 | 13 | mod foo { 14 | struct BestStruct {} 15 | } 16 | 17 | fn main() {} 18 | -------------------------------------------------------------------------------- /tests/option_cmd_line_terminator_crlf/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | README-expected.md text=auto eol=crlf 3 | -------------------------------------------------------------------------------- /tests/option_cmd_line_terminator_crlf/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/option_cmd_line_terminator_crlf/README-expected.md: -------------------------------------------------------------------------------- 1 | header 2 | 3 | 4 | 5 | # My crate 6 | 7 | This is a crate that does nothing. 8 | 9 | 10 | 11 | footer 12 | -------------------------------------------------------------------------------- /tests/option_cmd_line_terminator_crlf/README-template.md: -------------------------------------------------------------------------------- 1 | header 2 | 3 | 4 | 5 | Some old text here. 6 | 7 | 8 | 9 | footer 10 | -------------------------------------------------------------------------------- /tests/option_cmd_line_terminator_crlf/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a crate that does nothing. 4 | 5 | fn foo() {} 6 | -------------------------------------------------------------------------------- /tests/option_cmd_line_terminator_lf/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=crlf 2 | README-expected.md text=auto eol=lf 3 | -------------------------------------------------------------------------------- /tests/option_cmd_line_terminator_lf/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/option_cmd_line_terminator_lf/README-expected.md: -------------------------------------------------------------------------------- 1 | header 2 | 3 | 4 | 5 | # My crate 6 | 7 | This is a crate that does nothing. 8 | 9 | 10 | 11 | footer 12 | -------------------------------------------------------------------------------- /tests/option_cmd_line_terminator_lf/README-template.md: -------------------------------------------------------------------------------- 1 | header 2 | 3 | 4 | 5 | Some old text here. 6 | 7 | 8 | 9 | footer 10 | -------------------------------------------------------------------------------- /tests/option_cmd_line_terminator_lf/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a crate that does nothing. 4 | 5 | fn foo() {} 6 | -------------------------------------------------------------------------------- /tests/option_cmd_override_readme_path/.gitignore: -------------------------------------------------------------------------------- 1 | /r.md 2 | -------------------------------------------------------------------------------- /tests/option_cmd_override_readme_path/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/option_cmd_override_readme_path/README-expected.md: -------------------------------------------------------------------------------- 1 | header 2 | 3 | 4 | 5 | # My crate 6 | 7 | This is a crate that does nothing. 8 | 9 | 10 | 11 | footer 12 | -------------------------------------------------------------------------------- /tests/option_cmd_override_readme_path/README-template.md: -------------------------------------------------------------------------------- 1 | header 2 | 3 | 4 | 5 | Some old text here. 6 | 7 | 8 | 9 | footer 10 | -------------------------------------------------------------------------------- /tests/option_cmd_override_readme_path/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a crate that does nothing. 4 | 5 | fn foo() {} 6 | -------------------------------------------------------------------------------- /tests/option_cmd_workspace/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "otherproj", 5 | "myproj", 6 | ] 7 | -------------------------------------------------------------------------------- /tests/option_cmd_workspace/README-expected.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # My crate 4 | 5 | This is otherproj. 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/option_cmd_workspace/README-template.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/option_cmd_workspace/myproj/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "myproj" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/option_cmd_workspace/myproj/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is myproj. 4 | 5 | fn foo() {} 6 | -------------------------------------------------------------------------------- /tests/option_cmd_workspace/otherproj/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "otherproj" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/option_cmd_workspace/otherproj/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is otherproj. 4 | 5 | fn foo() {} 6 | -------------------------------------------------------------------------------- /tests/option_cmd_workspace_dependency_collision/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "cargo-rdme", 5 | "dependent", 6 | ] 7 | -------------------------------------------------------------------------------- /tests/option_cmd_workspace_dependency_collision/README-expected.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Fake cargo-rdme 4 | 5 | This is a fake cargo-rdme crate, designed to conflict with the one published on crates.io. 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/option_cmd_workspace_dependency_collision/README-template.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/option_cmd_workspace_dependency_collision/cargo-rdme/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cargo-rdme" 3 | version = "9000.0.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/option_cmd_workspace_dependency_collision/cargo-rdme/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Fake cargo-rdme 2 | //! 3 | //! This is a fake cargo-rdme crate, designed to conflict with the one published on crates.io. 4 | 5 | fn foo() {} 6 | -------------------------------------------------------------------------------- /tests/option_cmd_workspace_dependency_collision/dependent/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dependent" 3 | version = "0.0.1" 4 | edition = "2021" 5 | 6 | 7 | [dependencies] 8 | # Depends on a published version of a crate with a lower version than the one 9 | # of the same name in this workspace 10 | cargo-rdme = "1.4.3" 11 | 12 | [package.metadata.cargo-machete] 13 | ignored = ["cargo-rdme"] 14 | -------------------------------------------------------------------------------- /tests/option_cmd_workspace_dependency_collision/dependent/src/lib.rs: -------------------------------------------------------------------------------- 1 | fn foo() {} 2 | -------------------------------------------------------------------------------- /tests/option_conf_file_entrypoint_bin/.cargo-rdme.toml: -------------------------------------------------------------------------------- 1 | [entrypoint] 2 | type = "bin" 3 | -------------------------------------------------------------------------------- /tests/option_conf_file_entrypoint_bin/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/option_conf_file_entrypoint_bin/README-expected.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # My crate 4 | 5 | This is a bin crate that does nothing. 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/option_conf_file_entrypoint_bin/README-template.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/option_conf_file_entrypoint_bin/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a lib crate that does nothing. 4 | 5 | fn foo() {} 6 | -------------------------------------------------------------------------------- /tests/option_conf_file_entrypoint_bin/src/main.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a bin crate that does nothing. 4 | 5 | fn main() {} 6 | -------------------------------------------------------------------------------- /tests/option_conf_file_entrypoint_select_bin/.cargo-rdme.toml: -------------------------------------------------------------------------------- 1 | [entrypoint] 2 | type = "bin" 3 | bin-name = "foo" 4 | -------------------------------------------------------------------------------- /tests/option_conf_file_entrypoint_select_bin/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [[bin]] 7 | name = "foo" 8 | -------------------------------------------------------------------------------- /tests/option_conf_file_entrypoint_select_bin/README-expected.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # My crate 4 | 5 | This is a foo crate that does nothing. 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/option_conf_file_entrypoint_select_bin/README-template.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/option_conf_file_entrypoint_select_bin/src/bin/foo.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a foo crate that does nothing. 4 | 5 | fn foo() {} 6 | -------------------------------------------------------------------------------- /tests/option_conf_file_entrypoint_select_bin/src/main.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a main crate that does nothing. 4 | 5 | fn main() {} 6 | -------------------------------------------------------------------------------- /tests/option_conf_file_entrypoint_select_bin_custom_path/.cargo-rdme.toml: -------------------------------------------------------------------------------- 1 | [entrypoint] 2 | type = "bin" 3 | bin-name = "foo" 4 | -------------------------------------------------------------------------------- /tests/option_conf_file_entrypoint_select_bin_custom_path/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [[bin]] 7 | name = "foo" 8 | path = "src/foo-main.rs" 9 | -------------------------------------------------------------------------------- /tests/option_conf_file_entrypoint_select_bin_custom_path/README-expected.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # My crate 4 | 5 | This is a foo crate that does nothing. 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/option_conf_file_entrypoint_select_bin_custom_path/README-template.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/option_conf_file_entrypoint_select_bin_custom_path/src/foo-main.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a foo crate that does nothing. 4 | 5 | fn foo() {} 6 | -------------------------------------------------------------------------------- /tests/option_conf_file_entrypoint_select_bin_custom_path/src/main.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a main crate that does nothing. 4 | 5 | fn main() {} 6 | -------------------------------------------------------------------------------- /tests/option_conf_file_heading_base_level/.cargo-rdme.toml: -------------------------------------------------------------------------------- 1 | heading-base-level = 0 2 | -------------------------------------------------------------------------------- /tests/option_conf_file_heading_base_level/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/option_conf_file_heading_base_level/README-expected.md: -------------------------------------------------------------------------------- 1 | # My crate 2 | 3 | 4 | 5 | # Foo 6 | 7 | This is a crate that does nothing. 8 | 9 | 10 | 11 | ## Bar 12 | 13 | bar! 14 | -------------------------------------------------------------------------------- /tests/option_conf_file_heading_base_level/README-template.md: -------------------------------------------------------------------------------- 1 | # My crate 2 | 3 | 4 | 5 | ## Bar 6 | 7 | bar! 8 | -------------------------------------------------------------------------------- /tests/option_conf_file_heading_base_level/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Foo 2 | //! 3 | //! This is a crate that does nothing. 4 | 5 | fn foo() {} 6 | -------------------------------------------------------------------------------- /tests/option_conf_file_intralinks_docs_rs_base_url/.cargo-rdme.toml: -------------------------------------------------------------------------------- 1 | [intralinks] 2 | docs-rs-base-url = "https://mydocs.rs" 3 | -------------------------------------------------------------------------------- /tests/option_conf_file_intralinks_docs_rs_base_url/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/option_conf_file_intralinks_docs_rs_base_url/README-expected.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | This [beautiful crate](https://mydocs.rs/integration_test/latest/integration_test/) is cool because it contains [modules](https://mydocs.rs/integration_test/latest/integration_test/amodule/) and may use 4 | [copy](https://doc.rust-lang.org/stable/std/fs/fn.copy.html). 5 | 6 | 7 | -------------------------------------------------------------------------------- /tests/option_conf_file_intralinks_docs_rs_base_url/README-template.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/option_conf_file_intralinks_docs_rs_base_url/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This [beautiful crate](crate) is cool because it contains [modules](crate::amodule) and may use 2 | //! [copy](::std::fs::copy). 3 | 4 | mod amodule {} 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /tests/option_conf_file_intralinks_docs_rs_version/.cargo-rdme.toml: -------------------------------------------------------------------------------- 1 | [intralinks] 2 | docs-rs-version = "1.0.0" 3 | -------------------------------------------------------------------------------- /tests/option_conf_file_intralinks_docs_rs_version/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/option_conf_file_intralinks_docs_rs_version/README-expected.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | This [beautiful crate](https://docs.rs/integration_test/1.0.0/integration_test/) is cool because it contains [modules](https://docs.rs/integration_test/1.0.0/integration_test/amodule/) and may use 4 | [copy](https://doc.rust-lang.org/stable/std/fs/fn.copy.html). 5 | 6 | 7 | -------------------------------------------------------------------------------- /tests/option_conf_file_intralinks_docs_rs_version/README-template.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/option_conf_file_intralinks_docs_rs_version/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This [beautiful crate](crate) is cool because it contains [modules](crate::amodule) and may use 2 | //! [copy](::std::fs::copy). 3 | 4 | mod amodule {} 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /tests/option_conf_file_intralinks_strip_links/.cargo-rdme.toml: -------------------------------------------------------------------------------- 1 | [intralinks] 2 | strip-links = true 3 | -------------------------------------------------------------------------------- /tests/option_conf_file_intralinks_strip_links/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/option_conf_file_intralinks_strip_links/README-expected.md: -------------------------------------------------------------------------------- 1 | This will not be [modified](crate::amodule). 2 | 3 | 4 | 5 | This beautiful crate is cool because it contains modules and some 6 | other [stuff](https://en.wikipedia.org/wiki/Stuff) as well. 7 | 8 | This link is broken, but this should [wor\\k \[ju\]st](f\\i\(n\)e). 9 | 10 | Go ahead and check all the structs in foo specifically 11 | this one. Also, this is a nice function: copy. 12 | 13 | ![BestStruct doc](https://example.com/image.png) 14 | 15 | 16 | -------------------------------------------------------------------------------- /tests/option_conf_file_intralinks_strip_links/README-template.md: -------------------------------------------------------------------------------- 1 | This will not be [modified](crate::amodule). 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /tests/option_conf_file_intralinks_strip_links/src/main.rs: -------------------------------------------------------------------------------- 1 | //! This [beautiful crate](crate) is cool because it contains [modules](crate::amodule) and some 2 | //! other [stuff](https://en.wikipedia.org/wiki/Stuff) as well. 3 | //! 4 | //! This link is [broken](crate::broken), but this should [wor\\k \[ju\]st](f\\i\(n\)e). 5 | //! 6 | //! Go ahead and check all the [structs in foo](crate::foo#structs) specifically 7 | //! [this one](crate::foo::BestStruct). Also, this is a nice function: [copy](::std::fs::copy). 8 | //! 9 | //! [![BestStruct doc](https://example.com/image.png)](crate::foo::BestStruct) 10 | 11 | mod amodule {} 12 | 13 | mod foo { 14 | struct BestStruct {} 15 | } 16 | 17 | fn main() {} 18 | -------------------------------------------------------------------------------- /tests/option_conf_file_line_terminator_crlf/.cargo-rdme.toml: -------------------------------------------------------------------------------- 1 | line-terminator = "crlf" 2 | -------------------------------------------------------------------------------- /tests/option_conf_file_line_terminator_crlf/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | README-expected.md text=auto eol=crlf 3 | -------------------------------------------------------------------------------- /tests/option_conf_file_line_terminator_crlf/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/option_conf_file_line_terminator_crlf/README-expected.md: -------------------------------------------------------------------------------- 1 | header 2 | 3 | 4 | 5 | # My crate 6 | 7 | This is a crate that does nothing. 8 | 9 | 10 | 11 | footer 12 | -------------------------------------------------------------------------------- /tests/option_conf_file_line_terminator_crlf/README-template.md: -------------------------------------------------------------------------------- 1 | header 2 | 3 | 4 | 5 | Some old text here. 6 | 7 | 8 | 9 | footer 10 | -------------------------------------------------------------------------------- /tests/option_conf_file_line_terminator_crlf/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a crate that does nothing. 4 | 5 | fn foo() {} 6 | -------------------------------------------------------------------------------- /tests/option_conf_file_line_terminator_lf/.cargo-rdme.toml: -------------------------------------------------------------------------------- 1 | line-terminator = "lf" 2 | -------------------------------------------------------------------------------- /tests/option_conf_file_line_terminator_lf/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=crlf 2 | README-expected.md text=auto eol=lf 3 | -------------------------------------------------------------------------------- /tests/option_conf_file_line_terminator_lf/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/option_conf_file_line_terminator_lf/README-expected.md: -------------------------------------------------------------------------------- 1 | header 2 | 3 | 4 | 5 | # My crate 6 | 7 | This is a crate that does nothing. 8 | 9 | 10 | 11 | footer 12 | -------------------------------------------------------------------------------- /tests/option_conf_file_line_terminator_lf/README-template.md: -------------------------------------------------------------------------------- 1 | header 2 | 3 | 4 | 5 | Some old text here. 6 | 7 | 8 | 9 | footer 10 | -------------------------------------------------------------------------------- /tests/option_conf_file_line_terminator_lf/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a crate that does nothing. 4 | 5 | fn foo() {} 6 | -------------------------------------------------------------------------------- /tests/option_conf_file_override_readme_path/.cargo-rdme.toml: -------------------------------------------------------------------------------- 1 | readme-path = "r.md" 2 | -------------------------------------------------------------------------------- /tests/option_conf_file_override_readme_path/.gitignore: -------------------------------------------------------------------------------- 1 | /r.md 2 | -------------------------------------------------------------------------------- /tests/option_conf_file_override_readme_path/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/option_conf_file_override_readme_path/README-expected.md: -------------------------------------------------------------------------------- 1 | header 2 | 3 | 4 | 5 | # My crate 6 | 7 | This is a crate that does nothing. 8 | 9 | 10 | 11 | footer 12 | -------------------------------------------------------------------------------- /tests/option_conf_file_override_readme_path/README-template.md: -------------------------------------------------------------------------------- 1 | header 2 | 3 | 4 | 5 | Some old text here. 6 | 7 | 8 | 9 | footer 10 | -------------------------------------------------------------------------------- /tests/option_conf_file_override_readme_path/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a crate that does nothing. 4 | 5 | fn foo() {} 6 | -------------------------------------------------------------------------------- /tests/option_conf_file_workspace/.cargo-rdme.toml: -------------------------------------------------------------------------------- 1 | workspace-project = "myproj" 2 | readme-path = "README.md" 3 | -------------------------------------------------------------------------------- /tests/option_conf_file_workspace/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "otherproj", 5 | "myproj", 6 | ] 7 | -------------------------------------------------------------------------------- /tests/option_conf_file_workspace/README-expected.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # My crate 4 | 5 | This is myproj. 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/option_conf_file_workspace/README-template.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/option_conf_file_workspace/myproj/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "myproj" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/option_conf_file_workspace/myproj/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is myproj. 4 | 5 | fn foo() {} 6 | -------------------------------------------------------------------------------- /tests/option_conf_file_workspace/otherproj/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "otherproj" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/option_conf_file_workspace/otherproj/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is otherproj. 4 | 5 | fn foo() {} 6 | -------------------------------------------------------------------------------- /tests/separate_bin_and_lib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | name = "thelib" 8 | path = "src/lib/lib.rs" 9 | 10 | [[bin]] 11 | name = "thebin" 12 | path = "src/bin/the-binary/main.rs" 13 | -------------------------------------------------------------------------------- /tests/separate_bin_and_lib/README-expected.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # My crate 4 | 5 | Bin here. 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/separate_bin_and_lib/README-template.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/separate_bin_and_lib/src/bin/the-binary/main.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! Bin here. 4 | 5 | fn main() {} 6 | -------------------------------------------------------------------------------- /tests/separate_bin_and_lib/src/lib/lib.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! Lib here. 4 | 5 | fn main() {} 6 | -------------------------------------------------------------------------------- /tests/simple_single_marker/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/simple_single_marker/README-expected.md: -------------------------------------------------------------------------------- 1 | header 2 | 3 | 4 | 5 | # My crate 6 | 7 | This is a crate that does nothing. 8 | 9 | 10 | 11 | footer 12 | -------------------------------------------------------------------------------- /tests/simple_single_marker/README-template.md: -------------------------------------------------------------------------------- 1 | header 2 | 3 | 4 | 5 | footer 6 | -------------------------------------------------------------------------------- /tests/simple_single_marker/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a crate that does nothing. 4 | 5 | fn foo() {} 6 | -------------------------------------------------------------------------------- /tests/simple_single_marker_no_footer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/simple_single_marker_no_footer/README-expected.md: -------------------------------------------------------------------------------- 1 | header 2 | 3 | 4 | 5 | # My crate 6 | 7 | This is a crate that does nothing. 8 | 9 | 10 | -------------------------------------------------------------------------------- /tests/simple_single_marker_no_footer/README-template.md: -------------------------------------------------------------------------------- 1 | header 2 | 3 | 4 | -------------------------------------------------------------------------------- /tests/simple_single_marker_no_footer/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a crate that does nothing. 4 | 5 | fn foo() {} 6 | -------------------------------------------------------------------------------- /tests/simple_start_end_marker/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/simple_start_end_marker/README-expected.md: -------------------------------------------------------------------------------- 1 | header 2 | 3 | 4 | 5 | # My crate 6 | 7 | This is a crate that does nothing. 8 | 9 | 10 | 11 | footer 12 | -------------------------------------------------------------------------------- /tests/simple_start_end_marker/README-template.md: -------------------------------------------------------------------------------- 1 | header 2 | 3 | 4 | 5 | Some old text here. 6 | 7 | 8 | 9 | footer 10 | -------------------------------------------------------------------------------- /tests/simple_start_end_marker/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a crate that does nothing. 4 | 5 | fn foo() {} 6 | -------------------------------------------------------------------------------- /tests/testing.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | */ 5 | 6 | #![allow(clippy::missing_panics_doc)] 7 | 8 | use std::io::Write; 9 | use std::path::{Path, PathBuf}; 10 | use std::process::Command; 11 | 12 | pub struct TestOptions { 13 | pub readme_filename: &'static str, 14 | pub args: &'static [&'static str], 15 | pub expected_exit_code: i32, 16 | pub check_readme_expected: bool, 17 | pub force: bool, 18 | } 19 | 20 | impl Default for TestOptions { 21 | fn default() -> Self { 22 | TestOptions { 23 | readme_filename: "README.md", 24 | args: &[], 25 | expected_exit_code: 0, 26 | check_readme_expected: true, 27 | force: true, 28 | } 29 | } 30 | } 31 | 32 | #[must_use] 33 | pub fn test_dir(test_name: &str) -> PathBuf { 34 | let project_dir = std::env::current_dir().unwrap(); 35 | project_dir.join("tests").join(test_name) 36 | } 37 | 38 | #[must_use] 39 | pub fn test_readme_template(test_name: &str) -> PathBuf { 40 | test_dir(test_name).join("README-template.md") 41 | } 42 | 43 | #[must_use] 44 | pub fn test_readme_expected(test_name: &str) -> PathBuf { 45 | test_dir(test_name).join("README-expected.md") 46 | } 47 | 48 | fn is_stderr_terminal() -> bool { 49 | use std::io::IsTerminal; 50 | 51 | std::io::stderr().is_terminal() 52 | } 53 | 54 | fn print_framed(stream: &mut termcolor::Buffer, text: &str) { 55 | for line in text.lines() { 56 | writeln!(stream, "┃ {line}").unwrap(); 57 | } 58 | } 59 | 60 | fn print_stderr_framed(stream: &mut termcolor::Buffer, stderr: &str) { 61 | use termcolor::{ColorSpec, WriteColor}; 62 | 63 | stream.set_color(ColorSpec::new().set_bold(true)).unwrap(); 64 | writeln!(stream, "┏━━━ stderr ━━━━━━").unwrap(); 65 | stream.reset().unwrap(); 66 | print_framed(stream, stderr); 67 | writeln!(stream, "┗━━━━━━━━━━━━━━━━━").unwrap(); 68 | } 69 | 70 | fn print_failure_readme_mismatch( 71 | expected_readme: &str, 72 | got_readme: &str, 73 | readme_path: impl AsRef, 74 | expected_readme_path: impl AsRef, 75 | stderr: &str, 76 | ) { 77 | use termcolor::{Buffer, Color, ColorSpec, WriteColor}; 78 | 79 | let in_ci = std::env::var_os("CI").is_some(); 80 | let mut stream = match is_stderr_terminal() { 81 | true => Buffer::ansi(), 82 | false => Buffer::no_color(), 83 | }; 84 | 85 | stream.reset().unwrap(); 86 | 87 | stream.set_color(ColorSpec::new().set_bold(true).set_fg(Some(Color::Red))).unwrap(); 88 | write!(stream, "The README doesn’t match what was expected.").unwrap(); 89 | stream.reset().unwrap(); 90 | write!(stream, "\n\n").unwrap(); 91 | stream.set_color(ColorSpec::new().set_bold(true)).unwrap(); 92 | writeln!(stream, "┏━━━ Expected ━━━━").unwrap(); 93 | stream.reset().unwrap(); 94 | print_framed(&mut stream, expected_readme); 95 | stream.set_color(ColorSpec::new().set_bold(true)).unwrap(); 96 | writeln!(stream, "┠━━━ Got ━━━━━━━━━").unwrap(); 97 | stream.reset().unwrap(); 98 | print_framed(&mut stream, got_readme); 99 | writeln!(stream, "┗━━━━━━━━━━━━━━━━━").unwrap(); 100 | 101 | if !in_ci { 102 | write!(stream, "\nSee the diff with `").unwrap(); 103 | stream.set_color(ColorSpec::new().set_bold(true)).unwrap(); 104 | write!( 105 | stream, 106 | "diff {} {}", 107 | readme_path.as_ref().display(), 108 | expected_readme_path.as_ref().display() 109 | ) 110 | .unwrap(); 111 | stream.reset().unwrap(); 112 | writeln!(stream, "`.").unwrap(); 113 | } 114 | 115 | if !stderr.is_empty() { 116 | writeln!(stream).unwrap(); 117 | print_stderr_framed(&mut stream, stderr); 118 | } 119 | 120 | stream.flush().unwrap(); 121 | 122 | eprintln!("{}", std::str::from_utf8(stream.as_slice()).expect("invalid utf-8")); 123 | } 124 | 125 | fn print_failure_status_code_mismatch(expected_exit_code: i32, got_exit_code: i32, stderr: &str) { 126 | use termcolor::{Buffer, Color, ColorSpec, WriteColor}; 127 | 128 | let mut stream = match is_stderr_terminal() { 129 | true => Buffer::ansi(), 130 | false => Buffer::no_color(), 131 | }; 132 | 133 | stream.reset().unwrap(); 134 | 135 | stream.set_color(ColorSpec::new().set_bold(true).set_fg(Some(Color::Red))).unwrap(); 136 | write!(stream, "Expected code {expected_exit_code} but got code {got_exit_code} instead.") 137 | .unwrap(); 138 | stream.reset().unwrap(); 139 | writeln!(stream).unwrap(); 140 | 141 | if !stderr.is_empty() { 142 | writeln!(stream).unwrap(); 143 | print_stderr_framed(&mut stream, stderr); 144 | } 145 | 146 | stream.flush().unwrap(); 147 | 148 | eprintln!("{}", std::str::from_utf8(stream.as_slice()).expect("invalid utf-8")); 149 | } 150 | 151 | const BIN_PATH: &str = env!(concat!("CARGO_BIN_EXE_", env!("CARGO_PKG_NAME"))); 152 | 153 | pub fn run_test_with_options(test_name: &str, options: &TestOptions) { 154 | let bin_path = Path::new(BIN_PATH); 155 | let test_dir = test_dir(test_name); 156 | 157 | assert!(bin_path.is_file(), "Binary not found: {}", bin_path.display()); 158 | assert!(test_dir.is_dir(), "Test directory not found: {}", test_dir.display()); 159 | 160 | let expected_readme: PathBuf = test_readme_expected(test_name); 161 | let template_readme: PathBuf = test_readme_template(test_name); 162 | let readme = test_dir.join(options.readme_filename); 163 | 164 | if options.check_readme_expected { 165 | assert!( 166 | expected_readme.is_file(), 167 | "Expected readme not found: {}", 168 | expected_readme.display() 169 | ); 170 | assert!( 171 | template_readme.is_file(), 172 | "Template readme not found: {}", 173 | template_readme.display() 174 | ); 175 | 176 | std::fs::copy(&template_readme, &readme).unwrap(); 177 | } 178 | 179 | let args: Vec<&str> = { 180 | let mut args = Vec::from(options.args); 181 | 182 | if options.force { 183 | args.insert(0, "--force"); 184 | } 185 | 186 | args 187 | }; 188 | 189 | let output = Command::new(bin_path) 190 | .args(args) 191 | .current_dir(test_dir) 192 | .env("RUST_BACKTRACE", "1") 193 | .output() 194 | .unwrap_or_else(|_| panic!("Failed to execute {}", bin_path.display())); 195 | 196 | let stderr = String::from_utf8_lossy(&output.stderr); 197 | 198 | let exit_code = output.status.code().expect("no exist code"); 199 | 200 | if exit_code != options.expected_exit_code { 201 | print_failure_status_code_mismatch(options.expected_exit_code, exit_code, &stderr); 202 | panic!("Test {test_name} failed."); 203 | } 204 | 205 | if options.check_readme_expected { 206 | let expected = std::fs::read_to_string(&expected_readme).unwrap(); 207 | let got = std::fs::read_to_string(&readme).unwrap(); 208 | 209 | if expected != got { 210 | print_failure_readme_mismatch(&expected, &got, readme, expected_readme, &stderr); 211 | panic!("Test {test_name} failed."); 212 | } else { 213 | std::fs::remove_file(readme).unwrap(); 214 | } 215 | } 216 | } 217 | 218 | pub fn run_test(test_name: &str) { 219 | run_test_with_options(test_name, &TestOptions::default()); 220 | } 221 | -------------------------------------------------------------------------------- /tests/tests.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | */ 5 | 6 | mod testing; 7 | 8 | use crate::testing::{test_dir, test_readme_expected, test_readme_template}; 9 | use cargo_rdme::{LineTerminator, infer_line_terminator}; 10 | use std::io::{Read, Write}; 11 | use testing::{TestOptions, run_test, run_test_with_options}; 12 | 13 | #[test] 14 | fn integration_test_simple_single_marker() { 15 | run_test("simple_single_marker"); 16 | } 17 | 18 | #[test] 19 | fn integration_test_simple_single_marker_no_footer() { 20 | run_test("simple_single_marker_no_footer"); 21 | } 22 | 23 | #[test] 24 | fn integration_test_simple_start_end_marker() { 25 | run_test("simple_start_end_marker"); 26 | } 27 | 28 | #[test] 29 | fn integration_test_custom_lib_path() { 30 | run_test("custom_lib_path"); 31 | } 32 | 33 | #[test] 34 | fn integration_test_custom_readme_path() { 35 | let options = TestOptions { readme_filename: "READ-ME.md", ..TestOptions::default() }; 36 | 37 | run_test_with_options("custom_readme_path", &options); 38 | } 39 | 40 | #[test] 41 | fn integration_test_line_terminator_crlf() { 42 | let test_name = "line_terminator_crlf"; 43 | let readme_template = test_readme_template(test_name); 44 | let readme_expected = test_readme_expected(test_name); 45 | 46 | assert_eq!(infer_line_terminator(readme_template).unwrap(), LineTerminator::CrLf); 47 | assert_eq!(infer_line_terminator(readme_expected).unwrap(), LineTerminator::CrLf); 48 | 49 | run_test(test_name); 50 | } 51 | 52 | #[test] 53 | fn integration_test_multiline_doc() { 54 | run_test("multiline_doc"); 55 | } 56 | 57 | #[test] 58 | fn integration_test_option_cmd_override_readme_path() { 59 | let test_name = "option_cmd_override_readme_path"; 60 | 61 | let options = TestOptions { 62 | args: &["--readme-path", "r.md"], 63 | readme_filename: "r.md", 64 | ..TestOptions::default() 65 | }; 66 | 67 | run_test_with_options(test_name, &options); 68 | } 69 | 70 | #[test] 71 | fn integration_test_option_cmd_line_terminator_lf() { 72 | let test_name = "option_cmd_line_terminator_lf"; 73 | let readme_template = test_readme_template(test_name); 74 | let readme_expected = test_readme_expected(test_name); 75 | 76 | assert_eq!(infer_line_terminator(readme_template).unwrap(), LineTerminator::CrLf); 77 | assert_eq!(infer_line_terminator(readme_expected).unwrap(), LineTerminator::Lf); 78 | 79 | let options = TestOptions { args: &["--line-terminator", "lf"], ..TestOptions::default() }; 80 | 81 | run_test_with_options(test_name, &options); 82 | } 83 | 84 | #[test] 85 | fn integration_test_option_cmd_line_terminator_crlf() { 86 | let test_name = "option_cmd_line_terminator_crlf"; 87 | let readme_template = test_readme_template(test_name); 88 | let readme_expected = test_readme_expected(test_name); 89 | 90 | assert_eq!(infer_line_terminator(readme_template).unwrap(), LineTerminator::Lf); 91 | assert_eq!(infer_line_terminator(readme_expected).unwrap(), LineTerminator::CrLf); 92 | 93 | let options = TestOptions { args: &["--line-terminator", "crlf"], ..TestOptions::default() }; 94 | 95 | run_test_with_options(test_name, &options); 96 | } 97 | 98 | #[test] 99 | fn integration_test_option_conf_file_override_readme_path() { 100 | let test_name = "option_conf_file_override_readme_path"; 101 | 102 | let options = TestOptions { readme_filename: "r.md", ..TestOptions::default() }; 103 | 104 | run_test_with_options(test_name, &options); 105 | } 106 | 107 | #[test] 108 | fn integration_test_option_conf_file_line_terminator_lf() { 109 | let test_name = "option_conf_file_line_terminator_lf"; 110 | let readme_template = test_readme_template(test_name); 111 | let readme_expected = test_readme_expected(test_name); 112 | 113 | assert_eq!(infer_line_terminator(readme_template).unwrap(), LineTerminator::CrLf); 114 | assert_eq!(infer_line_terminator(readme_expected).unwrap(), LineTerminator::Lf); 115 | 116 | run_test(test_name); 117 | } 118 | 119 | #[test] 120 | fn integration_test_option_conf_file_line_terminator_crlf() { 121 | let test_name = "option_conf_file_line_terminator_crlf"; 122 | let readme_template = test_readme_template(test_name); 123 | let readme_expected = test_readme_expected(test_name); 124 | 125 | assert_eq!(infer_line_terminator(readme_template).unwrap(), LineTerminator::Lf); 126 | assert_eq!(infer_line_terminator(readme_expected).unwrap(), LineTerminator::CrLf); 127 | 128 | run_test(test_name); 129 | } 130 | 131 | #[test] 132 | fn integration_test_option_cmd_check_ok() { 133 | let test_name = "option_cmd_check_ok"; 134 | let options = TestOptions { 135 | args: &["--check"], 136 | check_readme_expected: false, 137 | expected_exit_code: 0, 138 | ..TestOptions::default() 139 | }; 140 | 141 | run_test_with_options(test_name, &options); 142 | } 143 | 144 | #[test] 145 | fn integration_test_option_cmd_check_fail() { 146 | let test_name = "option_cmd_check_fail"; 147 | let options = TestOptions { 148 | args: &["--check"], 149 | check_readme_expected: false, 150 | expected_exit_code: 3, 151 | ..TestOptions::default() 152 | }; 153 | 154 | run_test_with_options(test_name, &options); 155 | } 156 | 157 | #[test] 158 | fn integration_test_option_cmd_check_fail_because_warnings() { 159 | let test_name = "option_cmd_check_fail_because_warnings"; 160 | let options = TestOptions { 161 | args: &["--check"], 162 | check_readme_expected: false, 163 | expected_exit_code: 4, 164 | ..TestOptions::default() 165 | }; 166 | 167 | run_test_with_options(test_name, &options); 168 | } 169 | 170 | #[test] 171 | fn integration_test_option_cmd_check_no_fail_on_warnings() { 172 | let test_name = "option_cmd_check_no_fail_on_warnings"; 173 | let options = TestOptions { 174 | args: &["--check", "--no-fail-on-warnings"], 175 | check_readme_expected: false, 176 | expected_exit_code: 0, 177 | ..TestOptions::default() 178 | }; 179 | 180 | run_test_with_options(test_name, &options); 181 | } 182 | 183 | #[test] 184 | fn integration_test_option_cmd_check_fail_line_terminator() { 185 | let test_name = "option_cmd_check_fail_line_terminator"; 186 | 187 | // First check that the test would pass without the line terminator override. 188 | let options = TestOptions { 189 | args: &["--check"], 190 | check_readme_expected: false, 191 | expected_exit_code: 0, 192 | ..TestOptions::default() 193 | }; 194 | 195 | run_test_with_options(test_name, &options); 196 | 197 | let options = TestOptions { 198 | args: &["--check", "--line-terminator", "crlf"], 199 | check_readme_expected: false, 200 | expected_exit_code: 3, 201 | ..TestOptions::default() 202 | }; 203 | 204 | run_test_with_options(test_name, &options); 205 | } 206 | 207 | #[test] 208 | fn integration_test_entrypoint_bin_crate() { 209 | run_test("entrypoint_bin_crate"); 210 | } 211 | 212 | #[test] 213 | fn integration_test_entrypoint_bin_lib_crate_lib_wins() { 214 | run_test("entrypoint_bin_lib_crate_lib_wins"); 215 | } 216 | 217 | #[test] 218 | fn integration_test_option_conf_file_entrypoint_bin() { 219 | run_test("option_conf_file_entrypoint_bin"); 220 | } 221 | 222 | #[test] 223 | fn integration_test_option_conf_file_entrypoint_select_bin() { 224 | run_test("option_conf_file_entrypoint_select_bin"); 225 | } 226 | 227 | #[test] 228 | fn integration_test_option_conf_file_entrypoint_select_bin_custom_path() { 229 | run_test("option_conf_file_entrypoint_select_bin_custom_path"); 230 | } 231 | 232 | #[test] 233 | fn integration_test_option_cmd_entrypoint_bin() { 234 | let test_name = "option_cmd_entrypoint_bin"; 235 | 236 | let options = TestOptions { args: &["--entrypoint", "bin"], ..TestOptions::default() }; 237 | 238 | run_test_with_options(test_name, &options); 239 | } 240 | 241 | #[test] 242 | fn integration_test_option_cmd_entrypoint_select_bin_single() { 243 | let test_name = "option_cmd_entrypoint_select_bin_single"; 244 | let options = TestOptions { args: &["--entrypoint", "bin"], ..TestOptions::default() }; 245 | 246 | run_test_with_options(test_name, &options); 247 | } 248 | 249 | #[test] 250 | fn integration_test_option_cmd_entrypoint_select_bin() { 251 | let test_name = "option_cmd_entrypoint_select_bin"; 252 | 253 | let options = TestOptions { args: &["--entrypoint", "bin:foo"], ..TestOptions::default() }; 254 | 255 | run_test_with_options(test_name, &options); 256 | } 257 | 258 | #[test] 259 | fn integration_test_option_cmd_entrypoint_select_bin_custom_path() { 260 | let test_name = "option_cmd_entrypoint_select_bin_custom_path"; 261 | 262 | let options = TestOptions { args: &["--entrypoint", "bin:foo"], ..TestOptions::default() }; 263 | 264 | run_test_with_options(test_name, &options); 265 | } 266 | 267 | #[test] 268 | fn integration_test_separate_bin_and_lib() { 269 | let test_name = "separate_bin_and_lib"; 270 | 271 | let options = TestOptions { args: &["--entrypoint", "bin"], ..TestOptions::default() }; 272 | 273 | run_test_with_options(test_name, &options); 274 | } 275 | 276 | #[test] 277 | fn integration_test_marker_inside_doc() { 278 | run_test("marker_inside_doc"); 279 | } 280 | 281 | #[test] 282 | fn integration_test_avoid_overwrite_uncommitted_readme() { 283 | use std::fs::File; 284 | 285 | let test_name = "avoid_overwrite_uncommitted_readme"; 286 | let readme_path = test_dir(test_name).join("README.md"); 287 | 288 | let options = TestOptions { 289 | check_readme_expected: false, 290 | expected_exit_code: 2, 291 | force: false, 292 | ..TestOptions::default() 293 | }; 294 | 295 | let mut file = File::create(&readme_path).unwrap(); 296 | file.write_all("A file!".as_bytes()).unwrap(); 297 | drop(file); 298 | 299 | run_test_with_options(test_name, &options); 300 | 301 | let mut file = File::open(&readme_path).unwrap(); 302 | let mut content = String::new(); 303 | file.read_to_string(&mut content).unwrap(); 304 | 305 | assert_eq!(content, "A file!"); 306 | } 307 | 308 | #[test] 309 | fn integration_test_transform_rust_code_block_remove_comments() { 310 | run_test("transform_rust_code_block_remove_comments"); 311 | } 312 | 313 | #[test] 314 | fn integration_test_transform_rust_code_block_add_markdown_tag() { 315 | run_test("transform_rust_code_block_add_markdown_tag"); 316 | } 317 | 318 | #[test] 319 | fn integration_test_transform_rust_code_nested_fenced_blocks() { 320 | run_test("transform_rust_code_nested_fenced_blocks"); 321 | } 322 | 323 | #[test] 324 | fn integration_test_transform_intralinks_simple() { 325 | run_test("transform_intralinks_simple"); 326 | } 327 | 328 | #[test] 329 | fn integration_test_transform_intralinks_method() { 330 | run_test("transform_intralinks_impl_items"); 331 | } 332 | 333 | #[test] 334 | fn integration_test_transform_intralinks_reference_links() { 335 | run_test("transform_intralinks_reference_links"); 336 | } 337 | 338 | #[test] 339 | fn integration_test_transform_intralinks_module_walk() { 340 | run_test("transform_intralinks_module_walk"); 341 | } 342 | 343 | #[test] 344 | fn integration_test_transform_intralinks_ambiguous_module() { 345 | run_test("transform_intralinks_ambiguous_module"); 346 | } 347 | 348 | #[test] 349 | fn integration_test_transform_intralinks_stdlib_links() { 350 | run_test("transform_intralinks_stdlib_links"); 351 | } 352 | 353 | #[test] 354 | fn integration_test_transform_intralinks_crate_name_hyphen() { 355 | run_test("transform_intralinks_crate_name_hyphen"); 356 | } 357 | 358 | #[test] 359 | fn integration_test_transform_intralinks_backticked() { 360 | run_test("transform_intralinks_backticked"); 361 | } 362 | 363 | #[test] 364 | fn integration_test_option_conf_file_workspace() { 365 | run_test("option_conf_file_workspace"); 366 | } 367 | 368 | #[test] 369 | fn integration_test_option_cmd_workspace() { 370 | let test_name = "option_cmd_workspace"; 371 | 372 | let options = TestOptions { 373 | args: &["--workspace-project", "otherproj", "--readme-path", "README.md"], 374 | ..TestOptions::default() 375 | }; 376 | 377 | run_test_with_options(test_name, &options); 378 | } 379 | 380 | #[test] 381 | fn integration_test_option_cmd_workspace_dependency_collision() { 382 | let test_name = "option_cmd_workspace_dependency_collision"; 383 | 384 | let options = TestOptions { 385 | args: &["--workspace-project", "cargo-rdme", "--readme-path", "README.md"], 386 | ..TestOptions::default() 387 | }; 388 | 389 | run_test_with_options(test_name, &options); 390 | } 391 | 392 | #[test] 393 | fn integration_test_option_conf_file_intralinks_docs_rs_base_url() { 394 | run_test("option_conf_file_intralinks_docs_rs_base_url"); 395 | } 396 | 397 | #[test] 398 | fn integration_test_option_conf_file_intralinks_docs_rs_version() { 399 | run_test("option_conf_file_intralinks_docs_rs_version"); 400 | } 401 | 402 | #[test] 403 | fn integration_test_option_conf_file_intralinks_strip_links() { 404 | run_test("option_conf_file_intralinks_strip_links"); 405 | } 406 | 407 | #[test] 408 | fn integration_test_option_cmd_intralinks_strip_links() { 409 | let test_name = "option_cmd_intralinks_strip_links"; 410 | 411 | let options = TestOptions { args: &["--intralinks-strip-links"], ..TestOptions::default() }; 412 | 413 | run_test_with_options(test_name, &options); 414 | } 415 | 416 | #[test] 417 | fn integration_test_heading_level_auto_bump() { 418 | run_test("heading_level_auto_bump"); 419 | } 420 | 421 | #[test] 422 | fn integration_test_option_conf_file_heading_base_level() { 423 | run_test("option_conf_file_heading_base_level"); 424 | } 425 | 426 | #[test] 427 | fn integration_test_option_cmd_heading_base_level() { 428 | let test_name = "option_cmd_heading_base_level"; 429 | 430 | let options = TestOptions { args: &["--heading-base-level", "3"], ..TestOptions::default() }; 431 | 432 | run_test_with_options(test_name, &options); 433 | } 434 | 435 | #[test] 436 | fn integration_test_crate_procmacro() { 437 | run_test("crate_procmacro"); 438 | } 439 | -------------------------------------------------------------------------------- /tests/transform_intralinks_ambiguous_module/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/transform_intralinks_ambiguous_module/README-expected.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [a MyStruct](https://docs.rs/integration_test/latest/integration_test/a/struct.MyStruct.html) 4 | skip 5 | [b MyStruct](https://docs.rs/integration_test/latest/integration_test/b/struct.MyStruct.html) 6 | skip 7 | [c MyStruct](https://docs.rs/integration_test/latest/integration_test/c/struct.MyStruct.html) 8 | skip 9 | [d Same is a trait](https://docs.rs/integration_test/latest/integration_test/d/trait.Same.html) 10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/transform_intralinks_ambiguous_module/README-template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /tests/transform_intralinks_ambiguous_module/src/main.rs: -------------------------------------------------------------------------------- 1 | //! [a MyStruct](crate::a::MyStruct) 2 | //! [skip](crate::a::Skip) 3 | //! [b MyStruct](crate::b::MyStruct) 4 | //! [skip](crate::b::MyStructTest) 5 | //! [c MyStruct](crate::c::MyStruct) 6 | //! [skip](crate::c::MyStructTest) 7 | //! [d Same is a trait](crate::d::Same) 8 | 9 | #[cfg(not(foo))] 10 | mod a { 11 | struct MyStruct {} 12 | } 13 | 14 | #[cfg(foo)] 15 | mod a { 16 | struct Skip {} 17 | } 18 | 19 | #[cfg(not(test))] 20 | mod b { 21 | struct MyStruct {} 22 | } 23 | 24 | #[cfg(test)] 25 | mod b { 26 | struct MyStructTest {} 27 | } 28 | 29 | #[cfg(test)] 30 | mod c { 31 | struct MyStructTest {} 32 | } 33 | 34 | #[cfg(not(test))] 35 | mod c { 36 | struct MyStruct {} 37 | } 38 | 39 | #[cfg(test)] 40 | mod d { 41 | struct Same {} 42 | } 43 | 44 | #[cfg(not(test))] 45 | mod d { 46 | trait Same {} 47 | } 48 | 49 | fn main() {} 50 | -------------------------------------------------------------------------------- /tests/transform_intralinks_backticked/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/transform_intralinks_backticked/README-expected.md: -------------------------------------------------------------------------------- 1 | This will not be [modified](crate::amodule). 2 | 3 | 4 | 5 | This [beautiful crate](https://docs.rs/integration_test/latest/integration_test/) is cool because it contains [modules](https://docs.rs/integration_test/latest/integration_test/amodule/) and some 6 | other [stuff](https://en.wikipedia.org/wiki/Stuff) as well. 7 | 8 | This link is broken, but this should [wor\\k \[ju\]st](f\\i\(n\)e). 9 | 10 | Go ahead and check all the [structs in foo](https://docs.rs/integration_test/latest/integration_test/foo/#structs) and 11 | [structs in foo](https://docs.rs/integration_test/latest/integration_test/foo/#structs) specifically [this one](https://docs.rs/integration_test/latest/integration_test/foo/struct.BestStruct.html) 12 | 13 | [![BestStruct doc](https://example.com/image.png)](https://docs.rs/integration_test/latest/integration_test/foo/struct.BestStruct.html) 14 | 15 | 16 | -------------------------------------------------------------------------------- /tests/transform_intralinks_backticked/README-template.md: -------------------------------------------------------------------------------- 1 | This will not be [modified](crate::amodule). 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /tests/transform_intralinks_backticked/src/main.rs: -------------------------------------------------------------------------------- 1 | //! This [beautiful crate](`crate`) is cool because it contains [modules](`crate::amodule`) and some 2 | //! other [stuff](https://en.wikipedia.org/wiki/Stuff) as well. 3 | //! 4 | //! This link is [broken](`crate::broken`), but this should [wor\\k \[ju\]st](f\\i\(n\)e). 5 | //! 6 | //! Go ahead and check all the [structs in foo](`crate::foo#structs`) and 7 | //! [structs in foo](`crate::foo`#structs) specifically [this one](`crate::foo::BestStruct`) 8 | //! 9 | //! [![BestStruct doc](https://example.com/image.png)](`crate::foo::BestStruct`) 10 | 11 | mod amodule {} 12 | 13 | mod foo { 14 | struct BestStruct {} 15 | } 16 | 17 | fn main() {} 18 | -------------------------------------------------------------------------------- /tests/transform_intralinks_crate_name_hyphen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration-test-hyphen" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/transform_intralinks_crate_name_hyphen/README-expected.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | This [crate](https://docs.rs/integration-test-hyphen/latest/integration_test_hyphen/) contains an empty [main](https://docs.rs/integration-test-hyphen/latest/integration_test_hyphen/fn.main.html) function. 4 | 5 | 6 | -------------------------------------------------------------------------------- /tests/transform_intralinks_crate_name_hyphen/README-template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /tests/transform_intralinks_crate_name_hyphen/src/main.rs: -------------------------------------------------------------------------------- 1 | //! This [crate](crate) contains an empty [main](crate::main) function. 2 | 3 | fn main() {} 4 | -------------------------------------------------------------------------------- /tests/transform_intralinks_impl_items/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/transform_intralinks_impl_items/README-expected.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | This crate has [`Foo::new()`](https://docs.rs/integration_test/latest/integration_test/struct.Foo.html#method.new), [`Foo::a_method()`](https://docs.rs/integration_test/latest/integration_test/struct.Foo.html#method.a_method), 4 | and [`Foo::another_method()`](https://docs.rs/integration_test/latest/integration_test/struct.Foo.html#method.another_method). 5 | 6 | It also has [`Foo::no_self()`](https://docs.rs/integration_test/latest/integration_test/struct.Foo.html#method.no_self). There's also [`Bar::beer()`](https://docs.rs/integration_test/latest/integration_test/amod/struct.Bar.html#method.beer). 7 | 8 | Struct `Foo` has a [type called `baz`](https://docs.rs/integration_test/latest/integration_test/struct.Foo.html#associatedtype.Baz) and a 9 | [const called `number`](https://docs.rs/integration_test/latest/integration_test/struct.Foo.html#associatedconstant.number). 10 | 11 | We have a function in `FooAlias` [called `hello`](https://docs.rs/integration_test/latest/integration_test/type.FooAlias.html#method.hello). 12 | 13 | And in `MyEnum` we have [called `hey`](https://docs.rs/integration_test/latest/integration_test/enum.MyEnum.html#method.hey). 14 | 15 | And in `MyUnion` we have [called `sup`](https://docs.rs/integration_test/latest/integration_test/union.MyUnion.html#method.sup). 16 | 17 | 18 | -------------------------------------------------------------------------------- /tests/transform_intralinks_impl_items/README-template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /tests/transform_intralinks_impl_items/src/main.rs: -------------------------------------------------------------------------------- 1 | //! This crate has [`Foo::new()`](`crate::Foo::new`), [`Foo::a_method()`](`crate::Foo::a_method`), 2 | //! and [`Foo::another_method()`](`crate::Foo::another_method`). 3 | //! 4 | //! It also has [`Foo::no_self()`](`crate::Foo::no_self`). There's also [`Bar::beer()`](`crate::amod::Bar::beer`). 5 | //! 6 | //! Struct `Foo` has a [type called `baz`](`crate::Foo::Baz`) and a 7 | //! [const called `number`](`crate::Foo::number`). 8 | //! 9 | //! We have a function in `FooAlias` [called `hello`](`crate::FooAlias::hello`). 10 | //! 11 | //! And in `MyEnum` we have [called `hey`](`crate::MyEnum::hey`). 12 | //! 13 | //! And in `MyUnion` we have [called `sup`](`crate::MyUnion::sup`). 14 | 15 | // TODO Remove this when inherent_associated_types stabilizes. 16 | #![feature(inherent_associated_types)] 17 | 18 | pub struct Foo {} 19 | 20 | impl Foo { 21 | const number: usize = 1234; 22 | 23 | type Baz = u32; 24 | 25 | pub fn new() -> Foo { 26 | Foo {} 27 | } 28 | 29 | pub fn a_method(&self) -> u32 { 30 | 42 31 | } 32 | 33 | pub fn no_self(s: &str) -> usize { 34 | s.len() 35 | } 36 | } 37 | 38 | impl Foo { 39 | pub fn another_method(&self, x: u32) -> u32 { 40 | self.a_method() + x 41 | } 42 | } 43 | 44 | mod amod { 45 | pub struct Bar {} 46 | 47 | impl Bar { 48 | pub fn beer(&self) {} 49 | } 50 | } 51 | 52 | type FooAlias = Foo; 53 | 54 | impl FooAlias { 55 | fn hello(&self) {} 56 | } 57 | 58 | enum MyEnum {} 59 | 60 | impl MyEnum { 61 | fn hey(&self) {} 62 | } 63 | 64 | union MyUnion {} 65 | 66 | impl MyUnion { 67 | fn sup(&self) {} 68 | } 69 | 70 | fn main() {} 71 | -------------------------------------------------------------------------------- /tests/transform_intralinks_module_walk/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/transform_intralinks_module_walk/README-expected.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [amodule](https://docs.rs/integration_test/latest/integration_test/amodule/) 4 | [amodule T](https://docs.rs/integration_test/latest/integration_test/amodule/trait.T.html) 5 | [foo](https://docs.rs/integration_test/latest/integration_test/foo/) 6 | [foo S0](https://docs.rs/integration_test/latest/integration_test/foo/struct.S0.html) 7 | [foo bar](https://docs.rs/integration_test/latest/integration_test/foo/bar/) 8 | [foo bar S1](https://docs.rs/integration_test/latest/integration_test/foo/bar/struct.S1.html) 9 | 10 | 11 | -------------------------------------------------------------------------------- /tests/transform_intralinks_module_walk/README-template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /tests/transform_intralinks_module_walk/src/amodule.rs: -------------------------------------------------------------------------------- 1 | trait T {} 2 | -------------------------------------------------------------------------------- /tests/transform_intralinks_module_walk/src/foo/bar.rs: -------------------------------------------------------------------------------- 1 | struct S1 {} 2 | -------------------------------------------------------------------------------- /tests/transform_intralinks_module_walk/src/foo/mod.rs: -------------------------------------------------------------------------------- 1 | struct S0 {} 2 | 3 | mod bar; 4 | -------------------------------------------------------------------------------- /tests/transform_intralinks_module_walk/src/main.rs: -------------------------------------------------------------------------------- 1 | //! [amodule](crate::amodule) 2 | //! [amodule T](crate::amodule::T) 3 | //! [foo](crate::foo) 4 | //! [foo S0](crate::foo::S0) 5 | //! [foo bar](crate::foo::bar) 6 | //! [foo bar S1](crate::foo::bar::S1) 7 | 8 | mod amodule; 9 | mod foo; 10 | 11 | fn main() {} 12 | -------------------------------------------------------------------------------- /tests/transform_intralinks_reference_links/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/transform_intralinks_reference_links/README-expected.md: -------------------------------------------------------------------------------- 1 | This will not be [modified]. 2 | 3 | 4 | 5 | This [beautiful crate] is cool because it contains [modules] 6 | and some other [stuff] as well. 7 | 8 | This link is broken and this is not supported, 9 | but this should [wor\\k \[fi\]ne]. 10 | 11 | Go ahead and check all the [structs in foo] specifically 12 | [this one]. Also, this is a nice function: [copy][cp]. 13 | 14 | [![BestStruct doc]][BestStruct] 15 | 16 | [beautiful crate]: https://docs.rs/integration_test/latest/integration_test/ 17 | [modules]: https://docs.rs/integration_test/latest/integration_test/amodule/ 18 | [stuff]: https://en.wikipedia.org/wiki/Stuff 19 | [wor\\k \[fi\]ne]: f\\i\(n\)e 20 | [structs in foo]: https://docs.rs/integration_test/latest/integration_test/foo/#structs 21 | [this one]: https://docs.rs/integration_test/latest/integration_test/foo/struct.BestStruct.html 22 | [cp]: https://doc.rust-lang.org/stable/std/fs/fn.copy.html#examples "A title here" 23 | [BestStruct doc]: https://example.com/image.png 24 | [BestStruct]: https://docs.rs/integration_test/latest/integration_test/foo/struct.BestStruct.html 25 | 26 | 27 | 28 | [modified]: crate::amodule 29 | -------------------------------------------------------------------------------- /tests/transform_intralinks_reference_links/README-template.md: -------------------------------------------------------------------------------- 1 | This will not be [modified]. 2 | 3 | 4 | 5 | 6 | [modified]: crate::amodule 7 | -------------------------------------------------------------------------------- /tests/transform_intralinks_reference_links/src/main.rs: -------------------------------------------------------------------------------- 1 | //! This [beautiful crate] is cool because it contains [modules] 2 | //! and some other [stuff] as well. 3 | //! 4 | //! This link is [broken] and this is [not supported], 5 | //! but this should [wor\\k \[fi\]ne]. 6 | //! 7 | //! Go ahead and check all the [structs in foo] specifically 8 | //! [this one]. Also, this is a nice function: [copy][cp]. 9 | //! 10 | //! [![BestStruct doc]][BestStruct] 11 | //! 12 | //! [beautiful crate]: crate 13 | //! [modules]: crate::amodule 14 | //! [stuff]: https://en.wikipedia.org/wiki/Stuff 15 | //! [broken]: crate::broken 16 | //! [not supported]: ::foo::bar 17 | //! [wor\\k \[fi\]ne]: f\\i\(n\)e 18 | //! [structs in foo]: crate::foo#structs 19 | //! [this one]: crate::foo::BestStruct 20 | //! [cp]: ::std::fs::copy#examples "A title here" 21 | //! [BestStruct doc]: https://example.com/image.png 22 | //! [BestStruct]: crate::foo::BestStruct 23 | 24 | mod amodule {} 25 | 26 | mod foo { 27 | struct BestStruct {} 28 | } 29 | 30 | fn main() {} 31 | -------------------------------------------------------------------------------- /tests/transform_intralinks_simple/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/transform_intralinks_simple/README-expected.md: -------------------------------------------------------------------------------- 1 | This will not be [modified](crate::amodule). 2 | 3 | 4 | 5 | This [beautiful crate](https://docs.rs/integration_test/latest/integration_test/) is cool because it contains [modules](https://docs.rs/integration_test/latest/integration_test/amodule/) and some 6 | other [stuff](https://en.wikipedia.org/wiki/Stuff) as well. 7 | 8 | This link is broken, but this should [wor\\k \[ju\]st](f\\i\(n\)e). 9 | 10 | Go ahead and check all the [structs in foo](https://docs.rs/integration_test/latest/integration_test/foo/#structs) specifically 11 | [this one](https://docs.rs/integration_test/latest/integration_test/foo/struct.BestStruct.html) 12 | 13 | [![BestStruct doc](https://example.com/image.png)](https://docs.rs/integration_test/latest/integration_test/foo/struct.BestStruct.html) 14 | 15 | 16 | -------------------------------------------------------------------------------- /tests/transform_intralinks_simple/README-template.md: -------------------------------------------------------------------------------- 1 | This will not be [modified](crate::amodule). 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /tests/transform_intralinks_simple/src/main.rs: -------------------------------------------------------------------------------- 1 | //! This [beautiful crate](crate) is cool because it contains [modules](crate::amodule) and some 2 | //! other [stuff](https://en.wikipedia.org/wiki/Stuff) as well. 3 | //! 4 | //! This link is [broken](crate::broken), but this should [wor\\k \[ju\]st](f\\i\(n\)e). 5 | //! 6 | //! Go ahead and check all the [structs in foo](crate::foo#structs) specifically 7 | //! [this one](crate::foo::BestStruct) 8 | //! 9 | //! [![BestStruct doc](https://example.com/image.png)](crate::foo::BestStruct) 10 | 11 | mod amodule {} 12 | 13 | mod foo { 14 | struct BestStruct {} 15 | } 16 | 17 | fn main() {} 18 | -------------------------------------------------------------------------------- /tests/transform_intralinks_stdlib_links/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/transform_intralinks_stdlib_links/README-expected.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [Vec](https://doc.rust-lang.org/stable/alloc/vec/struct.Vec.html) 4 | [collections](https://doc.rust-lang.org/stable/std/collections/) 5 | [copy](https://doc.rust-lang.org/stable/std/fs/fn.copy.html) 6 | broken 7 | [std](https://doc.rust-lang.org/stable/std/) 8 | [alloc](https://doc.rust-lang.org/stable/alloc/) 9 | 10 | 11 | -------------------------------------------------------------------------------- /tests/transform_intralinks_stdlib_links/README-template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /tests/transform_intralinks_stdlib_links/src/main.rs: -------------------------------------------------------------------------------- 1 | //! [Vec](::alloc::vec::Vec) 2 | //! [collections](::std::collections) 3 | //! [copy](::std::fs::copy) 4 | //! [broken](::foo::bar) 5 | //! [std](::std) 6 | //! [alloc](::alloc) 7 | 8 | fn main() {} 9 | -------------------------------------------------------------------------------- /tests/transform_rust_code_block_add_markdown_tag/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/transform_rust_code_block_add_markdown_tag/README-expected.md: -------------------------------------------------------------------------------- 1 | header 2 | 3 | 4 | 5 | # My crate 6 | 7 | This is a crate that does nothing. 8 | 9 | First: 10 | 11 | ```rust 12 | println!("One"); 13 | ``` 14 | 15 | Second: 16 | 17 | ```rust 18 | println!("One"); 19 | ``` 20 | 21 | Third: 22 | 23 | ```text 24 | println!("One"); 25 | ``` 26 | 27 | Fourth: 28 | 29 | ```rust 30 | println!("One"); 31 | ``` 32 | 33 | End 34 | 35 | 36 | 37 | footer 38 | -------------------------------------------------------------------------------- /tests/transform_rust_code_block_add_markdown_tag/README-template.md: -------------------------------------------------------------------------------- 1 | header 2 | 3 | 4 | 5 | footer 6 | -------------------------------------------------------------------------------- /tests/transform_rust_code_block_add_markdown_tag/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a crate that does nothing. 4 | //! 5 | //! First: 6 | //! 7 | //! ```rust 8 | //! println!("One"); 9 | //! ``` 10 | //! 11 | //! Second: 12 | //! 13 | //! ```should_panic 14 | //! println!("One"); 15 | //! ``` 16 | //! 17 | //! Third: 18 | //! 19 | //! ```text 20 | //! println!("One"); 21 | //! ``` 22 | //! 23 | //! Fourth: 24 | //! 25 | //! println!("One"); 26 | //! 27 | //! End 28 | 29 | fn foo() {} 30 | -------------------------------------------------------------------------------- /tests/transform_rust_code_block_remove_comments/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/transform_rust_code_block_remove_comments/README-expected.md: -------------------------------------------------------------------------------- 1 | header 2 | 3 | 4 | 5 | # My crate 6 | 7 | This is a crate that does nothing. 8 | 9 | ```rust 10 | println!("One"); 11 | ``` 12 | 13 | ```rust 14 | println!("Two"); 15 | ``` 16 | 17 | ```rust 18 | println!("Three"); 19 | ``` 20 | 21 | ```text 22 | # comment 23 | println!("One"); 24 | ``` 25 | 26 | 27 | 28 | footer 29 | -------------------------------------------------------------------------------- /tests/transform_rust_code_block_remove_comments/README-template.md: -------------------------------------------------------------------------------- 1 | header 2 | 3 | 4 | 5 | footer 6 | -------------------------------------------------------------------------------- /tests/transform_rust_code_block_remove_comments/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! This is a crate that does nothing. 4 | //! 5 | //! ``` 6 | //! # comment 7 | //! println!("One"); 8 | //! ``` 9 | //! 10 | //! ```rust 11 | //! # comment 12 | //! println!("Two"); 13 | //! ``` 14 | //! 15 | //! ```should_panic 16 | //! # comment 17 | //! println!("Three"); 18 | //! ``` 19 | //! 20 | //! ```text 21 | //! # comment 22 | //! println!("One"); 23 | //! ``` 24 | 25 | fn foo() {} 26 | -------------------------------------------------------------------------------- /tests/transform_rust_code_nested_fenced_blocks/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /tests/transform_rust_code_nested_fenced_blocks/README-expected.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # My crate 4 | 5 | ````rust 6 | let s= " 7 | ``` 8 | "; 9 | println!("Hi"); 10 | let s= " 11 | ``` 12 | "; 13 | ```` 14 | 15 | 16 | -------------------------------------------------------------------------------- /tests/transform_rust_code_nested_fenced_blocks/README-template.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/transform_rust_code_nested_fenced_blocks/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # My crate 2 | //! 3 | //! ```` 4 | //! # Comment 1 5 | //! let s= " 6 | //! ``` 7 | //! "; 8 | //! # Comment 2 9 | //! println!("Hi"); 10 | //! let s= " 11 | //! ``` 12 | //! "; 13 | //! ```` 14 | 15 | fn foo() {} 16 | -------------------------------------------------------------------------------- /tools/check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | set -e 8 | 9 | cd $(dirname "$0") 10 | cd "$(git rev-parse --show-toplevel)" 11 | 12 | source "tools/utils.sh" 13 | 14 | RED='\033[0;31m' 15 | GREEN='\033[0;32m' 16 | NC='\033[0m' 17 | 18 | function on_failure { 19 | echo >&2 20 | echo -e "${RED}Whoopsie-daisy: something failed!$NC" >&2 21 | } 22 | 23 | assert_installed "cargo" 24 | 25 | trap on_failure ERR 26 | 27 | function check_basic { 28 | echo 'Building:' 29 | cargo build --features fatal-warnings --all-targets 30 | echo 'Testing:' 31 | cargo test --features fatal-warnings --all-targets 32 | # Weirdly, the `cargo test ... --all-targets ...` above does not run the tests in the documentation, so we run the 33 | # doc tests like this. 34 | # See https://github.com/rust-lang/cargo/issues/6669. 35 | echo 'Testing doc:' 36 | cargo test --features fatal-warnings --doc 37 | echo 'Checking the benchmarks:' 38 | cargo bench --features fatal-warnings -- --test 39 | echo 'Checking documentation:' 40 | cargo doc --features fatal-warnings --no-deps 41 | } 42 | 43 | function check_doc_url_links { 44 | assert_installed "cargo-deadlinks" 45 | 46 | echo 'Checking doc url links:' 47 | cargo deadlinks 48 | } 49 | 50 | function check_unused_deps { 51 | assert_installed "cargo-machete" 52 | 53 | echo 'Checking unused dependencies:' 54 | cargo machete 55 | } 56 | 57 | function check_packaging { 58 | echo 'Checking packaging:' 59 | cargo package --allow-dirty 60 | } 61 | 62 | function check_fmt { 63 | assert_installed "cargo-fmt" 64 | 65 | echo 'Checking code format:' 66 | cargo fmt -- --check 67 | } 68 | 69 | function check_toml_fmt { 70 | assert_installed "taplo" 71 | 72 | echo 'Checking toml format:' 73 | taplo fmt --check 74 | } 75 | 76 | function check_readme { 77 | echo 'Checking readme:' 78 | cargo run -- --check 79 | } 80 | 81 | function check_msrv { 82 | assert_installed "cargo-msrv" 83 | 84 | echo 'Checking the minimum supported rust version:' 85 | cargo msrv verify 86 | } 87 | 88 | function check_clippy { 89 | assert_installed "cargo-clippy" 90 | 91 | echo 'Checking with clippy:' 92 | cargo clippy --all-targets -- -D warnings 93 | } 94 | 95 | to_run=(basic doc_url_links unused_deps packaging fmt toml_fmt readme msrv clippy) 96 | 97 | if [ $# -ge 1 ]; then 98 | to_run=("$@") 99 | fi 100 | 101 | for check in "${to_run[@]}"; do 102 | check_$check 103 | done 104 | 105 | echo 106 | echo -e "${GREEN}Everything looks lovely!$NC" 107 | 108 | exit 0 109 | -------------------------------------------------------------------------------- /tools/codecov.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | set -e 8 | 9 | cd $(dirname "$0") 10 | cd "$(git rev-parse --show-toplevel)" 11 | 12 | source "tools/utils.sh" 13 | 14 | assert_installed "cargo-tarpaulin" 15 | 16 | output_format=html 17 | 18 | args=$(getopt -o '' -l xml -- "$@") 19 | 20 | eval set -- "$args" 21 | 22 | while [ $# -ge 1 ]; do 23 | case "$1" in 24 | --) 25 | # No more options left. 26 | shift 27 | break 28 | ;; 29 | --xml) 30 | output_format=xml 31 | ;; 32 | esac 33 | 34 | shift 35 | done 36 | 37 | # TODO it seems the `--force-clean` is not working. 38 | cargo clean 39 | cargo tarpaulin --force-clean --ignore-panics --engine llvm --timeout 1200 --out $output_format --all-features 40 | 41 | if [ "$output_format" == "html" ]; then 42 | echo 43 | echo "You can find the test coverage results at file://$(pwd)/tarpaulin-report.html" 44 | fi 45 | -------------------------------------------------------------------------------- /tools/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | set -e 8 | 9 | MAIN_BRANCH=main 10 | 11 | cd $(dirname "$0") 12 | cd "$(git rev-parse --show-toplevel)" 13 | 14 | source "tools/utils.sh" 15 | 16 | function set_version { 17 | local version=$1 18 | 19 | sed -i "0,/version = .*$/s//version = \"$version\"/" Cargo.toml 20 | 21 | # Update version in `Cargo.lock`. 22 | cargo update -w --offline 23 | } 24 | 25 | if [ $(git symbolic-ref --short HEAD) != $MAIN_BRANCH ]; then 26 | echo "Not in $MAIN_BRANCH branch." >&2 27 | exit 1 28 | fi 29 | 30 | if [ $(git status --porcelain --untracked-files=no | wc -l) -ne 0 ]; then 31 | echo "Working directory is not clean." >&2 32 | exit 1 33 | fi 34 | 35 | echo 'Checking if local branch is up to date...' 36 | 37 | git remote update origin > /dev/null 2> /dev/null 38 | 39 | if git status -uno | grep --silent "behind"; then 40 | echo "Local branch is not up to date." >&2 41 | exit 1 42 | fi 43 | 44 | echo "Current version is $(project_version)." 45 | 46 | echo -n "Which version do you want to release? " 47 | read release_version 48 | 49 | echo -n "Which will be the next version? " 50 | read next_version 51 | 52 | if ! echo "$next_version" | grep --silent -- "-pre$"; then 53 | echo 'Next version must end in `-pre`.' >&2 54 | fi 55 | 56 | echo 57 | echo "Current version: $(project_version)" 58 | echo "Release version: $release_version" 59 | echo "Next version: $next_version" 60 | echo 61 | echo -n 'Does this look right [yes/no]? ' 62 | 63 | read answer 64 | 65 | if [ "$answer" != "yes" ]; then 66 | exit 0 67 | fi 68 | 69 | echo -n "Running tests... " 70 | 71 | if ! ./tools/check.sh 2>/dev/null > /dev/null; then 72 | echo "It failed :(" >&2 73 | exit 0 74 | fi 75 | 76 | echo "done." 77 | 78 | while ! grep "^## " release-notes.md | head -1 | grep --silent "^## $release_version$"; do 79 | echo 80 | echo "There's no entry for this version in the release notes." 81 | echo -n "Go ahead and add them and press enter when you're done... " 82 | read 83 | done 84 | 85 | set_version "$release_version" 86 | 87 | git commit -am "Release v${release_version}." 88 | git tag --sign -a "v${release_version}" -m "$(project_name) version ${release_version}." 89 | 90 | set_version "$next_version" 91 | 92 | git commit -am "Bump to version $next_version." 93 | 94 | echo "Check if everything is alright. If so do:" 95 | echo 96 | echo " git push --atomic origin $MAIN_BRANCH v${release_version} && git checkout v${release_version} && cargo publish && git checkout $MAIN_BRANCH" 97 | echo 98 | -------------------------------------------------------------------------------- /tools/utils.sh: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | function assert_installed { 6 | local bin="$1" 7 | 8 | if ! [ -x "$(which "$bin" 2> /dev/null)" ]; then 9 | echo "error: $bin not installed." >&2 10 | exit 1 11 | fi 12 | } 13 | 14 | function project_name { 15 | cargo pkgid | tac -s'/' | head -1 | cut -d'#' -f1 16 | } 17 | 18 | function project_version { 19 | cargo pkgid | tac -s'/' | head -1 | cut -d'#' -f2 20 | } 21 | --------------------------------------------------------------------------------