├── .github ├── FUNDING.yml └── workflows │ ├── deploy.yml │ └── check.yml ├── .gitignore ├── Cargo.toml ├── src ├── template.html ├── main.rs ├── macros.rs ├── input.rs ├── token.rs ├── parser.rs └── page_gen.rs ├── static ├── theme-light.css ├── theme-dark.css ├── script.js └── style.css ├── LICENSE ├── data ├── fs.yml ├── chrono.yml └── index.yml └── Cargo.lock /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [upsuper] 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /out 3 | /target 4 | **/*.rs.bk 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cheatsheet-gen" 3 | version = "0.1.0" 4 | authors = ["Xidorn Quan "] 5 | edition = "2018" 6 | publish = false 7 | 8 | [dependencies] 9 | bitflags = "1.0.4" 10 | combine = "4.0.1" 11 | either_n = "0.2.0" 12 | lazy_static = "1.3.0" 13 | serde_yaml = "0.8.9" 14 | v_htmlescape = "0.13.1" 15 | 16 | [dependencies.serde] 17 | version = "1.0.90" 18 | features = ["derive"] 19 | 20 | [dev-dependencies] 21 | pretty_assertions = "1" 22 | -------------------------------------------------------------------------------- /src/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {title} - Rust cheat sheet 6 | 7 | 8 | 9 | 10 | {content} 11 | 19 | 20 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate combine; 3 | 4 | use crate::input::InputData; 5 | use crate::page_gen::generate_to; 6 | use std::env; 7 | use std::error::Error; 8 | 9 | #[macro_use] 10 | mod macros; 11 | 12 | mod input; 13 | mod page_gen; 14 | mod parser; 15 | mod token; 16 | 17 | fn main() -> Result<(), Box> { 18 | let mut args = env::args(); 19 | let _ = args.next(); // executable path 20 | // Get path of input data file and output file 21 | let input_file = args.next().expect("must specify input file"); 22 | let output_file = args.next().expect("must specify output file"); 23 | // Generate the page 24 | let input = InputData::from_file(&input_file)?; 25 | generate_to(&output_file, &input)?; 26 | Ok(()) 27 | } 28 | -------------------------------------------------------------------------------- /static/theme-light.css: -------------------------------------------------------------------------------- 1 | body, .impls h4, .impls ul { 2 | color: black; 3 | background: white; 4 | } 5 | .group-title { 6 | color: #8e908c; 7 | } 8 | main a, main span { color: rgba(0, 0, 0, var(--opacity)); } 9 | .method, .fn { color: rgba(154, 110, 49, var(--opacity)); } 10 | .trait { color: rgba(124, 90, 243, var(--opacity)); } 11 | .enum { color: rgba(80, 129, 87, var(--opacity)); } 12 | .primitive { color: rgba(44, 128, 147, var(--opacity)); } 13 | .struct { color: rgba(173, 68, 142, var(--opacity)); } 14 | .type, .assoc-type { color: rgba(186, 93, 0, var(--opacity)); } 15 | .union { color: rgba(118, 123, 39, var(--opacity)); } 16 | .where { color: rgba(78, 76, 76, var(--opacity)); } 17 | footer { 18 | background: white; 19 | color: #999; 20 | } 21 | footer, footer li:last-child { 22 | border-color: #ccc; 23 | } 24 | footer li.on a { 25 | color: #333; 26 | background: #ccc; 27 | } 28 | -------------------------------------------------------------------------------- /static/theme-dark.css: -------------------------------------------------------------------------------- 1 | body, .impls h4, .impls ul { 2 | color: #ddd; 3 | background: #353535; 4 | } 5 | .group-title { 6 | color: #8d8d8b; 7 | } 8 | main a, main span { color: rgba(221, 221, 221, var(--opacity)); } 9 | .method, .fn { color: rgba(43, 171, 99, var(--opacity)); } 10 | .trait { color: rgba(183, 140, 242, var(--opacity)); } 11 | .enum { color: rgba(130, 176, 137, var(--opacity)); } 12 | .primitive { color: rgba(67, 174, 199, var(--opacity)); } 13 | .struct { color: rgba(45, 191, 184, var(--opacity)); } 14 | .type, .assoc-type { color: rgba(255, 127, 0, var(--opacity)); } 15 | .union { color: rgba(166, 174, 55, var(--opacity)); } 16 | .where { color: rgba(221, 221, 221, var(--opacity)); } 17 | footer { 18 | background: #353535; 19 | color: #ccc; 20 | } 21 | footer, footer li:last-child { 22 | border-color: #999; 23 | } 24 | footer li.on a { 25 | color: #333; 26 | background: #ccc; 27 | } 28 | -------------------------------------------------------------------------------- /static/script.js: -------------------------------------------------------------------------------- 1 | const root = document.documentElement; 2 | const args = location.search.slice(1).split(',').filter(arg => !!arg); 3 | for (const arg of args) { 4 | switch (arg) { 5 | case 'dark': 6 | document.getElementById('theme').href = 'theme-dark.css'; 7 | break; 8 | case 'large': 9 | case 'single': 10 | root.classList.add(arg); 11 | break; 12 | default: 13 | console.warn(`Unknown argument ${arg}`); 14 | } 15 | } 16 | 17 | window.addEventListener("DOMContentLoaded", () => { 18 | const footer = document.querySelector('footer'); 19 | const modeSwitches = footer.querySelectorAll('li > a[href^="?"]'); 20 | for (const a of modeSwitches) { 21 | const mode = a.getAttribute('href').slice(1); 22 | if (args.includes(mode)) { 23 | a.parentNode.classList.add('on'); 24 | a.href = '?' + args.filter(arg => arg !== mode).join(','); 25 | } else { 26 | a.href = '?' + [...args, mode].join(','); 27 | } 28 | } 29 | }, { once: true }); 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | 6 | name: Deploy 7 | 8 | jobs: 9 | deploy: 10 | name: Deploy 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout sources 14 | uses: actions/checkout@v2 15 | 16 | - name: Install stable toolchain 17 | id: toolchain 18 | uses: actions-rs/toolchain@v1 19 | with: 20 | profile: minimal 21 | toolchain: stable 22 | override: true 23 | 24 | - name: Cache Cargo 25 | uses: actions/cache@v2 26 | with: 27 | path: | 28 | ~/.cargo/registry 29 | ~/.cargo/git 30 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 31 | restore-keys: | 32 | ${{ runner.os }}-cargo- 33 | 34 | - name: Cache target 35 | uses: actions/cache@v2 36 | with: 37 | path: target 38 | key: ${{ runner.os }}-build-${{ steps.toolchain.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.lock') }}-${{ hashFiles('**/*.rs') }} 39 | restore-keys: | 40 | ${{ runner.os }}-build-${{ steps.toolchain.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.lock') }}- 41 | ${{ runner.os }}-build-${{ steps.toolchain.outputs.rustc_hash }}- 42 | 43 | - name: Build pages 44 | shell: bash 45 | run: ./build_pages.sh 46 | 47 | - name: Deploy 48 | uses: peaceiris/actions-gh-pages@v3 49 | with: 50 | github_token: ${{ secrets.GITHUB_TOKEN }} 51 | publish_dir: ./out 52 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | name: Check 3 | 4 | jobs: 5 | format: 6 | name: Format 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout sources 10 | uses: actions/checkout@v2 11 | 12 | - name: Install stable toolchain 13 | uses: actions-rs/toolchain@v1 14 | with: 15 | profile: minimal 16 | toolchain: stable 17 | override: true 18 | components: rustfmt 19 | 20 | - name: Run cargo fmt 21 | uses: actions-rs/cargo@v1 22 | with: 23 | command: fmt 24 | args: --all -- --check 25 | 26 | clippy: 27 | name: Clippy 28 | runs-on: ubuntu-latest 29 | steps: 30 | - name: Checkout sources 31 | uses: actions/checkout@v2 32 | 33 | - name: Install stable toolchain 34 | id: toolchain 35 | uses: actions-rs/toolchain@v1 36 | with: 37 | profile: minimal 38 | toolchain: stable 39 | override: true 40 | components: clippy 41 | 42 | - name: Cache Cargo 43 | uses: actions/cache@v2 44 | with: 45 | path: | 46 | ~/.cargo/registry 47 | ~/.cargo/git 48 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 49 | restore-keys: | 50 | ${{ runner.os }}-cargo- 51 | 52 | - name: Cache target 53 | uses: actions/cache@v2 54 | with: 55 | path: target 56 | key: ${{ runner.os }}-clippy-${{ steps.toolchain.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.lock') }}-${{ hashFiles('**/*.rs') }} 57 | restore-keys: | 58 | ${{ runner.os }}-clippy-${{ steps.toolchain.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.lock') }}- 59 | ${{ runner.os }}-clippy-${{ steps.toolchain.outputs.rustc_hash }}- 60 | 61 | - name: Run cargo clippy 62 | uses: actions-rs/cargo@v1 63 | with: 64 | command: clippy 65 | args: -- -D warnings 66 | -------------------------------------------------------------------------------- /static/style.css: -------------------------------------------------------------------------------- 1 | @import url(https://cdn.jsdelivr.net/gh/tonsky/FiraCode@2/distr/fira_code.css); 2 | 3 | body { 4 | font: 16px/1.5 'Fira Code', monospace; 5 | --opacity: 1; 6 | } 7 | main { 8 | position: absolute; 9 | width: auto; 10 | display: flex; 11 | white-space: pre; 12 | transform-origin: 0 0; 13 | transform: scale(0.5); 14 | } 15 | .single main { 16 | display: unset; 17 | } 18 | .large main { 19 | transform: unset; 20 | } 21 | a { 22 | text-decoration: none; 23 | color: inherit; 24 | } 25 | a:hover { 26 | text-decoration: underline; 27 | } 28 | ul { 29 | list-style: none; 30 | } 31 | ul, li { 32 | padding: 0; 33 | margin: 0; 34 | } 35 | .section { 36 | padding: 0 2em 5em; 37 | } 38 | .part-title-group { 39 | margin: 1.5em 0 1em; 40 | } 41 | .part-title { 42 | font-size: 2em; 43 | line-height: normal; 44 | } 45 | .part-title, .part-subtitle { 46 | margin: 0; 47 | } 48 | .part-subtitle { 49 | font-size: 1.5em; 50 | } 51 | .group-title { 52 | font: inherit; 53 | margin: 1em 0 0; 54 | } 55 | .group-title::before { 56 | content: "// "; 57 | } 58 | .group-list, .type-impls { 59 | margin-bottom: 1em; 60 | } 61 | .group-list { 62 | padding-left: 2em; 63 | } 64 | .item::before { 65 | content: "=> "; 66 | margin-left: -2em; 67 | } 68 | .item-fn::before { 69 | content: ":: "; 70 | } 71 | .type-impls a, .group-list a { 72 | font-weight: 500; 73 | } 74 | .prefix-fn { 75 | margin-left: -1.8em; 76 | opacity: 0; 77 | -moz-user-select: none; 78 | user-select: none; 79 | } 80 | .nested { --opacity: 0.6; } 81 | .nested .nested { --opacity: 0.4; } 82 | .trait-matched { 83 | position: relative; 84 | display: inline-block; 85 | cursor: pointer; 86 | outline: 0 none; 87 | } 88 | .trait-matched:hover .impls { 89 | visibility: visible; 90 | } 91 | .impls { 92 | visibility: hidden; 93 | position: absolute; 94 | top: 0; 95 | left: 0; 96 | z-index: 1; 97 | cursor: auto; 98 | pointer-events: none; 99 | padding-bottom: 2em; 100 | --opacity: 1; 101 | } 102 | .impls .nested { --opacity: 0.6; } 103 | .impls .nested .nested { --opacity: 0.4; } 104 | .impls-title { 105 | font: inherit; 106 | margin: 0; 107 | width: max-content; 108 | pointer-events: auto; 109 | } 110 | .impls-list { 111 | padding: 2px 10px; 112 | margin: 0 -10px; 113 | border: 1px solid; 114 | border-radius: 5px; 115 | width: max-content; 116 | pointer-events: auto; 117 | } 118 | footer { 119 | font-size: 0.6em; 120 | position: fixed; 121 | bottom: 16px; 122 | right: 16px; 123 | border: 1px solid; 124 | border-radius: 5px; 125 | opacity: .8; 126 | } 127 | footer ul { 128 | display: flex; 129 | } 130 | footer li:first-child { 131 | margin-left: 1px; 132 | } 133 | footer li a { 134 | display: block; 135 | padding: 5px .5em; 136 | border-radius: 5px; 137 | margin-right: 1px; 138 | } 139 | footer li:last-child { 140 | border-left: 1px solid; 141 | } 142 | footer a:hover { 143 | text-decoration: none; 144 | } 145 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | #[macro_export] 3 | macro_rules! tokens { 4 | ($($t:tt)*) => {{ 5 | #[allow(unused_imports)] 6 | use crate::token::{Primitive, Range, Token, TokenStream}; 7 | let mut result = vec![]; 8 | tokens_impl!(result $($t)*); 9 | result 10 | }}; 11 | } 12 | 13 | #[cfg(test)] 14 | #[macro_export] 15 | macro_rules! tokens_impl { 16 | ($result:ident) => {}; 17 | ($result:ident where $($t:tt)*) => { 18 | $result.push(Token::Where); 19 | tokens_impl!($result $($t)*); 20 | }; 21 | ($result:ident +$ident:ident $($t:tt)*) => { 22 | $result.push(Token::AssocType(stringify!($ident))); 23 | tokens_impl!($result $($t)*); 24 | }; 25 | ($result:ident $ident:ident $($t:tt)*) => { 26 | $result.push(Token::Identifier(stringify!($ident))); 27 | tokens_impl!($result $($t)*); 28 | }; 29 | ($result:ident $str:literal $($t:tt)*) => { 30 | $result.push(Token::Text($str)); 31 | tokens_impl!($result $($t)*); 32 | }; 33 | ($result:ident &$r:literal $($t:tt)*) => { 34 | $result.push(Token::Primitive(Primitive::Ref(concat!("&", $r)))); 35 | tokens_impl!($result $($t)*); 36 | }; 37 | ($result:ident *$r:literal $($t:tt)*) => { 38 | $result.push(Token::Primitive(Primitive::Ptr(concat!("*", $r)))); 39 | tokens_impl!($result $($t)*); 40 | }; 41 | ($result:ident @() $($t:tt)*) => { 42 | $result.push(Token::Type(TokenStream(vec![ 43 | Token::Primitive(Primitive::Unit), 44 | ]))); 45 | tokens_impl!($result $($t)*); 46 | }; 47 | ($result:ident @( $($inner:tt)* ) $($t:tt)*) => { 48 | $result.push(Token::Type(TokenStream(vec![ 49 | Token::Primitive(Primitive::TupleStart), 50 | Token::Nested(TokenStream(tokens!($($inner)*))), 51 | Token::Primitive(Primitive::TupleEnd), 52 | ]))); 53 | tokens_impl!($result $($t)*); 54 | }; 55 | ($result:ident @[ $($inner:tt)* ] $($t:tt)*) => { 56 | let mut inner = vec![]; 57 | inner.push(Token::Primitive(Primitive::SliceStart)); 58 | tokens_impl!(inner $($inner)*); 59 | inner.push(Token::Primitive(Primitive::SliceEnd)); 60 | $result.push(Token::Type(TokenStream(inner))); 61 | tokens_impl!($result $($t)*); 62 | }; 63 | ($result:ident ~$range:ident $($t:tt)*) => { 64 | $result.push(Token::Range(Range::$range)); 65 | tokens_impl!($result $($t)*); 66 | }; 67 | ($result:ident @$ident:ident $($t:tt)*) => { 68 | $result.push(Token::Type(TokenStream(vec![ 69 | Token::Primitive(Primitive::Named(stringify!($ident))), 70 | ]))); 71 | tokens_impl!($result $($t)*); 72 | }; 73 | ($result:ident ^$ident:ident $($t:tt)*) => { 74 | $result.push(Token::Type(TokenStream(vec![ 75 | Token::Identifier(stringify!($ident)), 76 | ]))); 77 | tokens_impl!($result $($t)*); 78 | }; 79 | ($result:ident ^[ $($inner:tt)* ] $($t:tt)*) => { 80 | $result.push(Token::Type(TokenStream(tokens!($($inner)*)))); 81 | tokens_impl!($result $($t)*); 82 | }; 83 | ($result:ident { $($inner:tt)* } $($t:tt)*) => { 84 | $result.push(Token::Nested(TokenStream(tokens!($($inner)*)))); 85 | tokens_impl!($result $($t)*); 86 | }; 87 | } 88 | -------------------------------------------------------------------------------- /src/input.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | use std::collections::HashMap; 3 | use std::error::Error; 4 | use std::fs::File; 5 | use std::path::Path; 6 | 7 | const DEFAULT_STD_URL: &str = "https://doc.rust-lang.org/std/"; 8 | 9 | #[derive(Debug, Deserialize)] 10 | pub struct InputData { 11 | pub title: String, 12 | #[serde(default)] 13 | pub base: BaseUrlMap, 14 | pub main: Vec>, 15 | #[serde(default)] 16 | pub trait_impls: Vec, 17 | pub references: Vec, 18 | } 19 | 20 | impl InputData { 21 | pub fn from_file(path: impl AsRef) -> Result> { 22 | let file = File::open(path)?; 23 | Ok(serde_yaml::from_reader(file)?) 24 | } 25 | } 26 | 27 | #[derive(Debug, Default, Deserialize)] 28 | pub struct BaseUrlMap(HashMap); 29 | 30 | impl BaseUrlMap { 31 | pub fn get_url_for(&self, name: &str) -> Option<&str> { 32 | self.0.get(name).map(String::as_str).or(match name { 33 | "std" => Some(DEFAULT_STD_URL), 34 | _ => None, 35 | }) 36 | } 37 | } 38 | 39 | // TODO: try to avoid using untagged here 40 | // because untagged makes it hard to debug data file when parsing fails. 41 | // Also see https://github.com/serde-rs/serde/issues/1520 42 | #[derive(Debug, Deserialize)] 43 | #[serde(untagged)] 44 | pub enum Part { 45 | Mod(Mod), 46 | Type(Type), 47 | } 48 | 49 | #[derive(Debug, Deserialize)] 50 | pub struct Mod { 51 | #[serde(rename = "mod")] 52 | pub name: String, 53 | pub path: String, 54 | pub groups: Vec, 55 | } 56 | 57 | #[derive(Debug, Deserialize)] 58 | pub struct Type { 59 | #[serde(rename = "type")] 60 | pub ty: String, 61 | pub constraints: Option, 62 | pub impls: Option>, 63 | pub groups: Vec, 64 | } 65 | 66 | #[derive(Clone, Copy, Debug, Deserialize)] 67 | #[serde(rename_all = "lowercase")] 68 | pub enum Kind { 69 | Enum, 70 | Primitive, 71 | Struct, 72 | Trait, 73 | Type, 74 | Union, 75 | } 76 | 77 | impl Kind { 78 | pub fn to_str(self) -> &'static str { 79 | match self { 80 | Kind::Enum => "enum", 81 | Kind::Primitive => "primitive", 82 | Kind::Struct => "struct", 83 | Kind::Trait => "trait", 84 | Kind::Type => "type", 85 | Kind::Union => "union", 86 | } 87 | } 88 | } 89 | 90 | #[derive(Debug, Deserialize)] 91 | pub struct Group { 92 | pub name: Option, 93 | pub items: Vec, 94 | } 95 | 96 | #[derive(Debug, Deserialize)] 97 | #[serde(untagged)] 98 | pub enum InputItem { 99 | Plain(String), 100 | Detailed { 101 | trait_impl: Option, 102 | content: String, 103 | }, 104 | } 105 | 106 | impl InputItem { 107 | pub fn content(&self) -> &str { 108 | match self { 109 | InputItem::Plain(content) => content.as_str(), 110 | InputItem::Detailed { content, .. } => content.as_str(), 111 | } 112 | } 113 | 114 | pub fn trait_impl(&self) -> Option<&str> { 115 | match self { 116 | InputItem::Plain(_) => None, 117 | InputItem::Detailed { trait_impl, .. } => trait_impl.as_ref().map(String::as_str), 118 | } 119 | } 120 | } 121 | 122 | #[derive(Debug, Deserialize)] 123 | pub struct TraitImplPattern { 124 | pub pat: String, 125 | pub generic: Option, 126 | pub impls: Vec, 127 | } 128 | 129 | #[derive(Debug, Deserialize)] 130 | pub struct References { 131 | pub kind: Kind, 132 | pub names: Vec, 133 | #[serde(default)] 134 | pub aliases: HashMap, 135 | } 136 | -------------------------------------------------------------------------------- /data/fs.yml: -------------------------------------------------------------------------------- 1 | title: Filesystem 2 | 3 | base: 4 | fs2: "https://docs.rs/fs2/0.4.3/fs2/" 5 | 6 | main: 7 | # fs & fs2 8 | - - mod: "fs" 9 | path: "std::fs" 10 | groups: 11 | - name: "Read & write" 12 | items: 13 | - "read (AsRef) -> Result>" 14 | - "read_to_string (AsRef) -> Result" 15 | - "write (AsRef, &str) -> Result<()>" 16 | - name: "Directory" 17 | items: 18 | - "read_dir (AsRef) -> Result>>" 19 | - "create_dir (AsRef) -> Result<()>" 20 | - "create_dir_all (AsRef) -> Result<()>" 21 | - "remove_dir (AsRef) -> Result<()>" 22 | - "remove_dir_all (AsRef) -> Result<()>" 23 | - name: "File operation" 24 | items: 25 | - "copy (AsRef, AsRef) -> Result" 26 | - "rename (AsRef, AsRef) -> Result<()>" 27 | - "remove_file (AsRef) -> Result<()>" 28 | - name: "Metadata" 29 | items: 30 | - "metadata (AsRef) -> Result" 31 | - "symlink_metadata (AsRef) -> Result" 32 | - "set_permissions (AsRef, Permissions) -> Result<()>" 33 | - name: "Link" 34 | items: 35 | - "canonicalize (AsRef) -> Result" 36 | - "hard_link (AsRef, AsRef) -> Result<()>" 37 | - "read_link (AsRef) -> Result" 38 | - mod: "fs2" 39 | path: "fs2" 40 | groups: 41 | - name: "Filesystem info" 42 | items: 43 | - "available_space (AsRef) -> Result" 44 | - "free_space (AsRef) -> Result" 45 | - "total_space (AsRef) -> Result" 46 | - "allocation_granularity (AsRef) -> Result" 47 | - "statvfs (AsRef) -> Result" 48 | # Path 49 | - - type: "&Path" 50 | groups: 51 | - name: "Type conversion and display" 52 | items: 53 | - "as_os_str () -> &OsStr" 54 | - "to_path_buf () -> PathBuf" 55 | - "to_str () -> Option<&str>" 56 | - "to_string_lossy () -> Cow" 57 | - "display () -> Display" 58 | - name: "Path type" 59 | items: 60 | - "has_root () -> bool" 61 | - "is_absolute () -> bool" 62 | - "is_relative () -> bool" 63 | - name: "Filename in path" 64 | items: 65 | - "file_name () -> Option<&OsStr>" 66 | - "file_stem () -> Option<&OsStr>" 67 | - "extension () -> Option<&OsStr>" 68 | - name: "Components of path" 69 | items: 70 | - "iter () -> Iterator" 71 | - "components () -> Iterator" 72 | - name: "Ancestors" 73 | items: 74 | - "parent () -> Option<&Path>" 75 | - "ancestors () -> Iterator" 76 | - name: "Prefix / suffix" 77 | items: 78 | # TODO correctly use std::result::Result instead 79 | - "strip_prefix () -> Result<&Path, StripPrefixError>" 80 | - "starts_with (AsRef) -> bool" 81 | - "ends_with (AsRef) -> bool" 82 | - name: "Construct new path" 83 | items: 84 | - "join (AsRef) -> PathBuf" 85 | - "with_file_name (AsRef) -> PathBuf" 86 | - "with_extension (AsRef) -> PathBuf" 87 | - name: "Property of path target" 88 | items: 89 | - "exists () -> bool" 90 | - "is_file () -> bool" 91 | - "is_dir () -> bool" 92 | - name: "Metadata" 93 | items: 94 | - "metadata () -> Result" 95 | - "symlink_metadata () -> Result" 96 | - name: "Misc" 97 | items: 98 | - "read_dir () -> Result>>" 99 | - "read_link () -> Result" 100 | - "canonicalize () -> Result" 101 | - type: "PathBuf" 102 | groups: 103 | - items: 104 | - "::new () -> PathBuf" 105 | - "into_os_string () -> OsString" 106 | - "into_boxed_path () -> Box" 107 | - type: "&mut PathBuf" 108 | groups: 109 | - items: 110 | - "push (AsRef)" 111 | - "pop () -> bool" 112 | - "set_file_name (AsRef)" 113 | - "set_extension (AsRef) -> bool" 114 | # File & fs2::FileExt 115 | - - type: "File" 116 | impls: 117 | - "Read" 118 | - "Write" 119 | - "Seek" 120 | - "FileExt" 121 | groups: 122 | - name: "Open file" 123 | items: 124 | - "::open (AsRef) -> Result" 125 | - "::create (AsRef) -> Result" 126 | - type: "&File" 127 | impls: 128 | - "Read" 129 | - "Write" 130 | - "Seek" 131 | groups: 132 | - name: "Syncing" 133 | items: 134 | - "sync_all () -> Result<()>" 135 | - "sync_data () -> Result<()>" 136 | - name: "Metadata" 137 | items: 138 | - "metadata () -> Result" 139 | - "set_permissions (Permissions) -> Result<()>" 140 | - name: "Other" 141 | items: 142 | - "set_len (u64) -> Result<()>" 143 | - "try_clone () -> Result" 144 | - type: "&FileExt" 145 | groups: 146 | - name: "Allocation" 147 | items: 148 | - "allocate (u64) -> Result<()>" 149 | - "allocated_size () -> Result" 150 | - name: "Lock" 151 | items: 152 | - "unlock () -> Result<()>" 153 | - "lock_shared () -> Result<()>" 154 | - "lock_exclusive () -> Result<()>" 155 | - "try_lock_shared () -> Result<()>" 156 | - "try_lock_exclusive () -> Result<()>" 157 | # Metadata 158 | - - type: "&Metadata" 159 | groups: 160 | - name: "File type" 161 | items: 162 | - "is_dir () -> bool" 163 | - "is_file () -> bool" 164 | - "file_type () -> FileType" 165 | - name: "Time" 166 | items: 167 | - "created () -> Result" 168 | - "modified () -> Result" 169 | - "accessed () -> Result" 170 | - name: "Misc" 171 | items: 172 | - "len () -> u64" 173 | - "permissions () -> Permissions" 174 | 175 | trait_impls: 176 | - pat: "AsRef" 177 | impls: 178 | - "&str" 179 | - "&Path" 180 | - "&OsStr" 181 | - pat: "AsRef" 182 | impls: 183 | - "&str" 184 | - "&OsStr" 185 | - "&Path" 186 | 187 | references: 188 | - kind: trait 189 | names: 190 | - "fs2::FileExt" 191 | - "std::convert::AsRef" 192 | - "std::fmt::Display" 193 | - "std::io::Read" 194 | - "std::io::Seek" 195 | - "std::io::Write" 196 | - "std::iter::Iterator" 197 | - kind: enum 198 | names: 199 | - "std::borrow::Cow" 200 | - "std::io::SeekFrom" 201 | - "std::option::Option" 202 | - "std::path::Component" 203 | - kind: struct 204 | names: 205 | - "fs2::FsStats" 206 | - "std::boxed::Box" 207 | - "std::ffi::OsStr" 208 | - "std::ffi::OsString" 209 | - "std::fs::DirEntry" 210 | - "std::fs::File" 211 | - "std::fs::FileType" 212 | - "std::fs::Metadata" 213 | - "std::fs::Permissions" 214 | - "std::path::Path" 215 | - "std::path::PathBuf" 216 | - "std::path::StripPrefixError" 217 | - "std::string::String" 218 | - "std::time::SystemTime" 219 | - "std::vec::Vec" 220 | - kind: type 221 | names: 222 | - "std::io::Result" 223 | -------------------------------------------------------------------------------- /src/token.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display, Write as _}; 2 | use std::iter::FromIterator; 3 | 4 | #[derive(Clone, Debug, Default, Eq, PartialEq)] 5 | pub struct TokenStream<'a>(pub Vec>); 6 | 7 | impl<'a> TokenStream<'a> { 8 | pub fn matches( 9 | &'a self, 10 | pat: &TokenStream<'_>, 11 | generic: Option<&str>, 12 | ) -> Result>, ()> { 13 | let mut replacement = None; 14 | if tokens_match(self, pat, generic, &mut replacement) { 15 | Ok(replacement) 16 | } else { 17 | Err(()) 18 | } 19 | } 20 | } 21 | 22 | fn tokens_match<'a>( 23 | tokens: &'a TokenStream<'a>, 24 | pat: &TokenStream<'_>, 25 | generic: Option<&str>, 26 | replacement: &mut Option<&'a TokenStream<'a>>, 27 | ) -> bool { 28 | tokens 29 | .0 30 | .iter() 31 | .zip(pat.0.iter()) 32 | .all(|(token, pat)| match (token, pat) { 33 | (Token::Where, Token::Where) => true, 34 | (Token::Identifier(this), Token::Identifier(pat)) => this == pat, 35 | (Token::Primitive(this), Token::Primitive(pat)) => this == pat, 36 | (Token::Range(this), Token::Range(pat)) => this == pat, 37 | (Token::AssocType(this), Token::AssocType(pat)) => this == pat, 38 | (Token::Nested(this), Token::Nested(pat)) => { 39 | tokens_match(this, pat, generic, replacement) 40 | } 41 | (Token::Text(this), Token::Text(pat)) => this 42 | .split_ascii_whitespace() 43 | .zip(pat.split_ascii_whitespace()) 44 | .all(|(this, pat)| this == pat), 45 | (Token::Type(this), Token::Type(pat)) => match (pat.0.as_slice(), generic) { 46 | ([Token::Identifier(ident)], Some(generic)) if *ident == generic => { 47 | if let Some(replacement) = replacement { 48 | tokens_match(this, replacement, None, &mut None) 49 | } else { 50 | *replacement = Some(this); 51 | true 52 | } 53 | } 54 | _ => tokens_match(this, pat, generic, replacement), 55 | }, 56 | _ => false, 57 | }) 58 | } 59 | 60 | impl Display for TokenStream<'_> { 61 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 62 | self.0.iter().try_for_each(|token| write!(f, "{}", token)) 63 | } 64 | } 65 | 66 | impl<'a> FromIterator> for TokenStream<'a> { 67 | fn from_iter>>(iter: I) -> Self { 68 | TokenStream(Vec::from_iter(iter)) 69 | } 70 | } 71 | 72 | impl<'a> IntoIterator for TokenStream<'a> { 73 | type Item = Token<'a>; 74 | type IntoIter = > as IntoIterator>::IntoIter; 75 | 76 | fn into_iter(self) -> Self::IntoIter { 77 | self.0.into_iter() 78 | } 79 | } 80 | 81 | impl<'a> Extend> for TokenStream<'a> { 82 | fn extend>>(&mut self, iter: I) { 83 | self.0.extend(iter); 84 | } 85 | } 86 | 87 | impl<'a, Iter> Extend for TokenStream<'a> 88 | where 89 | Iter: IntoIterator>, 90 | { 91 | fn extend>(&mut self, iter: I) { 92 | self.0.extend(iter.into_iter().flatten()) 93 | } 94 | } 95 | 96 | #[derive(Clone, Debug, Eq, PartialEq)] 97 | pub enum Token<'a> { 98 | Text(&'a str), 99 | Nested(TokenStream<'a>), 100 | Type(TokenStream<'a>), 101 | Primitive(Primitive<'a>), 102 | Identifier(&'a str), 103 | AssocType(&'a str), 104 | Range(RangeToken), 105 | Where, 106 | } 107 | 108 | impl Token<'_> { 109 | pub fn is_whitespace_only(&self) -> bool { 110 | match self { 111 | Token::Text(text) => text.trim().is_empty(), 112 | _ => false, 113 | } 114 | } 115 | } 116 | 117 | impl<'a> From> for Token<'a> { 118 | fn from(primitive: Primitive<'a>) -> Self { 119 | Token::Primitive(primitive) 120 | } 121 | } 122 | 123 | impl Display for Token<'_> { 124 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 125 | match self { 126 | Token::Text(s) | Token::Identifier(s) | Token::AssocType(s) => f.write_str(s), 127 | Token::Nested(inner) | Token::Type(inner) => write!(f, "{}", inner), 128 | Token::Primitive(p) => write!(f, "{}", p), 129 | Token::Range(r) => write!(f, "{}", r), 130 | Token::Where => f.write_str("where"), 131 | } 132 | } 133 | } 134 | 135 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 136 | pub enum Primitive<'a> { 137 | Ref(&'a str), 138 | Ptr(&'a str), 139 | SliceStart, 140 | SliceEnd, 141 | TupleStart, 142 | TupleEnd, 143 | Unit, 144 | Named(&'a str), 145 | } 146 | 147 | impl Display for Primitive<'_> { 148 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 149 | match self { 150 | Primitive::Ref(s) | Primitive::Ptr(s) | Primitive::Named(s) => f.write_str(s), 151 | Primitive::SliceStart => f.write_char('['), 152 | Primitive::SliceEnd => f.write_char(']'), 153 | Primitive::TupleStart => f.write_char('('), 154 | Primitive::TupleEnd => f.write_char(')'), 155 | Primitive::Unit => f.write_str("()"), 156 | } 157 | } 158 | } 159 | 160 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 161 | pub enum RangeToken { 162 | Range, 163 | RangeFrom, 164 | RangeFull, 165 | RangeInclusive, 166 | RangeTo, 167 | RangeToInclusive, 168 | } 169 | 170 | impl Display for RangeToken { 171 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 172 | f.write_str(match self { 173 | RangeToken::Range 174 | | RangeToken::RangeFrom 175 | | RangeToken::RangeFull 176 | | RangeToken::RangeTo => "..", 177 | RangeToken::RangeInclusive | RangeToken::RangeToInclusive => "..=", 178 | }) 179 | } 180 | } 181 | 182 | #[cfg(test)] 183 | mod tests { 184 | use super::Token; 185 | use crate::parser::parse_type; 186 | 187 | #[test] 188 | fn token_stream_matches() { 189 | fn check_match( 190 | pat: &str, 191 | generic: Option<&str>, 192 | cases: &[(&str, Result]>, ()>)], 193 | ) { 194 | let pat = parse_type(pat).unwrap(); 195 | for (ty, expected) in cases.iter() { 196 | let ty = parse_type(ty).unwrap(); 197 | let actual = ty.matches(&pat, generic); 198 | let expected = match expected { 199 | Ok(Some([Token::Type(tokens)])) => Ok(Some(tokens)), 200 | Ok(None) => Ok(None), 201 | Err(()) => Err(()), 202 | _ => unreachable!("unexpected `expected`: `{:?}`", expected), 203 | }; 204 | assert_eq!(actual, expected); 205 | } 206 | } 207 | check_match( 208 | "Try", 209 | Some("T"), 210 | &[ 211 | ("Try ", Ok(Some(&tokens!(@usize)))), 212 | ( 213 | "Try >", 214 | Ok(Some(&tokens!(^[Option "<" ^T ">"]))), 215 | ), 216 | ( 217 | "Try Option >", 218 | Ok(Some(&tokens!(^["(" ") -> " ^[Option "<" ^T ">"]]))), 219 | ), 220 | ("Try", Err(())), 221 | ("Result", Err(())), 222 | ("&Try", Err(())), 223 | ], 224 | ); 225 | check_match( 226 | "SliceIndex<[T]>", 227 | Some("T"), 228 | &[ 229 | ("SliceIndex<[usize]>", Ok(Some(&tokens!(@usize)))), 230 | ("SliceIndex<[()]>", Ok(Some(&tokens!(@())))), 231 | ("SliceIndex<[[T]]>", Ok(Some(&tokens!(@[^T])))), 232 | ("SliceIndex", Err(())), 233 | ], 234 | ); 235 | check_match( 236 | "RangeBounds", 237 | None, 238 | &[ 239 | ("RangeBounds", Ok(None)), 240 | ("RangeBounds < usize >", Ok(None)), 241 | ("RangeBounds", Err(())), 242 | ], 243 | ); 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "autocfg" 7 | version = "1.1.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 10 | 11 | [[package]] 12 | name = "bitflags" 13 | version = "1.3.2" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 16 | 17 | [[package]] 18 | name = "buf-min" 19 | version = "0.6.1" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "f4531c8a9fe2fb94e0d2afdf6bb4effd4797baf98dd26b6e20be71a92ac78e8d" 22 | dependencies = [ 23 | "bytes 0.5.6", 24 | ] 25 | 26 | [[package]] 27 | name = "bytes" 28 | version = "0.5.6" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" 31 | 32 | [[package]] 33 | name = "bytes" 34 | version = "1.2.1" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" 37 | 38 | [[package]] 39 | name = "cfg-if" 40 | version = "1.0.0" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 43 | 44 | [[package]] 45 | name = "cheatsheet-gen" 46 | version = "0.1.0" 47 | dependencies = [ 48 | "bitflags", 49 | "combine", 50 | "either_n", 51 | "lazy_static", 52 | "pretty_assertions", 53 | "serde", 54 | "serde_yaml", 55 | "v_htmlescape", 56 | ] 57 | 58 | [[package]] 59 | name = "combine" 60 | version = "4.6.6" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" 63 | dependencies = [ 64 | "bytes 1.2.1", 65 | "memchr", 66 | ] 67 | 68 | [[package]] 69 | name = "ctor" 70 | version = "0.1.26" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" 73 | dependencies = [ 74 | "quote", 75 | "syn", 76 | ] 77 | 78 | [[package]] 79 | name = "diff" 80 | version = "0.1.13" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" 83 | 84 | [[package]] 85 | name = "either_n" 86 | version = "0.2.0" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "4c91ae510829160d5cfb19eb4ae7b6e01d44b767ca8f727c6cee936e53cc9ae5" 89 | 90 | [[package]] 91 | name = "hashbrown" 92 | version = "0.12.3" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 95 | 96 | [[package]] 97 | name = "indexmap" 98 | version = "1.9.1" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" 101 | dependencies = [ 102 | "autocfg", 103 | "hashbrown", 104 | ] 105 | 106 | [[package]] 107 | name = "lazy_static" 108 | version = "1.4.0" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 111 | 112 | [[package]] 113 | name = "linked-hash-map" 114 | version = "0.5.6" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" 117 | 118 | [[package]] 119 | name = "memchr" 120 | version = "2.5.0" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 123 | 124 | [[package]] 125 | name = "nom" 126 | version = "4.2.3" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" 129 | dependencies = [ 130 | "memchr", 131 | "version_check", 132 | ] 133 | 134 | [[package]] 135 | name = "output_vt100" 136 | version = "0.1.3" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" 139 | dependencies = [ 140 | "winapi", 141 | ] 142 | 143 | [[package]] 144 | name = "pretty_assertions" 145 | version = "1.3.0" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755" 148 | dependencies = [ 149 | "ctor", 150 | "diff", 151 | "output_vt100", 152 | "yansi", 153 | ] 154 | 155 | [[package]] 156 | name = "proc-macro2" 157 | version = "1.0.47" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" 160 | dependencies = [ 161 | "unicode-ident", 162 | ] 163 | 164 | [[package]] 165 | name = "quote" 166 | version = "1.0.21" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" 169 | dependencies = [ 170 | "proc-macro2", 171 | ] 172 | 173 | [[package]] 174 | name = "ryu" 175 | version = "1.0.11" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" 178 | 179 | [[package]] 180 | name = "serde" 181 | version = "1.0.147" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" 184 | dependencies = [ 185 | "serde_derive", 186 | ] 187 | 188 | [[package]] 189 | name = "serde_derive" 190 | version = "1.0.147" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" 193 | dependencies = [ 194 | "proc-macro2", 195 | "quote", 196 | "syn", 197 | ] 198 | 199 | [[package]] 200 | name = "serde_yaml" 201 | version = "0.8.26" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" 204 | dependencies = [ 205 | "indexmap", 206 | "ryu", 207 | "serde", 208 | "yaml-rust", 209 | ] 210 | 211 | [[package]] 212 | name = "syn" 213 | version = "1.0.103" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" 216 | dependencies = [ 217 | "proc-macro2", 218 | "quote", 219 | "unicode-ident", 220 | ] 221 | 222 | [[package]] 223 | name = "unicode-ident" 224 | version = "1.0.5" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" 227 | 228 | [[package]] 229 | name = "v_escape" 230 | version = "0.16.1" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "b57701f09098e70ef300373fcfc1eda4e2961a88824f160894db534d8933a853" 233 | dependencies = [ 234 | "buf-min", 235 | "v_escape_derive", 236 | ] 237 | 238 | [[package]] 239 | name = "v_escape_derive" 240 | version = "0.8.5" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "f29769400af8b264944b851c961a4a6930e76604f59b1fcd51246bab6a296c8c" 243 | dependencies = [ 244 | "nom", 245 | "proc-macro2", 246 | "quote", 247 | "syn", 248 | ] 249 | 250 | [[package]] 251 | name = "v_htmlescape" 252 | version = "0.13.1" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "2609d928c084cd51b46a04b098bce48099278e0029a3489067b58673c720be59" 255 | dependencies = [ 256 | "cfg-if", 257 | "v_escape", 258 | ] 259 | 260 | [[package]] 261 | name = "version_check" 262 | version = "0.1.5" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" 265 | 266 | [[package]] 267 | name = "winapi" 268 | version = "0.3.9" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 271 | dependencies = [ 272 | "winapi-i686-pc-windows-gnu", 273 | "winapi-x86_64-pc-windows-gnu", 274 | ] 275 | 276 | [[package]] 277 | name = "winapi-i686-pc-windows-gnu" 278 | version = "0.4.0" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 281 | 282 | [[package]] 283 | name = "winapi-x86_64-pc-windows-gnu" 284 | version = "0.4.0" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 287 | 288 | [[package]] 289 | name = "yaml-rust" 290 | version = "0.4.5" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" 293 | dependencies = [ 294 | "linked-hash-map", 295 | ] 296 | 297 | [[package]] 298 | name = "yansi" 299 | version = "0.5.1" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" 302 | -------------------------------------------------------------------------------- /data/chrono.yml: -------------------------------------------------------------------------------- 1 | title: Chrono 2 | 3 | base: 4 | chrono: "https://docs.rs/chrono/0.4.6/chrono/" 5 | time: "https://docs.rs/time/0.1.42/time/" 6 | 7 | main: 8 | # DateTime & NaiveDateTime 9 | - - type: "DateTime" 10 | constraints: "where Tz: TimeZone" 11 | impls: 12 | - "Datelike" 13 | - "Timelike" 14 | - "Sub => Output = Duration" 15 | - "Add => Output = Self" 16 | - "Sub => Output = Self" 17 | - "Add => Output = Self" 18 | - "Sub => Output = Self" 19 | groups: 20 | - items: 21 | - "::from_utc (NaiveDateTime, Tz::Offset) -> Self" 22 | - name: "Checked calculation" 23 | items: 24 | - "checked_add_signed (Duration) -> Option" 25 | - "checked_sub_signed (Duration) -> Option" 26 | - "signed_duration_since (DateTime) -> Duration" 27 | - type: "DateTime" 28 | groups: 29 | - name: "Parse" 30 | items: 31 | - "::parse_from_rfc2822 (&str) -> ParseResult" 32 | - "::parse_from_rfc3339 (&str) -> ParseResult" 33 | - "::parse_from_str (&str, &str) -> ParseResult" 34 | - type: "&DateTime" 35 | constraints: "where Tz: TimeZone" 36 | groups: 37 | - name: "Data & time" 38 | items: 39 | - "date () -> Date" 40 | - "time () -> NaiveTime" 41 | - name: "Timestamp" 42 | items: 43 | - "timestamp () -> i64" 44 | - "timestamp_millis () -> i64" 45 | - "timestamp_nanos () -> i64" 46 | - name: "Timestamp sub-second part" 47 | items: 48 | - "timestamp_subsec_millis () -> u32" 49 | - "timestamp_subsec_micros () -> u32" 50 | - "timestamp_subsec_nanos () -> u32" 51 | - name: "Timezone" 52 | items: 53 | - "offset () -> &Tz::Offset" 54 | - "timezone () -> Tz" 55 | - "with_timezone (&Tz2) -> DateTime" 56 | - name: "To NaiveDateTime" 57 | items: 58 | - "naive_utc () -> NaiveDateTime" 59 | - "naive_local () -> NaiveDateTime" 60 | - type: "&DateTime" 61 | constraints: |- 62 | where Tz: TimeZone, 63 | Tz::Offset: Display 64 | groups: 65 | - items: 66 | - "format (&str) -> Display" 67 | - "to_rfc2822 () -> String" 68 | - "to_rfc3339 () -> String" 69 | - "to_rfc3339_opts (SecondsFormat, bool) -> String" 70 | - type: "NaiveDateTime" 71 | impls: 72 | - "Datelike" 73 | - "Timelike" 74 | - "AddAssign" 75 | - "SubAssign" 76 | - "Add => Output = Self" 77 | - "Sub => Output = Self" 78 | - "Add => Output = Self" 79 | - "Sub => Output = Self" 80 | groups: 81 | - name: "Construct" 82 | items: 83 | - "::new (NaiveDate, NaiveTime) -> Self" 84 | - "::from_timestamp (i64, u32) -> Self" 85 | - "::from_timestamp_opt (i64, u32) -> Self" 86 | - "::parse_from_str (&str, &str) -> ParseResult" 87 | - name: "Checked calculation" 88 | items: 89 | - "checked_add_signed (Duration) -> Option" 90 | - "checked_sub_signed (Duration) -> Option" 91 | - "signed_duration_since (NaiveDateTime) -> Duration" 92 | - type: "&NaiveDateTime" 93 | groups: 94 | - name: "Date & time" 95 | items: 96 | - "date () -> NaiveDate" 97 | - "time () -> NaiveTime" 98 | - name: "Timestamp" 99 | items: 100 | - "timestamp () -> i64" 101 | - "timestamp_millis () -> i64" 102 | - "timestamp_nanos () -> i64" 103 | - name: "Timestamp sub-second part" 104 | items: 105 | - "timestamp_subsec_millis () -> u32" 106 | - "timestamp_subsec_micros () -> u32" 107 | - "timestamp_subsec_nanos () -> u32" 108 | - name: "Format" 109 | items: 110 | - "format (&str) -> Display" 111 | # Date & NaiveDate 112 | - - type: "Date" 113 | constraints: "where Tz: TimeZone" 114 | impls: 115 | - "Datelike" 116 | - "Sub => Output = Duration" 117 | - "Add => Output = Self" 118 | - "Sub => Output = Self" 119 | groups: 120 | - items: 121 | - "::from_utc (NaiveDate, Tz::Offset) -> Self" 122 | - name: "Checked calculation" 123 | items: 124 | - "checked_add_signed (Duration) -> Option" 125 | - "checked_sub_signed (Duration) -> Option" 126 | - "signed_duration_since (Date) -> Duration" 127 | - type: "&Date" 128 | constraints: "where Tz: TimeZone" 129 | groups: 130 | - name: "To DateTime (panic when invalid)" 131 | items: 132 | - "and_hms (u32, u32, u32) -> DateTime" 133 | - "and_hms_milli (u32, u32, u32, u32) -> DateTime" 134 | - "and_hms_micro (u32, u32, u32, u32) -> DateTime" 135 | - "and_hms_nano (u32, u32, u32, u32) -> DateTime" 136 | - name: "To DateTime (None when invalid)" 137 | items: 138 | - "and_time (NaiveTime) -> Option>" 139 | - "and_hms_opt (u32, u32, u32) -> Option>" 140 | - "and_hms_milli_opt (u32, u32, u32, u32) -> Option>" 141 | - "and_hms_micro_opt (u32, u32, u32, u32) -> Option>" 142 | - "and_hms_nano_opt (u32, u32, u32, u32) -> Option>" 143 | - name: "Next / previous date" 144 | items: 145 | - "succ () -> Self" 146 | - "pred () -> Self" 147 | - "succ_opt () -> Option" 148 | - "pred_opt () -> Option" 149 | - name: "Timezone" 150 | items: 151 | - "offset () -> &Tz::Offset" 152 | - "timezone () -> Tz" 153 | - "with_timezone (&Tz2) -> Date" 154 | - name: "To NaiveDate" 155 | items: 156 | - "naive_utc () -> NaiveDate" 157 | - "naive_local () -> NaiveDate" 158 | - name: "Format" 159 | items: 160 | - "format (&str) -> Display where Tz::Offset: Display" 161 | - type: "NaiveDate" 162 | impls: 163 | - "Datelike" 164 | - "AddAssign" 165 | - "SubAssign" 166 | - "Sub => Output = Duration" 167 | - "Add => Output = Self" 168 | - "Sub => Output = Self" 169 | groups: 170 | - name: "Construct (panic when invalid)" 171 | items: 172 | - "::from_ymd (i32, u32, u32) -> Self" 173 | - "::from_yo (i32, u32) -> Self" 174 | - "::from_isoywd (i32, u32, Weekday) -> Self" 175 | - "::from_num_days_from_ce (i32) -> Self" 176 | - name: "Construct (None when invalid)" 177 | items: 178 | - "::from_ymd_opt (i32, u32, u32) -> Option" 179 | - "::from_yo_opt (i32, u32) -> Option" 180 | - "::from_isoywd_opt (i32, u32, Weekday) -> Option" 181 | - "::from_num_days_from_ce_opt (i32) -> Option" 182 | - name: "Parse" 183 | items: 184 | - "::parse_from_str (&str, &str) -> ParseResult" 185 | - name: "Checked calculation" 186 | items: 187 | - "checked_add_signed (Duration) -> Option" 188 | - "checked_sub_signed (Duration) -> Option" 189 | - "signed_duration_since (Self) -> Duration" 190 | - type: "&NaiveDate" 191 | groups: 192 | - name: "To NaiveDateTime" 193 | items: 194 | - "and_time (NaiveTime) -> NaiveDateTime" 195 | - name: "To NaiveDateTime (panic when invalid)" 196 | items: 197 | - "and_hms (u32, u32, u32) -> NaiveDateTime" 198 | - "and_hms_milli (u32, u32, u32, u32) -> NaiveDateTime" 199 | - "and_hms_micro (u32, u32, u32, u32) -> NaiveDateTime" 200 | - "and_hms_nano (u32, u32, u32, u32) -> NaiveDateTime" 201 | - name: "To NaiveDateTime (Option when invalid)" 202 | items: 203 | - "and_hms_opt (u32, u32, u32) -> Option" 204 | - "and_hms_milli_opt (u32, u32, u32, u32) -> Option" 205 | - "and_hms_micro_opt (u32, u32, u32, u32) -> Option" 206 | - "and_hms_nano_opt (u32, u32, u32, u32) -> Option" 207 | - name: "Next / previous date" 208 | items: 209 | - "succ () -> Self" 210 | - "pred () -> Self" 211 | - "succ_opt () -> Option" 212 | - "pred_opt () -> Option" 213 | - name: "Format" 214 | items: 215 | - "format (&str) -> Display" 216 | # NaiveTime & Duration 217 | - - type: "NaiveTime" 218 | impls: 219 | - "Timelike" 220 | - "AddAssign" 221 | - "SubAssign" 222 | - "Sub => Output = Duration" 223 | - "Add => Output = Self" 224 | - "Sub => Output = Self" 225 | - "Add => Output = Self" 226 | - "Sub => Output = Self" 227 | groups: 228 | - name: "Construct (panic when invalid)" 229 | items: 230 | - "::from_hms (u32, u32, u32) -> Self" 231 | - "::from_hms_milli (u32, u32, u32, u32) -> Self" 232 | - "::from_hms_micro (u32, u32, u32, u32) -> Self" 233 | - "::from_hms_nano (u32, u32, u32, u32) -> Self" 234 | - "::from_num_seconds_from_midnight (u32, u32) -> Self" 235 | - name: "Construct (None when invalid)" 236 | items: 237 | - "::from_hms_opt (u32, u32, u32) -> Option" 238 | - "::from_hms_milli_opt (u32, u32, u32, u32) -> Option" 239 | - "::from_hms_micro_opt (u32, u32, u32, u32) -> Option" 240 | - "::from_hms_nano_opt (u32, u32, u32, u32) -> Option" 241 | - "::from_num_seconds_from_midnight_opt (u32, u32) -> Option" 242 | - name: "Parse" 243 | items: 244 | - "::parse_from_str (&str, &str) -> ParseResult" 245 | - name: "Calculation" 246 | items: 247 | - "signed_duration_since (Self) -> Duration" 248 | - type: "&NaiveTime" 249 | groups: 250 | - name: "Overflowing calculation" 251 | items: 252 | - "overflowing_add_signed (Duration) -> (Self, i64)" 253 | - "overflowing_sub_signed (Duration) -> (Self, i64)" 254 | - name: "Format" 255 | items: 256 | - "format (&str) -> Display" 257 | - type: "Duration" 258 | impls: 259 | - "Neg => Output = Self" 260 | - "Mul => Output = Self" 261 | - "Div => Output = Self" 262 | - "Add => Output = Self" 263 | - "Sub => Output = Self" 264 | groups: 265 | - name: "Construct from numbers" 266 | items: 267 | - "::weeks (i64) -> Self" 268 | - "::days (i64) -> Self" 269 | - "::hours (i64) -> Self" 270 | - "::minutes (i64) -> Self" 271 | - "::seconds (i64) -> Self" 272 | - "::milliseconds (i64) -> Self" 273 | - "::microseconds (i64) -> Self" 274 | - "::nanoseconds (i64) -> Self" 275 | - name: "Measure duration" 276 | items: 277 | - "::span (() -> ()) -> Self" 278 | - name: "Special values" 279 | items: 280 | - "::zero () -> Self" 281 | - "::min_value () -> Self" 282 | - "::max_value () -> Self" 283 | - name: "From Duration in std" 284 | items: 285 | - "::from_std (StdDuration) -> Result" 286 | - type: "&Duration" 287 | groups: 288 | - name: "Numbers" 289 | items: 290 | - "num_weeks () -> i64" 291 | - "num_days () -> i64" 292 | - "num_hours () -> i64" 293 | - "num_minutes () -> i64" 294 | - "num_seconds () -> i64" 295 | - "num_milliseconds () -> i64" 296 | - "num_microseconds () -> Option" 297 | - "num_nanoseconds () -> Option" 298 | - name: "Checked calculation" 299 | items: 300 | - "checked_add (&Self) -> Option" 301 | - "checked_sub (&Self) -> Option" 302 | - items: 303 | - "is_zero () -> bool" 304 | - "to_std () -> Result" 305 | # Datelike & Timelike 306 | - - type: "&Datelike" 307 | groups: 308 | - name: "Date numbers" 309 | items: 310 | - "year () -> i32" 311 | - "month () -> u32" 312 | - "day () -> u32" 313 | - "ordinal () -> u32" 314 | - name: "Date numbers (zero-based)" 315 | items: 316 | - "month0 () -> u32" 317 | - "day0 () -> u32" 318 | - "ordinal0 () -> u32" 319 | - name: "Week" 320 | items: 321 | - "weekday () -> Weekday" 322 | - "iso_week () -> IsoWeek" 323 | - name: "Construct with different date number" 324 | items: 325 | - "with_year (i32) -> Option" 326 | - "with_month (u32) -> Option" 327 | - "with_day (u32) -> Option" 328 | - "with_ordinal (u32) -> Option" 329 | - name: "Construct with different date number (zero-based)" 330 | items: 331 | - "with_month0 (u32) -> Option" 332 | - "with_day0 (u32) -> Option" 333 | - "with_ordinal0 (u32) -> Option" 334 | - name: "Common era" 335 | items: 336 | - "year_ce () -> (bool, u32)" 337 | - "num_days_from_ce () -> i32" 338 | - type: "&Timelike" 339 | groups: 340 | - name: "Time numbers" 341 | items: 342 | - "hour () -> u32" 343 | - "minute () -> u32" 344 | - "second () -> u32" 345 | - "nanosecond () -> u32" 346 | - name: "Construct with different numbers" 347 | items: 348 | - "with_hour (u32) -> Option" 349 | - "with_minute (u32) -> Option" 350 | - "with_second (u32) -> Option" 351 | - "with_nanosecond (u32) -> Option" 352 | - items: 353 | - "hour12 () -> (bool, u32)" 354 | - "num_seconds_from_midnight () -> u32" 355 | 356 | trait_impls: 357 | - pat: "TimeZone" 358 | impls: 359 | - "Utc" 360 | - "Local" 361 | - "FixedOffset" 362 | 363 | references: 364 | - kind: enum 365 | names: 366 | - "chrono::Weekday" 367 | - "chrono::SecondsFormat" 368 | - "chrono::format::Item" 369 | - "std::option::Option" 370 | - "std::result::Result" 371 | - kind: struct 372 | names: 373 | - "chrono::Date" 374 | - "chrono::DateTime" 375 | - "chrono::Duration" 376 | - "chrono::naive::IsoWeek" 377 | - "chrono::naive::NaiveDate" 378 | - "chrono::naive::NaiveDateTime" 379 | - "chrono::naive::NaiveTime" 380 | - "chrono::offset::FixedOffset" 381 | - "chrono::offset::Local" 382 | - "chrono::offset::Utc" 383 | - "std::string::String" 384 | - "time::OutOfRangeError" 385 | aliases: 386 | StdDuration: "std::time::Duration" 387 | - kind: trait 388 | names: 389 | - "chrono::Datelike" 390 | - "chrono::Timelike" 391 | - "chrono::offset::TimeZone" 392 | - "std::clone::Clone" 393 | - "std::fmt::Display" 394 | - "std::iter::Iterator" 395 | - "std::ops::Add" 396 | - "std::ops::AddAssign" 397 | - "std::ops::Div" 398 | - "std::ops::Mul" 399 | - "std::ops::Neg" 400 | - "std::ops::Sub" 401 | - "std::ops::SubAssign" 402 | - kind: type 403 | names: 404 | - "chrono::format::ParseResult" 405 | -------------------------------------------------------------------------------- /src/parser.rs: -------------------------------------------------------------------------------- 1 | use crate::token::{Primitive, RangeToken, Token, TokenStream}; 2 | use combine::error::StringStreamError; 3 | use combine::parser::{ 4 | char::{alpha_num, char, letter, spaces, string}, 5 | choice::{choice, optional}, 6 | combinator::attempt, 7 | range::recognize, 8 | repeat::{many, skip_many1}, 9 | Parser, 10 | }; 11 | use either_n::{Either2, Either3, Either7}; 12 | use std::iter; 13 | 14 | pub struct ParsedItem<'a> { 15 | pub takes_self: bool, 16 | pub name: &'a str, 17 | pub tokens: TokenStream<'a>, 18 | } 19 | 20 | impl<'a> ParsedItem<'a> { 21 | pub fn parse(input: &'a str) -> Result { 22 | let parser = (optional(string("::")), identifier_str(), item_after_name()); 23 | parse(parser, input).map(|(prefix, name, rest)| ParsedItem { 24 | takes_self: prefix.is_none(), 25 | name, 26 | tokens: TokenStream(rest.collect()), 27 | }) 28 | } 29 | } 30 | 31 | pub fn parse_constraints(input: &str) -> Result, ()> { 32 | parse(where_clause(), input).map(Iterator::collect) 33 | } 34 | 35 | pub fn parse_type(input: &str) -> Result, ()> { 36 | parse(single_type_like_token(), input).map(|token| match token { 37 | Token::Type(inner) => inner, 38 | _ => unreachable!(), 39 | }) 40 | } 41 | 42 | pub fn parse_impl(input: &str) -> Result, ()> { 43 | let parser = chain2( 44 | single_type_like(), 45 | optional_tokens(chain2(lex("=>"), sep1_by_lex(assoc_type_param, ","))), 46 | ); 47 | parse(parser, input).map(Iterator::collect) 48 | } 49 | 50 | pub fn parse_trait_impl(input: &str) -> Result, ()> { 51 | let parser = chain2( 52 | single_type_like(), 53 | optional_tokens(chain2(lex("=>"), sep1_by_lex(assoc_type_param, ","))), 54 | ); 55 | parse(parser, input).map(Iterator::collect) 56 | } 57 | 58 | // TODO: Replace this macro with named existential type when it's available. 59 | // See https://github.com/rust-lang/rust/issues/34511 60 | macro_rules! parser_str_to_iter_token { 61 | ($a:lifetime) => { 62 | parser_str_to!($a, impl Iterator>) 63 | }; 64 | } 65 | 66 | macro_rules! parser_str_to { 67 | ($a:lifetime, $ty:ty) => { 68 | impl Parser<&$a str, Output = $ty> 69 | } 70 | } 71 | 72 | fn parse<'a, T>(mut parser: parser_str_to!('a, T), input: &'a str) -> Result { 73 | parser 74 | .parse(input) 75 | .map_err(|_| ()) 76 | .and_then(|(result, remaining)| match remaining { 77 | "" => Ok(result), 78 | _ => Err(()), 79 | }) 80 | } 81 | 82 | fn item_after_name<'a>() -> parser_str_to_iter_token!('a) { 83 | chain5( 84 | lex("("), 85 | nested_type_like_list(), 86 | lex(")"), 87 | optional_tokens(chain2(lex("->"), single_type_like())), 88 | optional_tokens(where_clause()), 89 | ) 90 | } 91 | 92 | fn where_clause<'a>() -> parser_str_to_iter_token!('a) { 93 | chain2( 94 | wrap("where", Token::Where), 95 | sep1_by_lex(single_where_constraint, ","), 96 | ) 97 | } 98 | 99 | fn single_where_constraint<'a>() -> parser_str_to_iter_token!('a) { 100 | chain3( 101 | single_type_like(), 102 | lex(":"), 103 | sep1_by_lex(simple_named_type, "+"), 104 | ) 105 | } 106 | 107 | type BoxedTokenIter<'a> = Box> + 'a>; 108 | 109 | // Add an extra wrapper for this parser so that it can be invoked recursively. 110 | parser! { 111 | fn type_like['a]()(&'a str) -> BoxedTokenIter<'a> where [] { 112 | type_like_inner() 113 | } 114 | } 115 | 116 | fn type_like_inner<'a>() -> parser_str_to!('a, BoxedTokenIter<'a>) { 117 | sep1_by_lex(single_type_like, "|").map(to_boxed_iter) 118 | } 119 | 120 | // Add an extra wrapper for this parser so that we don't have too deep type name. 121 | parser! { 122 | fn single_type_like['a]()(&'a str) -> BoxedTokenIter<'a> where [] { 123 | single_type_like_inner() 124 | } 125 | } 126 | 127 | fn single_type_like_inner<'a>() -> parser_str_to!('a, BoxedTokenIter<'a>) { 128 | single_type_like_token().map(iter::once).map(to_boxed_iter) 129 | } 130 | 131 | fn to_boxed_iter<'a, T>(iter: impl Iterator + 'a) -> Box + 'a> { 132 | Box::new(iter) 133 | } 134 | 135 | fn single_type_like_token<'a>() -> parser_str_to!('a, Token<'a>) { 136 | to_type_token(choice(( 137 | attempt(ref_type()).map(Either7::One), 138 | attempt(ptr_type()).map(Either7::Two), 139 | attempt(slice_type()).map(Either7::Three), 140 | attempt(fn_type()).map(Either7::Four), 141 | attempt(tuple_type()).map(Either7::Five), 142 | attempt(range_type()).map(Either7::Six), 143 | named_type().map(Either7::Seven), 144 | ))) 145 | } 146 | 147 | fn ref_type<'a>() -> parser_str_to_iter_token!('a) { 148 | chain3( 149 | recognize(( 150 | char('&'), 151 | optional(string("mut")), 152 | optional(attempt((spaces(), lifetime()))), 153 | )) 154 | .map(|s| iter::once(Token::Primitive(Primitive::Ref(s)))), 155 | maybe_spaces(), 156 | single_type_like(), 157 | ) 158 | } 159 | 160 | fn ptr_type<'a>() -> parser_str_to_iter_token!('a) { 161 | chain3( 162 | recognize((char('*'), choice((string("const"), string("mut"))))) 163 | .map(|s| iter::once(Token::Primitive(Primitive::Ptr(s)))), 164 | maybe_spaces(), 165 | single_type_like(), 166 | ) 167 | } 168 | 169 | fn slice_type<'a>() -> parser_str_to_iter_token!('a) { 170 | chain3( 171 | wrap_start("[", Primitive::SliceStart), 172 | type_like(), 173 | wrap_end("]", Primitive::SliceEnd), 174 | ) 175 | } 176 | 177 | fn fn_type<'a>() -> parser_str_to_iter_token!('a) { 178 | chain4( 179 | text((char('('), spaces())), 180 | nested_type_like_list(), 181 | text((spaces(), char(')'), spaces(), string("->"), spaces())), 182 | type_like(), 183 | ) 184 | } 185 | 186 | fn tuple_type<'a>() -> parser_str_to_iter_token!('a) { 187 | choice(( 188 | attempt(wrap("()", Primitive::Unit)).map(Either2::One), 189 | chain3( 190 | wrap_start("(", Primitive::TupleStart), 191 | choice(( 192 | attempt(chain2( 193 | type_like(), 194 | text((spaces(), char(','), spaces(), string("..."), spaces())), 195 | )) 196 | .map(|tokens| Either2::One(iter::once(Token::Nested(tokens.collect())))), 197 | nested_type_like_list().map(Either2::Two), 198 | )), 199 | wrap_end(")", Primitive::TupleEnd), 200 | ) 201 | .map(Either2::Two), 202 | )) 203 | } 204 | 205 | fn nested_type_like_list<'a>() -> parser_str_to_iter_token!('a) { 206 | optional( 207 | sep1_by_lex(type_like, ",") 208 | .map(Iterator::collect) 209 | .map(Token::Nested), 210 | ) 211 | .map(IntoIterator::into_iter) 212 | } 213 | 214 | fn range_type<'a>() -> parser_str_to_iter_token!('a) { 215 | ( 216 | optional(named_type()), 217 | choice((attempt(lex_str("..=")), attempt(lex_str("..")))), 218 | optional(named_type()), 219 | ) 220 | .and_then(|(start, op, end)| { 221 | let range = match (&start, op.trim(), &end) { 222 | (None, "..", None) => RangeToken::RangeFull, 223 | (None, "..", Some(_)) => RangeToken::RangeTo, 224 | (None, "..=", Some(_)) => RangeToken::RangeToInclusive, 225 | (Some(_), "..", None) => RangeToken::RangeFrom, 226 | (Some(_), "..", Some(_)) => RangeToken::Range, 227 | (Some(_), "..=", Some(_)) => RangeToken::RangeInclusive, 228 | _ => return Err(StringStreamError::UnexpectedParse), 229 | }; 230 | let start = start.into_iter().flatten(); 231 | let end = end.into_iter().flatten(); 232 | Ok(iter::empty() 233 | .chain(start) 234 | .chain(range_token(op, range)) 235 | .chain(end)) 236 | }) 237 | } 238 | 239 | fn range_token(s: &str, range: RangeToken) -> impl Iterator> { 240 | let start = match &s[..s.len() - s.trim_start().len()] { 241 | "" => None, 242 | spaces => Some(Token::Text(spaces)), 243 | }; 244 | let end = match &s[s.trim_end().len()..] { 245 | "" => None, 246 | spaces => Some(Token::Text(spaces)), 247 | }; 248 | iter::empty() 249 | .chain(start) 250 | .chain(iter::once(Token::Range(range))) 251 | .chain(end) 252 | } 253 | 254 | fn named_type<'a>() -> parser_str_to_iter_token!('a) { 255 | chain4( 256 | optional_tokens(lex("dyn ")), 257 | simple_named_type(), 258 | // Associated items 259 | many::, _, _>(attempt(chain2( 260 | lex("::"), 261 | identifier_str().map(Token::AssocType).map(iter::once), 262 | ))), 263 | // Additional bounds 264 | optional_tokens(chain2(lex("+"), sep1_by_lex(simple_named_type, "+"))), 265 | ) 266 | } 267 | 268 | // Add an extra wrapper for this parser so that we don't have too deep type name. 269 | parser! { 270 | fn simple_named_type['a]()(&'a str) -> BoxedTokenIter<'a> where [] { 271 | simple_named_type_inner() 272 | } 273 | } 274 | 275 | fn simple_named_type_inner<'a>() -> parser_str_to!('a, BoxedTokenIter<'a>) { 276 | chain2( 277 | // Name 278 | identifier_str().map(|ident| { 279 | iter::once(if is_primitive(ident) { 280 | Token::Primitive(Primitive::Named(ident)) 281 | } else { 282 | Token::Identifier(ident) 283 | }) 284 | }), 285 | // Optional parameters 286 | optional_tokens(chain3( 287 | lex("<"), 288 | sep1_by_lex(type_param, ","), 289 | text((spaces(), char('>'))), 290 | )), 291 | ) 292 | .map(|ty| to_boxed_iter(iter::once(Token::Type(ty.collect())))) 293 | } 294 | 295 | fn to_type_token<'a>(inner: parser_str_to_iter_token!('a)) -> parser_str_to!('a, Token<'a>) { 296 | inner.map(|ty| { 297 | let mut inner: Vec<_> = ty.collect(); 298 | match inner.as_ref() as &[_] { 299 | [Token::Type(_)] => inner.remove(0), 300 | _ => Token::Type(TokenStream(inner)), 301 | } 302 | }) 303 | } 304 | 305 | #[rustfmt::skip] 306 | fn is_primitive(ident: &str) -> bool { 307 | matches!( 308 | ident, 309 | "bool" | "char" | "str" | 310 | "i8" | "i16" | "i32" | "i64" | "i128" | "isize" | 311 | "u8" | "u16" | "u32" | "u64" | "u128" | "usize" 312 | ) 313 | } 314 | 315 | fn type_param<'a>() -> parser_str_to_iter_token!('a) { 316 | choice(( 317 | attempt(lifetime_param()).map(Either3::One), 318 | attempt(assoc_type_param()).map(Either3::Two), 319 | type_like().map(Either3::Three), 320 | )) 321 | } 322 | 323 | fn lifetime_param<'a>() -> parser_str_to_iter_token!('a) { 324 | text(lifetime()) 325 | } 326 | 327 | fn assoc_type_param<'a>() -> parser_str_to_iter_token!('a) { 328 | chain3( 329 | identifier_str().map(Token::AssocType).map(iter::once), 330 | lex("="), 331 | type_like(), 332 | ) 333 | } 334 | 335 | fn optional_tokens<'a>(inner: parser_str_to_iter_token!('a)) -> parser_str_to_iter_token!('a) { 336 | optional(attempt(inner)) 337 | .map(IntoIterator::into_iter) 338 | .map(Iterator::flatten) 339 | } 340 | 341 | fn sep1_by_lex<'a, P, I>( 342 | parser_fn: impl Fn() -> P, 343 | sep: &'static str, 344 | ) -> parser_str_to_iter_token!('a) 345 | where 346 | P: Parser<&'a str, Output = I>, 347 | I: Iterator>, 348 | { 349 | chain2( 350 | parser_fn(), 351 | many::, _, _>(attempt(chain2(lex(sep), parser_fn()))), 352 | ) 353 | } 354 | 355 | fn lex<'a>(s: &'static str) -> parser_str_to_iter_token!('a) { 356 | text(lex_str(s)) 357 | } 358 | 359 | fn lex_str<'a>(s: &'static str) -> parser_str_to!('a, &'a str) { 360 | recognize((spaces(), string(s), spaces())) 361 | } 362 | 363 | fn wrap_start<'a>( 364 | inner: &'static str, 365 | token: impl Into>, 366 | ) -> parser_str_to_iter_token!('a) { 367 | let token = token.into(); 368 | chain2( 369 | string(inner).map(move |_| iter::once(token.clone())), 370 | maybe_spaces(), 371 | ) 372 | } 373 | 374 | fn wrap_end<'a>(inner: &'static str, token: impl Into>) -> parser_str_to_iter_token!('a) { 375 | let token = token.into(); 376 | chain2( 377 | maybe_spaces(), 378 | string(inner).map(move |_| iter::once(token.clone())), 379 | ) 380 | } 381 | 382 | fn wrap<'a>(inner: &'static str, token: impl Into>) -> parser_str_to_iter_token!('a) { 383 | let token = token.into(); 384 | chain3( 385 | maybe_spaces(), 386 | string(inner).map(move |_| iter::once(token.clone())), 387 | maybe_spaces(), 388 | ) 389 | } 390 | 391 | fn maybe_spaces<'a>() -> parser_str_to_iter_token!('a) { 392 | recognize(spaces()).map(|s| match s { 393 | "" => None.into_iter(), 394 | s => Some(Token::Text(s)).into_iter(), 395 | }) 396 | } 397 | 398 | fn text<'a>(inner: impl Parser<&'a str>) -> parser_str_to_iter_token!('a) { 399 | text_token(inner).map(iter::once) 400 | } 401 | 402 | fn text_token<'a>(inner: impl Parser<&'a str>) -> impl Parser<&'a str, Output = Token<'a>> { 403 | recognize(inner).map(Token::Text) 404 | } 405 | 406 | fn lifetime<'a>() -> parser_str_to!('a, &'a str) { 407 | recognize((char('\''), skip_many1(letter()))) 408 | } 409 | 410 | fn identifier_str<'a>() -> parser_str_to!('a, &'a str) { 411 | recognize(skip_many1(choice((alpha_num(), char('_'))))) 412 | } 413 | 414 | macro_rules! impl_chain { 415 | ($name:ident: $($v:ident)+) => { 416 | fn $name<'a>($( 417 | $v: parser_str_to!('a, impl IntoIterator>), 418 | )+) -> parser_str_to_iter_token!('a) { 419 | ($($v),+).map(|($($v),+)| { 420 | iter::empty() $(.chain($v.into_iter()))+ 421 | }) 422 | } 423 | } 424 | } 425 | 426 | impl_chain!(chain2: a b); 427 | impl_chain!(chain3: a b c); 428 | impl_chain!(chain4: a b c d); 429 | impl_chain!(chain5: a b c d e); 430 | 431 | #[cfg(test)] 432 | mod tests { 433 | use combine::Parser; 434 | use pretty_assertions::assert_eq; 435 | 436 | macro_rules! test { 437 | ($parser:ident: [$($input:literal => [$($expected:tt)*],)*]) => { 438 | #[test] 439 | fn $parser() { 440 | $( 441 | let (tokens, remaining) = super::$parser().parse($input) 442 | .expect("failed to parse"); 443 | assert_eq!(remaining, "", "unparsed content"); 444 | assert_eq!(tokens.collect::>(), tokens!($($expected)*)); 445 | )* 446 | } 447 | }; 448 | } 449 | 450 | test!(item_after_name: [ 451 | " ((T) -> ())" => [" (" { ^["(" { ^T } ") -> " @()] } ")"], 452 | " ((&T) -> bool) -> (B, B) where B: Default + Extend" => [ 453 | " (" { ^["(" { ^[&"" ^T] } ") -> " @bool] } ") " "-> " @( ^B ", " ^B ) 454 | " " where " " ^B ": " ^Default " + " ^[ Extend "<" ^T ">" ] 455 | ], 456 | " (S, T) -> S where S: Default + Clone, Tz::Offset: Display" => [ 457 | " (" { ^S ", " ^T } ") " "-> " ^S " " where " " 458 | ^S ": " ^Default " + " ^Clone ", " ^[ ^Tz "::" +Offset ] ": " ^Display 459 | ], 460 | ]); 461 | 462 | test!(type_like: [ 463 | // Named 464 | "Foo" => [^Foo], 465 | "Option" => [^[Option "<" ^Foo ">"]], 466 | "Foo::Err" => [^[^Foo "::" +Err]], 467 | "Box" => [^[Box "<" ^["dyn " ^Foo] ">"]], 468 | "Iterator + Add + Clone" => [ 469 | ^[^[Iterator "<" +Item " = " ^T ">"] " + " ^[Add "<" +Rhs " = " ^Self ">"] " + " ^Clone] 470 | ], 471 | // References 472 | "&Foo" => [^[&"" ^Foo]], 473 | "&'a Foo" => [^[&"'a" " " ^Foo]], 474 | "&mut Foo" => [^[&"mut" " " ^Foo]], 475 | "&mut 'a Foo" => [^[&"mut 'a" " " ^Foo]], 476 | "&[Foo]" => [^[&"" @[^Foo]]], 477 | "&dyn Foo" => [^[&"" ^["dyn " ^Foo]]], 478 | // Pointers 479 | "*const Foo" => [^[*"const" " " ^Foo]], 480 | "*mut Foo" => [^[*"mut" " " ^Foo]], 481 | "*const [Foo]" => [^[*"const" " " @[^Foo]]], 482 | // Tuple-like 483 | "()" => [@()], 484 | "(Foo, &Bar)" => [@(^Foo ", " ^[&"" ^Bar])], 485 | "(Foo, ...)" => [@(^Foo ", ...")], 486 | // Range 487 | "usize.. usize" => [^[@usize ~Range " " @usize]], 488 | "usize..=usize" => [^[@usize ~RangeInclusive @usize]], 489 | " .. usize" => [^[" " ~RangeTo " " @usize]], 490 | " ..=usize" => [^[" " ~RangeToInclusive @usize]], 491 | "usize.. " => [^[@usize ~RangeFrom " "]], 492 | " .. " => [^[" " ~RangeFull " "]], 493 | // Function 494 | "() -> Foo" => [^["(" ") -> " ^Foo]], 495 | "(Iterator) -> Result<(), T>" => [ 496 | ^["(" { ^[Iterator "<" +Item " = " ^T ">"] } ") -> " ^[Result "<" @() ", " ^T ">"]] 497 | ], 498 | "(Foo, &(Bar, &mut 'a [Baz])) -> T" => [ 499 | ^["(" { ^Foo ", " ^[&"" @(^Bar ", " ^[&"mut 'a" " " @[^Baz]])] } ") -> " ^T] 500 | ], 501 | // Union (pseudo-type) 502 | "Foo | &Bar | (Baz) -> bool" => [ 503 | ^Foo " | " ^[&"" ^[Bar "<" ^T ">"]] " | " ^["(" { ^Baz } ") -> " @bool] 504 | ], 505 | ]); 506 | } 507 | -------------------------------------------------------------------------------- /src/page_gen.rs: -------------------------------------------------------------------------------- 1 | use crate::input::{ 2 | BaseUrlMap, Group, InputData, InputItem, Kind, Mod, Part, References, TraitImplPattern, Type, 3 | }; 4 | use crate::parser::{self, ParsedItem}; 5 | use crate::token::{Primitive, RangeToken, Token, TokenStream}; 6 | use bitflags::bitflags; 7 | use std::collections::HashMap; 8 | use std::fmt::{Display, Formatter, Result, Write as _}; 9 | use std::fs::File; 10 | use std::io::{self, Write as _}; 11 | use std::iter; 12 | use std::path::Path; 13 | use v_htmlescape::escape; 14 | 15 | pub fn generate_to(path: impl AsRef, input: &InputData) -> io::Result<()> { 16 | let mut file = File::create(path)?; 17 | let content_writer = PageContentWriter { input }; 18 | write!( 19 | file, 20 | include_str!("template.html"), 21 | title = escape(&input.title), 22 | content = content_writer 23 | )?; 24 | Ok(()) 25 | } 26 | 27 | struct PageContentWriter<'a> { 28 | input: &'a InputData, 29 | } 30 | 31 | impl Display for PageContentWriter<'_> { 32 | fn fmt(&self, f: &mut Formatter) -> Result { 33 | let InputData { 34 | base, 35 | trait_impls, 36 | references, 37 | main, 38 | .. 39 | } = self.input; 40 | Generator::new(base, trait_impls, references).generate(f, main) 41 | } 42 | } 43 | 44 | struct Generator<'a> { 45 | base: &'a BaseUrlMap, 46 | trait_impls: Vec>, 47 | references: HashMap<&'a str, Reference>, 48 | } 49 | 50 | struct TraitImpl<'a> { 51 | pat: TokenStream<'a>, 52 | generic: Option<&'a str>, 53 | impls: Vec>, 54 | } 55 | 56 | impl<'a> Generator<'a> { 57 | fn new( 58 | base: &'a BaseUrlMap, 59 | trait_impls: &'a [TraitImplPattern], 60 | ref_data: &'a [References], 61 | ) -> Self { 62 | let trait_impls = trait_impls 63 | .iter() 64 | .map(|trait_impl| { 65 | let pat = parse_type(&trait_impl.pat); 66 | let generic = trait_impl.generic.as_deref(); 67 | let impls = trait_impl 68 | .impls 69 | .iter() 70 | .map(|impl_| { 71 | parser::parse_trait_impl(impl_) 72 | .map_err(|_| format!("failed to parse trait impl: {}", impl_)) 73 | .unwrap() 74 | }) 75 | .collect(); 76 | TraitImpl { 77 | pat, 78 | generic, 79 | impls, 80 | } 81 | }) 82 | .collect(); 83 | let references = ref_data 84 | .iter() 85 | .flat_map(|reference| { 86 | let kind = reference.kind; 87 | iter::empty() 88 | .chain(reference.names.iter().map(move |item| { 89 | let (path, name) = parse_path(item); 90 | let url = build_type_url(base, &path, kind, name); 91 | (name, Reference { kind, url }) 92 | })) 93 | .chain(reference.aliases.iter().map(move |(alias, path)| { 94 | let (path, name) = parse_path(path); 95 | let url = build_type_url(base, &path, kind, name); 96 | (alias.as_str(), Reference { kind, url }) 97 | })) 98 | }) 99 | .collect(); 100 | Generator { 101 | base, 102 | trait_impls, 103 | references, 104 | } 105 | } 106 | 107 | fn generate(&self, f: &mut Formatter, data: &[Vec]) -> Result { 108 | write!(f, "
")?; 109 | data.iter() 110 | .try_for_each(|section| self.generate_section(f, section))?; 111 | write!(f, "
")?; 112 | Ok(()) 113 | } 114 | 115 | fn generate_section(&self, f: &mut Formatter, section: &[Part]) -> Result { 116 | write!(f, r#"
"#)?; 117 | section 118 | .iter() 119 | .try_for_each(|part| self.generate_part(f, part))?; 120 | write!(f, "
")?; 121 | Ok(()) 122 | } 123 | 124 | fn generate_part(&self, f: &mut Formatter, part: &Part) -> Result { 125 | let info = self.build_part_info(part); 126 | write!(f, r#"
"#)?; 127 | write!( 128 | f, 129 | r#"

{}

"#, 130 | info.base_url, 131 | escape(info.title) 132 | )?; 133 | if let Some(constraints) = &info.constraints { 134 | write!(f, r#"

"#)?; 135 | self.generate_tokens(f, constraints, Flags::LINKIFY | Flags::EXPAND_TRAIT)?; 136 | write!(f, "

")?; 137 | } 138 | write!(f, "
")?; 139 | if let Part::Type(ty) = part { 140 | if let Some(impls) = &ty.impls { 141 | self.generate_impls(f, impls)?; 142 | } 143 | } 144 | info.groups 145 | .iter() 146 | .try_for_each(|group| self.generate_group(f, group, &info)) 147 | } 148 | 149 | fn build_part_info(&self, part: &'a Part) -> PartInfo<'a> { 150 | match part { 151 | Part::Mod(m) => self.build_part_info_for_mod(m), 152 | Part::Type(t) => self.build_part_info_for_type(t), 153 | } 154 | } 155 | 156 | fn build_part_info_for_mod(&self, m: &'a Mod) -> PartInfo<'a> { 157 | let path: Vec<_> = m.path.split("::").collect(); 158 | let url = build_path_url(self.base, &path); 159 | PartInfo { 160 | title: &m.name, 161 | base_url: url, 162 | constraints: None, 163 | groups: &m.groups, 164 | fn_type: FunctionType::Function, 165 | } 166 | } 167 | 168 | fn build_part_info_for_type(&self, t: &'a Type) -> PartInfo<'a> { 169 | let ty = parse_type(&t.ty); 170 | // Unwrap references 171 | let mut inner = &ty; 172 | loop { 173 | let mut iter = inner.0.iter().filter(|token| !token.is_whitespace_only()); 174 | let next_token = match iter.next() { 175 | Some(Token::Primitive(Primitive::Ref(_))) => iter.next(), 176 | _ => break, 177 | }; 178 | inner = match next_token { 179 | Some(Token::Type(inner)) => inner, 180 | _ => unreachable!("unexpected token after ref: {:?}", next_token), 181 | }; 182 | } 183 | // Use the first token as the source of base url for this part 184 | let first_token = inner.0.first().expect("empty inner"); 185 | let url = match first_token { 186 | Token::Identifier(ident) => match self.references.get(ident) { 187 | Some(r) => r.url.clone(), 188 | None => unreachable!("unknown name: {}", ident), 189 | }, 190 | Token::Primitive(primitive) => self.get_primitive_url(primitive), 191 | _ => unreachable!("unexpected token inside type: {}", first_token), 192 | }; 193 | let constraints = 194 | t.constraints.as_ref().map(|constraints| { 195 | match parser::parse_constraints(constraints.as_str()) { 196 | Ok(tokens) => tokens, 197 | Err(_) => unreachable!("failed to parse: {}", constraints), 198 | } 199 | }); 200 | PartInfo { 201 | title: &t.ty, 202 | base_url: url, 203 | constraints, 204 | groups: &t.groups, 205 | fn_type: FunctionType::Method, 206 | } 207 | } 208 | 209 | fn generate_impls(&self, f: &mut Formatter, impls: &[String]) -> Result { 210 | write!(f, r#"
    "#)?; 211 | for impl_item in impls.iter() { 212 | let parsed = match parser::parse_impl(impl_item) { 213 | Ok(tokens) => tokens, 214 | Err(_) => unreachable!("failed to parse impl: {}", impl_item), 215 | }; 216 | write!(f, "
  • impl ")?; 217 | self.generate_tokens(f, &parsed, Flags::LINKIFY)?; 218 | write!(f, "
  • ")?; 219 | } 220 | write!(f, "
")?; 221 | Ok(()) 222 | } 223 | 224 | fn generate_group(&self, f: &mut Formatter, group: &Group, part_info: &PartInfo) -> Result { 225 | if let Some(name) = &group.name { 226 | write!(f, r#"

{}

"#, escape(name))?; 227 | } 228 | write!(f, r#"
    "#)?; 229 | group 230 | .items 231 | .iter() 232 | .try_for_each(|item| self.generate_item(f, item, part_info))?; 233 | write!(f, "
")?; 234 | Ok(()) 235 | } 236 | 237 | fn generate_item(&self, f: &mut Formatter, item: &InputItem, part_info: &PartInfo) -> Result { 238 | let parsed = ParsedItem::parse(item.content()) 239 | .map_err(|_| format!("failed to parse `{}`", item.content())) 240 | .unwrap(); 241 | let kind = match part_info.fn_type { 242 | FunctionType::Function => "fn", 243 | FunctionType::Method => { 244 | if parsed.takes_self { 245 | "method" 246 | } else { 247 | "fn" 248 | } 249 | } 250 | }; 251 | write!(f, r#"
  • "#, kind)?; 252 | write!(f, r#"fn "#)?; 253 | let url = match part_info.fn_type { 254 | FunctionType::Function => format!("fn.{}.html", parsed.name), 255 | FunctionType::Method => match item.trait_impl() { 256 | Some(trait_impl) => format!("#impl-{}", escape(trait_impl)), 257 | None => format!("#method.{}", parsed.name), 258 | }, 259 | }; 260 | write!( 261 | f, 262 | r#"{}"#, 263 | part_info.base_url, url, kind, parsed.name 264 | )?; 265 | self.generate_tokens(f, &parsed.tokens, Flags::LINKIFY | Flags::EXPAND_TRAIT)?; 266 | write!(f, "
  • ")?; 267 | Ok(()) 268 | } 269 | 270 | fn generate_tokens(&self, f: &mut Formatter, tokens: &TokenStream<'_>, flags: Flags) -> Result { 271 | tokens.0.iter().try_for_each(|token| match token { 272 | Token::Text(text) => write!(f, "{}", escape(text)), 273 | Token::Where => write!(f, r#"where"#), 274 | Token::Identifier(ident) => self.generate_identifier(f, ident, flags), 275 | Token::AssocType(ty) => write!(f, r#"{}"#, ty), 276 | Token::Primitive(primitive) => self.generate_primitive(f, primitive, flags), 277 | Token::Range(range) => self.generate_range(f, *range, flags), 278 | Token::Type(ty) => self.generate_type(f, ty, flags), 279 | Token::Nested(nested) => { 280 | write!(f, r#""#)?; 281 | self.generate_tokens(f, nested, flags)?; 282 | write!(f, "") 283 | } 284 | }) 285 | } 286 | 287 | fn generate_type(&self, f: &mut Formatter, tokens: &TokenStream<'_>, flags: Flags) -> Result { 288 | if !flags.contains(Flags::EXPAND_TRAIT) { 289 | return self.generate_tokens(f, tokens, flags); 290 | } 291 | let matched = self.trait_impls.iter().find_map(|trait_impl| { 292 | match tokens.matches(&trait_impl.pat, trait_impl.generic) { 293 | Ok(replacement) => Some((trait_impl, replacement)), 294 | Err(()) => None, 295 | } 296 | }); 297 | let (trait_impl, replacement) = match matched { 298 | Some(matched) => matched, 299 | None => return self.generate_tokens(f, tokens, flags), 300 | }; 301 | write!(f, r#""#)?; 302 | self.generate_tokens(f, tokens, flags & !(Flags::LINKIFY | Flags::EXPAND_TRAIT))?; 303 | write!(f, r#"")?; 322 | Ok(()) 323 | } 324 | 325 | fn generate_identifier(&self, f: &mut Formatter, ident: &str, flags: Flags) -> Result { 326 | match self.references.get(ident) { 327 | Some(r) => { 328 | let kind = r.kind.to_str(); 329 | if flags.contains(Flags::LINKIFY) { 330 | write!(f, r#"{}"#, r.url, kind, ident) 331 | } else { 332 | write!(f, r#"{}"#, kind, ident) 333 | } 334 | } 335 | None => write!(f, "{}", ident), 336 | } 337 | } 338 | 339 | fn generate_primitive( 340 | &self, 341 | f: &mut Formatter, 342 | primitive: &Primitive<'_>, 343 | flags: Flags, 344 | ) -> Result { 345 | if flags.contains(Flags::LINKIFY) { 346 | let url = self.get_primitive_url(primitive); 347 | write!( 348 | f, 349 | r#"{}"#, 350 | url, primitive, 351 | ) 352 | } else { 353 | write!(f, r#"{}"#, primitive) 354 | } 355 | } 356 | 357 | fn get_primitive_url(&self, primitive: &Primitive<'_>) -> String { 358 | let name = match primitive { 359 | Primitive::SliceStart | Primitive::SliceEnd => "slice", 360 | Primitive::TupleStart | Primitive::TupleEnd => "tuple", 361 | Primitive::Unit => "unit", 362 | Primitive::Ref(_) => "reference", 363 | Primitive::Ptr(_) => "pointer", 364 | Primitive::Named(name) => name, 365 | }; 366 | let std_url = self.base.get_url_for("std").unwrap(); 367 | format!("{}primitive.{}.html", std_url, name) 368 | } 369 | 370 | fn generate_range(&self, f: &mut Formatter, range: RangeToken, flags: Flags) -> Result { 371 | if flags.contains(Flags::LINKIFY) { 372 | let name = match range { 373 | RangeToken::Range => "Range", 374 | RangeToken::RangeFrom => "RangeFrom", 375 | RangeToken::RangeFull => "RangeFull", 376 | RangeToken::RangeInclusive => "RangeInclusive", 377 | RangeToken::RangeTo => "RangeTo", 378 | RangeToken::RangeToInclusive => "RangeToInclusive", 379 | }; 380 | write!( 381 | f, 382 | r#"{}"#, 383 | self.base.get_url_for("std").unwrap(), 384 | name, 385 | range 386 | ) 387 | } else { 388 | write!(f, "{}", range) 389 | } 390 | } 391 | } 392 | 393 | fn parse_type(ty: &str) -> TokenStream<'_> { 394 | parser::parse_type(ty) 395 | .map_err(|_| format!("failed to parse `{}`", ty)) 396 | .unwrap() 397 | } 398 | 399 | fn build_type_url(base: &BaseUrlMap, path: &[&str], kind: Kind, name: &str) -> String { 400 | let mut url = build_path_url(base, path); 401 | write!(url, "{}.{}.html", kind.to_str(), name).unwrap(); 402 | url 403 | } 404 | 405 | fn build_path_url(base: &BaseUrlMap, path: &[&str]) -> String { 406 | let (crate_name, path) = path.split_first().expect("zero-length path"); 407 | let mut url = base 408 | .get_url_for(crate_name) 409 | .expect("unknown crate") 410 | .to_string(); 411 | for s in path.iter() { 412 | url.push_str(s); 413 | url.push('/'); 414 | } 415 | url 416 | } 417 | 418 | fn build_tokens_with_replacement<'a>( 419 | tokens: &'a TokenStream<'a>, 420 | generic: &str, 421 | replacement: &'a TokenStream<'a>, 422 | ) -> TokenStream<'a> { 423 | tokens 424 | .0 425 | .iter() 426 | .map(|token| match token { 427 | Token::Type(nested) => Token::Type(match nested.0.as_slice() { 428 | [Token::Identifier(ident)] if *ident == generic => replacement.clone(), 429 | _ => build_tokens_with_replacement(nested, generic, replacement), 430 | }), 431 | Token::Nested(nested) => { 432 | Token::Nested(build_tokens_with_replacement(nested, generic, replacement)) 433 | } 434 | _ => token.clone(), 435 | }) 436 | .collect() 437 | } 438 | 439 | #[derive(Debug)] 440 | struct Reference { 441 | kind: Kind, 442 | url: String, 443 | } 444 | 445 | struct PartInfo<'a> { 446 | title: &'a str, 447 | base_url: String, 448 | constraints: Option>, 449 | groups: &'a [Group], 450 | fn_type: FunctionType, 451 | } 452 | 453 | fn parse_path(s: &str) -> (Box<[&str]>, &str) { 454 | let mut path: Vec<_> = s.split("::").collect(); 455 | let name = path.pop().unwrap(); 456 | let path = path.into_boxed_slice(); 457 | (path, name) 458 | } 459 | 460 | #[derive(Clone, Copy)] 461 | enum FunctionType { 462 | Function, 463 | Method, 464 | } 465 | 466 | bitflags! { 467 | struct Flags: u8 { 468 | /// Linkify identifiers and symbols when possible 469 | const LINKIFY = 0b0001; 470 | /// Expand trait to list of types when available 471 | const EXPAND_TRAIT = 0b0010; 472 | } 473 | } 474 | -------------------------------------------------------------------------------- /data/index.yml: -------------------------------------------------------------------------------- 1 | title: Basics 2 | 3 | main: 4 | # Option & Result 5 | - - type: "Option" 6 | groups: 7 | - name: "To inner type" 8 | items: 9 | - "unwrap () -> T" 10 | - "unwrap_or (T) -> T" 11 | - "unwrap_or_else (() -> T) -> T" 12 | - "unwrap_or_default () -> T where T: Default" 13 | - "expect (&str) -> T" 14 | - name: "Converting to another type" 15 | items: 16 | - "map ((T) -> U) -> Option" 17 | - "map_or (U, (T) -> U) -> U" 18 | - "map_or_else (() -> U, (T) -> U) -> U" 19 | - name: "To Result" 20 | items: 21 | - "ok_or (E) -> Result" 22 | - "ok_or_else (() -> E) -> Result" 23 | - name: "Conditioning" 24 | items: 25 | - "filter ((&T) -> bool) -> Option" 26 | - "and (Option) -> Option" 27 | - "and_then ((T) -> Option) -> Option" 28 | - "or (Option) -> Option" 29 | - "or_else (() -> Option) -> Option" 30 | - "xor (Option) -> Option" 31 | - type: "Option<&T>" 32 | groups: 33 | - name: "Cloning inner" 34 | items: 35 | - "cloned () -> Option where T: Clone" 36 | - "copied () -> Option where T: Copy" 37 | - type: "Option>" 38 | groups: 39 | - items: 40 | - "flatten () -> Option" 41 | - type: "Option>" 42 | groups: 43 | - items: 44 | - "transpose () -> Result, E>" 45 | - type: "&Option" 46 | groups: 47 | - name: "Checking inner" 48 | items: 49 | - "is_some () -> bool" 50 | - "is_none () -> bool" 51 | - name: "To inner reference" 52 | items: 53 | - "as_ref () -> Option<&T>" 54 | - "iter () -> Iterator<&T>" 55 | - |- 56 | as_deref () -> Option<&U> 57 | where T: Deref 58 | - type: "&mut Option" 59 | groups: 60 | - name: "To inner mutable reference" 61 | items: 62 | - "as_mut () -> Option<&mut T>" 63 | - "iter_mut () -> Iterator<&mut T>" 64 | - |- 65 | as_deref_mut () -> Option<&mut U> 66 | where T: DerefMut + Deref 67 | - name: "Mutation" 68 | items: 69 | - "take () -> Option" 70 | - "replace (T) -> Option" 71 | - "insert (T) -> &mut T" 72 | - "get_or_insert (T) -> &mut T" 73 | - "get_or_insert_with (() -> T) -> &mut T" 74 | - type: "Result" 75 | groups: 76 | - name: "To inner type" 77 | items: 78 | - "unwrap () -> T where E: Debug" 79 | - "unwrap_err () -> E where T: Debug" 80 | - "unwrap_or (T) -> T" 81 | - "unwrap_or_else ((E) -> T) -> T" 82 | - "unwrap_or_default () -> T where T: Default" 83 | - "expect (&str) -> T" 84 | - "expect_err (&str) -> E" 85 | - "ok () -> Option" 86 | - "err () -> Option" 87 | - name: "Mapping" 88 | items: 89 | - "map ((T) -> U) -> Result" 90 | - "map_err ((E) -> F) -> Result" 91 | - "map_or (U, (T) -> U) -> U" 92 | - "map_or_else ((E) -> U, (T) -> U) -> U" 93 | - name: "Conditioning" 94 | items: 95 | - "and (Result) -> Result" 96 | - "and_then ((T) -> Result) -> Result" 97 | - "or (Result) -> Result" 98 | - "or_else ((E) -> Result) -> Result" 99 | - type: "Result, E>" 100 | groups: 101 | - name: "Transposing" 102 | items: 103 | - "transpose () -> Option>" 104 | - type: "&Result" 105 | groups: 106 | - name: "Checking inner" 107 | items: 108 | - "is_ok () -> bool" 109 | - "is_err () -> bool" 110 | - name: "To inner reference" 111 | items: 112 | - "as_ref () -> Result<&T, &E>" 113 | - "iter () -> Iterator" 114 | - type: "&mut Result" 115 | groups: 116 | - name: "To inner mutable reference" 117 | items: 118 | - "as_mut () -> Result<&mut T, &mut E>" 119 | - "iter_mut () -> Iterator" 120 | # Iterator 121 | - - type: "Iterator" 122 | groups: 123 | - name: "Mapping and filtering" 124 | items: 125 | - "map (( T) -> U) -> Iterator" 126 | - "filter ((&T) -> bool) -> Iterator" 127 | - "filter_map (( T) -> Option) -> Iterator" 128 | - name: "Collecting and folding" 129 | items: 130 | - "fold (S, (S, T) -> S) -> S" 131 | - "collect () -> B where B: FromIterator" 132 | - "partition ((&T) -> bool) -> (B, B) where B: Default + Extend" 133 | - name: "Counting and enumerating" 134 | items: 135 | - "count () -> usize" 136 | - "last () -> Option" 137 | - "enumerate () -> Iterator" 138 | - name: "Combining with other iterators" 139 | items: 140 | - "zip (IntoIterator) -> Iterator" 141 | - "chain (IntoIterator) -> Iterator" 142 | - name: "Flattening" 143 | items: 144 | - "flatten () -> Iterator where T: IntoIterator" 145 | - "flat_map ((T) -> IntoIterator) -> Iterator" 146 | - name: "Taking and skipping" 147 | items: 148 | - "skip (usize) -> Iterator" 149 | - "take (usize) -> Iterator" 150 | - "skip_while ((&T) -> bool) -> Iterator" 151 | - "take_while ((&T) -> bool) -> Iterator" 152 | - "step_by (usize) -> Iterator" 153 | - name: "Misc. iterating" 154 | items: 155 | - "for_each ((T) -> ()) -> ()" 156 | - "inspect ((&T) -> ()) -> Iterator" 157 | - "scan (S, (&mut S, T) -> Option) -> Iterator" 158 | - name: "Calculations" 159 | items: 160 | - "sum () -> S where S: Sum" 161 | - "product () -> P where P: Product" 162 | - name: "Maximum and minimum" 163 | items: 164 | - "max () -> Option where T: Ord" 165 | - "min () -> Option where T: Ord" 166 | - "max_by ((&T, &T) -> Ordering) -> Option" 167 | - "min_by ((&T, &T) -> Ordering) -> Option" 168 | - "max_by_key ((&T) -> U) -> Option where U: Ord" 169 | - "min_by_key ((&T) -> U) -> Option where U: Ord" 170 | - name: "Comparing with another iterator" 171 | items: 172 | - "eq (IntoIterator) -> bool where T: PartialEq" 173 | - "ne (IntoIterator) -> bool where T: PartialEq" 174 | - "lt (IntoIterator) -> bool where T: PartialOrd" 175 | - "le (IntoIterator) -> bool where T: PartialOrd" 176 | - "gt (IntoIterator) -> bool where T: PartialOrd" 177 | - "ge (IntoIterator) -> bool where T: PartialOrd" 178 | - "cmp (IntoIterator) -> Ordering where T: Ord" 179 | - "partial_cmp (IntoIterator)\n-> Option where T: PartialOrd" 180 | - name: "Reversing and cycling" 181 | items: 182 | - "rev () -> Iterator where Self: DoubleEndedIterator" 183 | - "cycle () -> Iterator where Self: Clone" 184 | - type: "Iterator" 185 | groups: 186 | - name: "Cloning inner" 187 | items: 188 | - "cloned () -> Iterator where T: Clone" 189 | - "copied () -> Iterator where T: Copy" 190 | - type: "&mut Iterator" 191 | groups: 192 | - name: "Finding and positioning" 193 | items: 194 | - "find ((&T) -> bool) -> Option" 195 | - "find_map (( T) -> Option) -> Option" 196 | - "position (( T) -> bool) -> Option" 197 | - |- 198 | rposition (( T) -> bool) -> Option 199 | where Self: ExactSizeIterator + DoubleEndedIterator 200 | - name: "Boolean operations" 201 | items: 202 | - "all ((T) -> bool) -> bool" 203 | - "any ((T) -> bool) -> bool" 204 | - name: "Try iterating" 205 | items: 206 | - "try_for_each ((T) -> R) -> R where R: Try" 207 | - "try_fold (S, (S, T) -> R) -> R where R: Try" 208 | - mod: "iter" 209 | path: "std::iter" 210 | groups: 211 | - name: "Creating simple iterators" 212 | items: 213 | - "empty () -> Iterator" 214 | - "once (T) -> Iterator" 215 | - "once_with (() -> T) -> Iterator" 216 | - "repeat (T) -> Iterator where T: Clone" 217 | - "repeat_with (() -> T) -> Iterator" 218 | - "from_fn (() -> Option) -> Iterator" 219 | - "successors (Option, (&T) -> Option) -> Iterator" 220 | # Slice & Vec 221 | - - type: "&[T]" 222 | groups: 223 | - name: "Splitting to iterator" 224 | items: 225 | - "split ((&T) -> bool) -> Iterator" 226 | - "rsplit ((&T) -> bool) -> Iterator" 227 | - "splitn (usize, (&T) -> bool) -> Iterator" 228 | - "rsplitn (usize, (&T) -> bool) -> Iterator" 229 | - name: "Splitting at position" 230 | items: 231 | - "split_at (usize) -> (&[T], &[T])" 232 | - "split_first () -> Option<(&T, &[T])>" 233 | - "split_last () -> Option<(&T, &[T])>" 234 | - name: "Chunks and windows" 235 | items: 236 | - "chunks (usize) -> Iterator" 237 | - "chunks_exact (usize) -> Iterator" 238 | - "rchunks (usize) -> Iterator" 239 | - "rchunks_exact (usize) -> Iterator" 240 | - "windows (usize) -> Iterator" 241 | - name: "Matching" 242 | items: 243 | - "contains (&T) -> bool where T: PartialEq" 244 | - "starts_with (&[T]) -> bool where T: PartialEq" 245 | - "ends_with (&[T]) -> bool where T: PartialEq" 246 | - name: "Binary searching" 247 | items: 248 | - "binary_search (&T) -> Result where T: Ord" 249 | - "binary_search_by ((&T) -> Ordering) -> Result" 250 | - "binary_search_by_key (&B, (&T) -> B) -> Result where B: Ord" 251 | - name: "Getting and iterating" 252 | items: 253 | - "first () -> Option<&T>" 254 | - "last () -> Option<&T>" 255 | - "get (SliceIndex<[T]>) -> Option<&T>" 256 | - "iter () -> Iterator" 257 | - name: "Length" 258 | items: 259 | - "len () -> usize" 260 | - "is_empty () -> bool" 261 | - type: "&mut [T]" 262 | groups: 263 | - name: "Splitting to iterator" 264 | items: 265 | - "split_mut ((&T) -> bool) -> Iterator" 266 | - "rsplit_mut ((&T) -> bool) -> Iterator" 267 | - "splitn_mut (usize, (&T) -> bool) -> Iterator" 268 | - "rsplitn_mut (usize, (&T) -> bool) -> Iterator" 269 | - name: "Splitting at position" 270 | items: 271 | - "split_at_mut (usize) -> (&mut [T], &mut [T])" 272 | - "split_first_mut () -> Option<(&mut T, &mut [T])>" 273 | - "split_last_mut () -> Option<(&mut T, &mut [T])>" 274 | - name: "Chunks" 275 | items: 276 | - "chunks_mut (usize) -> Iterator" 277 | - "chunks_exact_mut (usize) -> Iterator" 278 | - "rchunks_mut (usize) -> Iterator" 279 | - "rchunks_exact_mut (usize) -> Iterator" 280 | - name: "Sorting" 281 | items: 282 | - "sort () where T: Ord" 283 | - "sort_by ((&T, &T) -> Ordering)" 284 | - "sort_by_key ((&T) -> K) where K: Ord" 285 | - "sort_by_cached_key ((&T) -> K) where K: Ord" 286 | - "sort_unstable () where T: Ord" 287 | - "sort_unstable_by ((&T, &T) -> Ordering)" 288 | - "sort_unstable_by_key ((&T) -> K) where K: Ord" 289 | - name: "Rearranging" 290 | items: 291 | - "swap (usize, usize)" 292 | - "reverse ()" 293 | - "rotate_left (usize)" 294 | - "rotate_right (usize)" 295 | - name: "Overriding" 296 | items: 297 | - "swap_with_slice (&mut [T])" 298 | - "copy_from_slice (&[T]) where T: Copy" 299 | - "clone_from_slice (&[T]) where T: Clone" 300 | - name: "Getting and iterating" 301 | items: 302 | - "first_mut () -> Option<&mut T>" 303 | - "last_mut () -> Option<&mut T>" 304 | - "get_mut (SliceIndex<[T]>) -> Option<&mut T>" 305 | - "iter_mut () -> Iterator" 306 | - type: "&mut Vec" 307 | groups: 308 | - name: "Adding and removing single item" 309 | items: 310 | - "push (T)" 311 | - "pop () -> Option" 312 | - "insert (usize, T)" 313 | - "remove (usize) -> T" 314 | - "swap_remove (usize) -> T" 315 | - name: "Extending" 316 | items: 317 | - "append (&mut Vec)" 318 | - trait_impl: "Extend" 319 | content: "extend (IntoIterator)" 320 | - trait_impl: "Extend<&'a T>" 321 | content: "extend (IntoIterator) where T: Copy" 322 | - "extend_from_slice (&[T]) where T: Clone" 323 | - name: "Resizing" 324 | items: 325 | - "truncate (usize)" 326 | - "resize (usize, T) where T: Clone" 327 | - "resize_with (usize, () -> T)" 328 | - name: "Clearing" 329 | items: 330 | - "clear ()" 331 | - "retain ((&T) -> bool)" 332 | - name: "Removing or replacing range into iterator" 333 | items: 334 | - "drain (RangeBounds) -> Iterator" 335 | - "splice (RangeBounds, IntoIterator) -> Iterator" 336 | - name: "Deduplicating" 337 | items: 338 | - "dedup () where T: PartialEq" 339 | - "dedup_by ((&mut T, &mut T) -> bool)" 340 | - "dedup_by_key ((&mut T) -> K) where K: PartialEq" 341 | - name: "Splitting off" 342 | items: 343 | - "split_off (usize) -> Vec" 344 | - name: "Capacity manipulation" 345 | items: 346 | - "reserve (usize)" 347 | - "reserve_exact (usize)" 348 | - "shrink_to_fit ()" 349 | - mod: "slice" 350 | path: "std::slice" 351 | groups: 352 | - name: "Creating slice from reference" 353 | items: 354 | - "from_ref (&T) -> &[T]" 355 | - "from_mut (&mut T) -> &mut [T]" 356 | # String-related 357 | - - type: "&[u8]" 358 | groups: 359 | - name: "ASCII" 360 | items: 361 | - "is_ascii () -> bool" 362 | - "eq_ignore_ascii_case (&[u8]) -> bool" 363 | - "to_ascii_uppercase () -> Vec" 364 | - "to_ascii_lowercase () -> Vec" 365 | - type: "&mut [u8]" 366 | groups: 367 | - name: "ASCII" 368 | items: 369 | - "make_ascii_uppercase ()" 370 | - "make_ascii_lowercase ()" 371 | - mod: "str" 372 | path: "std::str" 373 | groups: 374 | - name: "Bytes" 375 | items: 376 | - "from_utf8 (&[u8]) -> Result<&str, Utf8Error>" 377 | - "from_utf8_mut (&mut [u8]) -> Result<&mut str, Utf8Error>" 378 | - type: "&str" 379 | groups: 380 | - name: "Chars" 381 | items: 382 | - "chars () -> Iterator" 383 | - "char_indices () -> Iterator" 384 | - "is_char_boundary (usize) -> bool" 385 | - name: "Bytes" 386 | items: 387 | - "bytes () -> Iterator" 388 | - "as_bytes () -> &[u8]" 389 | - name: "Splitting to two parts" 390 | items: 391 | - "split_at (usize) -> (&str, &str)" 392 | - name: "Splitting to iterator" 393 | items: 394 | - "lines () -> Iterator" 395 | - "split_whitespace () -> Iterator" 396 | - "split_ascii_whitespace () -> Iterator" 397 | - "split (Pattern) -> Iterator" 398 | - "rsplit (Pattern) -> Iterator" 399 | - "splitn (usize, Pattern) -> Iterator" 400 | - "rsplitn (usize, Pattern) -> Iterator" 401 | - "split_terminator (Pattern) -> Iterator" 402 | - "rsplit_terminator (Pattern) -> Iterator" 403 | - name: "Trimming" 404 | items: 405 | - "trim () -> &str" 406 | - "trim_start () -> &str" 407 | - "trim_end () -> &str" 408 | - "trim_matches (Pattern) -> &str" 409 | - "trim_start_matches (Pattern) -> &str" 410 | - "trim_end_matches (Pattern) -> &str" 411 | - name: "Matching and finding" 412 | items: 413 | - "contains (Pattern) -> bool" 414 | - "starts_with (Pattern) -> bool" 415 | - "ends_with (Pattern) -> bool" 416 | - "find (Pattern) -> Option" 417 | - "rfind (Pattern) -> Option" 418 | - "matches (Pattern) -> Iterator" 419 | - "rmatches (Pattern) -> Iterator" 420 | - "match_indices (Pattern) -> Iterator" 421 | - "rmatch_indices (Pattern) -> Iterator" 422 | - name: "Case" 423 | items: 424 | - "to_uppercase () -> String" 425 | - "to_lowercase () -> String" 426 | - "to_ascii_uppercase () -> String" 427 | - "to_ascii_lowercase () -> String" 428 | - "eq_ignore_ascii_case (&str) -> bool" 429 | - name: "Replacing" 430 | items: 431 | - "replace (Pattern, &str) -> String" 432 | - "replacen (Pattern, &str, usize) -> String" 433 | - name: "Length" 434 | items: 435 | - "len () -> usize" 436 | - "is_empty () -> bool" 437 | - name: "Misc." 438 | items: 439 | - "is_ascii () -> bool" 440 | - "repeat (usize) -> String" 441 | - "encode_utf16 () -> Iterator" 442 | - "parse () -> Result where F: FromStr" 443 | - type: "&mut str" 444 | groups: 445 | - name: "Splitting to two parts" 446 | items: 447 | - "split_at_mut (usize) -> (&mut str, &mut str)" 448 | - name: "Case conversion" 449 | items: 450 | - "make_ascii_uppercase ()" 451 | - "make_ascii_lowercase ()" 452 | - type: "&mut String" 453 | groups: 454 | - name: "Inserting and appending string" 455 | items: 456 | - "push_str (&str)" 457 | - "insert_str (usize, &str)" 458 | - name: "Adding and removing char" 459 | items: 460 | - "push (char)" 461 | - "pop () -> Option" 462 | - "insert (usize, char)" 463 | - "remove (usize) -> char" 464 | - name: "Clearing" 465 | items: 466 | - "clear ()" 467 | - "truncate (usize)" 468 | - "retain ((char) -> bool)" 469 | - name: "Capacity manipulation" 470 | items: 471 | - "reserve (usize)" 472 | - "reserve_exact (usize)" 473 | - "shrink_to_fit ()" 474 | - name: "Misc." 475 | items: 476 | - "split_off (usize) -> String" 477 | - "replace_range (RangeBounds, &str)" 478 | - "drain (RangeBounds) -> Iterator" 479 | 480 | trait_impls: 481 | - pat: "Try" 482 | generic: "T" 483 | impls: 484 | - "Option" 485 | - "Result" 486 | - pat: "Pattern" 487 | impls: 488 | - "char" 489 | - "&str" 490 | - "&[char]" 491 | - "(char) -> bool" 492 | - pat: "SliceIndex<[T]>" 493 | generic: "T" 494 | impls: 495 | - "usize" 496 | - "usize..usize" 497 | - "usize.." 498 | - " ..usize" 499 | - "usize..=usize" 500 | - " ..=usize" 501 | - " .." 502 | - pat: "RangeBounds" 503 | impls: 504 | - "usize..usize" 505 | - "usize.." 506 | - " ..usize" 507 | - "usize..=usize" 508 | - " ..=usize" 509 | - " .." 510 | 511 | references: 512 | - kind: trait 513 | names: 514 | - "std::clone::Clone" 515 | - "std::cmp::Ord" 516 | - "std::cmp::PartialEq" 517 | - "std::cmp::PartialOrd" 518 | - "std::default::Default" 519 | - "std::fmt::Debug" 520 | - "std::iter::DoubleEndedIterator" 521 | - "std::iter::ExactSizeIterator" 522 | - "std::iter::Extend" 523 | - "std::iter::FromIterator" 524 | - "std::iter::IntoIterator" 525 | - "std::iter::Iterator" 526 | - "std::iter::Product" 527 | - "std::iter::Sum" 528 | - "std::marker::Copy" 529 | - "std::ops::Deref" 530 | - "std::ops::DerefMut" 531 | - "std::ops::RangeBounds" 532 | - "std::ops::Try" 533 | - "std::slice::SliceIndex" 534 | - "std::str::FromStr" 535 | - "std::str::pattern::Pattern" 536 | - kind: enum 537 | names: 538 | - "std::option::Option" 539 | - "std::cmp::Ordering" 540 | - "std::result::Result" 541 | - kind: struct 542 | names: 543 | - "std::ops::Range" 544 | - "std::str::Utf8Error" 545 | - "std::string::String" 546 | - "std::vec::Vec" 547 | --------------------------------------------------------------------------------