├── .gitignore ├── LICENSE ├── Readme.md ├── arg-parsing-with-clap ├── .gitignore ├── Readme.md ├── clap-parsing │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ ├── logger.rs │ │ └── main.rs └── manual-parsing │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ └── main.rs ├── connect-svelte-and-rust-using-wasm ├── .gitignore ├── Readme.md ├── rust │ ├── .cargo │ │ └── config │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── parser.rs ├── svelte │ ├── .gitignore │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── favicon.png │ │ ├── global.css │ │ └── index.html │ ├── rollup.config.js │ ├── scripts │ │ └── setupTypeScript.js │ └── src │ │ ├── App.svelte │ │ └── main.js └── text.txt ├── dev-containers-for-easy-dev-setup ├── .devcontainer │ ├── app-frontend │ │ ├── devcontainer.json │ │ └── docker-compose.yaml │ ├── node │ │ ├── Dockerfile │ │ └── devcontainer.json │ └── ruby │ │ ├── Dockerfile │ │ └── devcontainer.json ├── backend.Dockerfile ├── backend │ └── server.py ├── docker-compose-manual.yaml ├── frontend.Dockerfile ├── frontend │ └── index.html └── ruby-test │ └── test.rb ├── excalibur-tutorial ├── .gitignore ├── Readme.md ├── favicon.ico ├── index.html ├── package-lock.json ├── package.json ├── public │ └── images │ │ └── sword.png ├── src │ ├── cameraStrategy.ts │ ├── constant.ts │ ├── files.d.ts │ ├── goblin.ts │ ├── level.ts │ ├── levelIcon.ts │ ├── levelSelector.ts │ ├── main.ts │ ├── player.ts │ ├── resources.ts │ ├── style.css │ ├── tiledLevel.ts │ └── vite-env.d.ts ├── tsconfig.json └── vite.config.js ├── generics-in-rust ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Readme.md └── src │ └── main.rs ├── javascript-parser-generators ├── Readme.md ├── chevrotain.mjs ├── grammar.js ├── grammar.pegjs ├── ohm-example.js ├── package-lock.json └── package.json ├── native-lazy-types ├── Cargo.lock ├── Cargo.toml ├── README.md └── src │ ├── lazy_static_example.rs │ ├── main.rs │ ├── native_lazy_example.rs │ ├── native_once_example.rs │ └── once_cell_example.rs ├── parser-with-pest ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Readme.md └── src │ ├── document.rs │ ├── html.pest │ └── main.rs ├── tdd-in-deno ├── .gitignore ├── bdd_test.ts ├── external_lib_test.ts ├── simple_test.ts ├── stepped_test.ts └── stubs.ts └── tui-libraries-for-interactive-apps ├── bubbletea ├── go.mod ├── go.sum └── main.go ├── clap ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── enquirer ├── index.js ├── package-lock.json └── package.json ├── gum └── main.sh ├── huh ├── go.mod ├── go.sum └── main.go ├── ink-example ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .prettierignore ├── package-lock.json ├── package.json ├── readme.md ├── source │ ├── app.js │ └── cli.js └── test.js ├── ratatui-example ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs └── textual └── app.py /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | target/ 3 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # LogRocket Blog Source 2 | 3 | --- 4 | 5 | This contains source code for blogs I have written for LogRocket. 6 | 7 | Each directory corresponds to a blog post, and corresponding Readme should contain link of the blog post. 8 | -------------------------------------------------------------------------------- /arg-parsing-with-clap/.gitignore: -------------------------------------------------------------------------------- 1 | **/target/ 2 | -------------------------------------------------------------------------------- /arg-parsing-with-clap/Readme.md: -------------------------------------------------------------------------------- 1 | # Command Line Argument Parsing in Rust with Clap 2 | 3 | --- 4 | 5 | Blog Link : [https://blog.logrocket.com/command-line-argument-parsing-rust-using-clap/](https://blog.logrocket.com/command-line-argument-parsing-rust-using-clap/) 6 | -------------------------------------------------------------------------------- /arg-parsing-with-clap/clap-parsing/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 = "atty" 7 | version = "0.2.14" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 10 | dependencies = [ 11 | "hermit-abi", 12 | "libc", 13 | "winapi", 14 | ] 15 | 16 | [[package]] 17 | name = "autocfg" 18 | version = "1.1.0" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 21 | 22 | [[package]] 23 | name = "bitflags" 24 | version = "1.3.2" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 27 | 28 | [[package]] 29 | name = "clap" 30 | version = "3.1.6" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "d8c93436c21e4698bacadf42917db28b23017027a4deccb35dbe47a7e7840123" 33 | dependencies = [ 34 | "atty", 35 | "bitflags", 36 | "clap_derive", 37 | "indexmap", 38 | "lazy_static", 39 | "os_str_bytes", 40 | "strsim", 41 | "termcolor", 42 | "textwrap", 43 | ] 44 | 45 | [[package]] 46 | name = "clap-parsing" 47 | version = "0.1.0" 48 | dependencies = [ 49 | "clap", 50 | ] 51 | 52 | [[package]] 53 | name = "clap_derive" 54 | version = "3.1.4" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "da95d038ede1a964ce99f49cbe27a7fb538d1da595e4b4f70b8c8f338d17bf16" 57 | dependencies = [ 58 | "heck", 59 | "proc-macro-error", 60 | "proc-macro2", 61 | "quote", 62 | "syn", 63 | ] 64 | 65 | [[package]] 66 | name = "hashbrown" 67 | version = "0.11.2" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 70 | 71 | [[package]] 72 | name = "heck" 73 | version = "0.4.0" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 76 | 77 | [[package]] 78 | name = "hermit-abi" 79 | version = "0.1.19" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 82 | dependencies = [ 83 | "libc", 84 | ] 85 | 86 | [[package]] 87 | name = "indexmap" 88 | version = "1.8.0" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" 91 | dependencies = [ 92 | "autocfg", 93 | "hashbrown", 94 | ] 95 | 96 | [[package]] 97 | name = "lazy_static" 98 | version = "1.4.0" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 101 | 102 | [[package]] 103 | name = "libc" 104 | version = "0.2.119" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" 107 | 108 | [[package]] 109 | name = "memchr" 110 | version = "2.4.1" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 113 | 114 | [[package]] 115 | name = "os_str_bytes" 116 | version = "6.0.0" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" 119 | dependencies = [ 120 | "memchr", 121 | ] 122 | 123 | [[package]] 124 | name = "proc-macro-error" 125 | version = "1.0.4" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 128 | dependencies = [ 129 | "proc-macro-error-attr", 130 | "proc-macro2", 131 | "quote", 132 | "syn", 133 | "version_check", 134 | ] 135 | 136 | [[package]] 137 | name = "proc-macro-error-attr" 138 | version = "1.0.4" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 141 | dependencies = [ 142 | "proc-macro2", 143 | "quote", 144 | "version_check", 145 | ] 146 | 147 | [[package]] 148 | name = "proc-macro2" 149 | version = "1.0.36" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" 152 | dependencies = [ 153 | "unicode-xid", 154 | ] 155 | 156 | [[package]] 157 | name = "quote" 158 | version = "1.0.15" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" 161 | dependencies = [ 162 | "proc-macro2", 163 | ] 164 | 165 | [[package]] 166 | name = "strsim" 167 | version = "0.10.0" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 170 | 171 | [[package]] 172 | name = "syn" 173 | version = "1.0.86" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" 176 | dependencies = [ 177 | "proc-macro2", 178 | "quote", 179 | "unicode-xid", 180 | ] 181 | 182 | [[package]] 183 | name = "termcolor" 184 | version = "1.1.3" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 187 | dependencies = [ 188 | "winapi-util", 189 | ] 190 | 191 | [[package]] 192 | name = "textwrap" 193 | version = "0.15.0" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" 196 | 197 | [[package]] 198 | name = "unicode-xid" 199 | version = "0.2.2" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 202 | 203 | [[package]] 204 | name = "version_check" 205 | version = "0.9.4" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 208 | 209 | [[package]] 210 | name = "winapi" 211 | version = "0.3.9" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 214 | dependencies = [ 215 | "winapi-i686-pc-windows-gnu", 216 | "winapi-x86_64-pc-windows-gnu", 217 | ] 218 | 219 | [[package]] 220 | name = "winapi-i686-pc-windows-gnu" 221 | version = "0.4.0" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 224 | 225 | [[package]] 226 | name = "winapi-util" 227 | version = "0.1.5" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 230 | dependencies = [ 231 | "winapi", 232 | ] 233 | 234 | [[package]] 235 | name = "winapi-x86_64-pc-windows-gnu" 236 | version = "0.4.0" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 239 | -------------------------------------------------------------------------------- /arg-parsing-with-clap/clap-parsing/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2021" 3 | name = "clap-parsing" 4 | version = "0.1.0" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | clap = {version = "4.5.9", features = ["derive"]} 10 | -------------------------------------------------------------------------------- /arg-parsing-with-clap/clap-parsing/src/logger.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | pub struct DummyLogger { 4 | verbosity: usize, 5 | } 6 | 7 | impl DummyLogger { 8 | pub fn new(verbosity: usize) -> Self { 9 | DummyLogger { verbosity } 10 | } 11 | 12 | pub fn log(&self, msg: T) { 13 | println!("{}", msg); 14 | } 15 | 16 | pub fn extra(&self, msg: T) { 17 | if self.verbosity > 0 { 18 | println!("{}", msg); 19 | } 20 | } 21 | 22 | pub fn debug(&self, msg: T) { 23 | if self.verbosity > 1 { 24 | println!("{}", msg); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /arg-parsing-with-clap/clap-parsing/src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::{Parser, Subcommand}; 2 | use logger::DummyLogger; 3 | use std::collections::VecDeque; 4 | use std::fs; 5 | use std::path::PathBuf; 6 | mod logger; 7 | 8 | fn validate_package_name(name: &str) -> Result { 9 | if name.trim().len() != name.len() { 10 | Err(String::from( 11 | "package name cannot have leading and trailing space", 12 | )) 13 | } else { 14 | Ok(name.to_string()) 15 | } 16 | } 17 | 18 | #[derive(Parser, Debug)] 19 | #[command(author = "Author Name", version, about)] 20 | /// A Very simple Package Hunter 21 | struct Arguments { 22 | #[arg(default_value_t = usize::MAX, short, long)] 23 | /// maximum depth to which sub-directories should be explored 24 | max_depth: usize, 25 | #[arg(short, long, action = clap::ArgAction::Count)] 26 | verbosity: u8, 27 | #[command(subcommand)] 28 | cmd: SubCommand, 29 | } 30 | 31 | #[derive(Subcommand, Debug)] 32 | enum SubCommand { 33 | /// Count how many times the package is used 34 | Count { 35 | #[arg(value_parser = validate_package_name)] 36 | /// Name of the package to search 37 | package_name: String, 38 | }, 39 | /// list all the projects 40 | Projects { 41 | #[arg(short, long, default_value_t = String::from("."), value_parser = validate_package_name)] 42 | /// directory to start exploring from 43 | start_path: String, 44 | #[arg(short, long, value_delimiter = ':')] 45 | /// paths to exclude when searching 46 | exclude: Vec, 47 | }, 48 | } 49 | 50 | /// Not the dracula 51 | fn count(name: &str, max_depth: usize, logger: &logger::DummyLogger) -> std::io::Result { 52 | let mut count = 0; 53 | logger.debug("Initializing queue"); 54 | // queue to store next dirs to explore 55 | let mut queue = VecDeque::new(); 56 | logger.debug("Adding current dir to queue"); 57 | // start with current dir 58 | queue.push_back((PathBuf::from("."), 0)); 59 | logger.extra("starting"); 60 | loop { 61 | if queue.is_empty() { 62 | logger.extra("queue empty"); 63 | break; 64 | } 65 | let (path, crr_depth) = queue.pop_back().unwrap(); 66 | logger.debug(format!("path :{:?}, depth :{}", path, crr_depth)); 67 | if crr_depth > max_depth { 68 | continue; 69 | } 70 | logger.extra(format!("exploring {:?}", path)); 71 | for dir in fs::read_dir(path)? { 72 | let dir = dir?; 73 | // we are concerned only if it is a directory 74 | if dir.file_type()?.is_dir() { 75 | if dir.file_name() == name { 76 | logger.log(format!("match found at {:?}", dir.path())); 77 | // we have a match, so stop exploring further 78 | count += 1; 79 | } else { 80 | logger.debug(format!("adding {:?} to queue", dir.path())); 81 | // not a match so check its sub-dirs 82 | queue.push_back((dir.path(), crr_depth + 1)); 83 | } 84 | } 85 | } 86 | } 87 | logger.extra("search completed"); 88 | return Ok(count); 89 | } 90 | 91 | fn projects( 92 | start: &str, 93 | max_depth: usize, 94 | exclude: &[String], 95 | logger: &DummyLogger, 96 | ) -> std::io::Result<()> { 97 | logger.debug("Initializing queue"); 98 | // queue to store next dirs to explore 99 | let mut queue = VecDeque::new(); 100 | logger.debug("Adding start dir to queue"); 101 | // start with current dir 102 | queue.push_back((PathBuf::from(start), 0)); 103 | logger.extra("starting"); 104 | loop { 105 | if queue.is_empty() { 106 | logger.extra("queue empty"); 107 | break; 108 | } 109 | let (path, crr_depth) = queue.pop_back().unwrap(); 110 | logger.debug(format!("path :{:?}, depth :{}", path, crr_depth)); 111 | if crr_depth > max_depth { 112 | continue; 113 | } 114 | logger.extra(format!("exploring {:?}", path)); 115 | // we label the loop so we can continue it from inner loop 116 | 'outer: for dir in fs::read_dir(path)? { 117 | let dir = dir?; 118 | let _path = dir.path(); 119 | let temp_path = _path.to_string_lossy(); 120 | for p in exclude { 121 | if temp_path.contains(p) { 122 | // this specifies that it should continue the 'outer loop 123 | // not the for p in exclude loop 124 | // I originally had bug where I just used continue, and was wondering why 125 | // the projects weren't getting filtered! 126 | continue 'outer; 127 | } 128 | } 129 | // we are concerned only if it is a directory 130 | if dir.file_type()?.is_dir() { 131 | if dir.file_name() == ".git" { 132 | logger.log(format!("project found at {:?}", dir.path())); 133 | // we have a match, so stop exploring further 134 | println!("{:?}", dir.path()); 135 | } else { 136 | logger.debug(format!("adding {:?} to queue", dir.path())); 137 | // not a match so check its sub-dirs 138 | queue.push_back((dir.path(), crr_depth + 1)); 139 | } 140 | } 141 | } 142 | } 143 | logger.extra("search completed"); 144 | return Ok(()); 145 | } 146 | 147 | fn main() { 148 | let args = Arguments::parse(); 149 | let logger = logger::DummyLogger::new(args.verbosity as usize); 150 | match args.cmd { 151 | SubCommand::Count { package_name } => match count(&package_name, args.max_depth, &logger) { 152 | Ok(c) => println!("{} uses found", c), 153 | Err(e) => eprintln!("error in processing : {}", e), 154 | }, 155 | SubCommand::Projects { 156 | start_path, 157 | exclude, 158 | } => match projects(&start_path, args.max_depth, &exclude, &logger) { 159 | Ok(_) => {} 160 | Err(e) => eprintln!("error in processing : {}", e), 161 | }, 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /arg-parsing-with-clap/manual-parsing/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 = "manual-parsing" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /arg-parsing-with-clap/manual-parsing/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "manual-parsing" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /arg-parsing-with-clap/manual-parsing/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | use std::fs; 3 | use std::path::PathBuf; 4 | 5 | #[derive(Default)] 6 | struct Arguments { 7 | package_name: String, 8 | max_depth: usize, 9 | } 10 | 11 | fn get_arguments() -> Arguments { 12 | let args: Vec<_> = std::env::args().collect(); 13 | if args.len() < 3 { 14 | eprintln!("filename is a required argument"); 15 | std::process::exit(1); 16 | } 17 | 18 | let mut ret = Arguments::default(); 19 | ret.max_depth = usize::MAX; 20 | 21 | if args[1] == "-f" { 22 | ret.package_name = args[2].clone(); 23 | } else { 24 | ret.max_depth = args[2].parse().unwrap(); 25 | } 26 | 27 | // now one argument is parsed, time for seconds 28 | 29 | if args.len() > 4 { 30 | if args[3] == "-f" { 31 | ret.package_name = args[4].clone(); 32 | } else { 33 | ret.max_depth = args[4].parse().unwrap(); 34 | } 35 | } 36 | 37 | return ret; 38 | } 39 | 40 | /// Not the dracula 41 | fn count(name: &str, max_depth: usize) -> std::io::Result { 42 | let mut count = 0; 43 | // queue to store next dirs to explore 44 | let mut queue = VecDeque::new(); 45 | // start with current dir 46 | queue.push_back((PathBuf::from("."), 0)); 47 | loop { 48 | if queue.is_empty() { 49 | break; 50 | } 51 | let (path, crr_depth) = queue.pop_back().unwrap(); 52 | if crr_depth > max_depth { 53 | continue; 54 | } 55 | 56 | for dir in fs::read_dir(path)? { 57 | let dir = dir?; 58 | // we are concerned only if it is a directory 59 | if dir.file_type()?.is_dir() { 60 | if dir.file_name() == name { 61 | // we have a match, so stop exploring further 62 | count += 1; 63 | } else { 64 | // not a match so check its sub-dirs 65 | queue.push_back((dir.path(), crr_depth + 1)); 66 | } 67 | } 68 | } 69 | } 70 | 71 | return Ok(count); 72 | } 73 | 74 | fn main() { 75 | let args = get_arguments(); 76 | match count(&args.package_name, args.max_depth) { 77 | Ok(c) => println!("{} uses found", c), 78 | Err(e) => eprintln!("error in processing : {}", e), 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /connect-svelte-and-rust-using-wasm/.gitignore: -------------------------------------------------------------------------------- 1 | **/node_modules/ 2 | **/target/ -------------------------------------------------------------------------------- /connect-svelte-and-rust-using-wasm/Readme.md: -------------------------------------------------------------------------------- 1 | # Integrating a Svelte app with Rust using WebAssembly 2 | 3 | --- 4 | 5 | Blog link : [https://blog.logrocket.com/integrating-svelte-app-rust-webassembly/](https://blog.logrocket.com/integrating-svelte-app-rust-webassembly/) 6 | -------------------------------------------------------------------------------- /connect-svelte-and-rust-using-wasm/rust/.cargo/config: -------------------------------------------------------------------------------- 1 | [target.wasm32-unknown-unknown] 2 | rustflags = [ 3 | "-C", "link-args=-z stack-size=2000000", 4 | ] 5 | -------------------------------------------------------------------------------- /connect-svelte-and-rust-using-wasm/rust/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 = "bumpalo" 7 | version = "3.9.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" 10 | 11 | [[package]] 12 | name = "cfg-if" 13 | version = "1.0.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 16 | 17 | [[package]] 18 | name = "itoa" 19 | version = "1.0.2" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" 22 | 23 | [[package]] 24 | name = "lazy_static" 25 | version = "1.4.0" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 28 | 29 | [[package]] 30 | name = "log" 31 | version = "0.4.17" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 34 | dependencies = [ 35 | "cfg-if", 36 | ] 37 | 38 | [[package]] 39 | name = "proc-macro2" 40 | version = "1.0.39" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" 43 | dependencies = [ 44 | "unicode-ident", 45 | ] 46 | 47 | [[package]] 48 | name = "quote" 49 | version = "1.0.18" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" 52 | dependencies = [ 53 | "proc-macro2", 54 | ] 55 | 56 | [[package]] 57 | name = "rust" 58 | version = "0.1.0" 59 | dependencies = [ 60 | "serde", 61 | "wasm-bindgen", 62 | ] 63 | 64 | [[package]] 65 | name = "ryu" 66 | version = "1.0.10" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" 69 | 70 | [[package]] 71 | name = "serde" 72 | version = "1.0.137" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" 75 | dependencies = [ 76 | "serde_derive", 77 | ] 78 | 79 | [[package]] 80 | name = "serde_derive" 81 | version = "1.0.137" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" 84 | dependencies = [ 85 | "proc-macro2", 86 | "quote", 87 | "syn", 88 | ] 89 | 90 | [[package]] 91 | name = "serde_json" 92 | version = "1.0.81" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" 95 | dependencies = [ 96 | "itoa", 97 | "ryu", 98 | "serde", 99 | ] 100 | 101 | [[package]] 102 | name = "syn" 103 | version = "1.0.95" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" 106 | dependencies = [ 107 | "proc-macro2", 108 | "quote", 109 | "unicode-ident", 110 | ] 111 | 112 | [[package]] 113 | name = "unicode-ident" 114 | version = "1.0.0" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" 117 | 118 | [[package]] 119 | name = "wasm-bindgen" 120 | version = "0.2.80" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" 123 | dependencies = [ 124 | "cfg-if", 125 | "serde", 126 | "serde_json", 127 | "wasm-bindgen-macro", 128 | ] 129 | 130 | [[package]] 131 | name = "wasm-bindgen-backend" 132 | version = "0.2.80" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" 135 | dependencies = [ 136 | "bumpalo", 137 | "lazy_static", 138 | "log", 139 | "proc-macro2", 140 | "quote", 141 | "syn", 142 | "wasm-bindgen-shared", 143 | ] 144 | 145 | [[package]] 146 | name = "wasm-bindgen-macro" 147 | version = "0.2.80" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" 150 | dependencies = [ 151 | "quote", 152 | "wasm-bindgen-macro-support", 153 | ] 154 | 155 | [[package]] 156 | name = "wasm-bindgen-macro-support" 157 | version = "0.2.80" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" 160 | dependencies = [ 161 | "proc-macro2", 162 | "quote", 163 | "syn", 164 | "wasm-bindgen-backend", 165 | "wasm-bindgen-shared", 166 | ] 167 | 168 | [[package]] 169 | name = "wasm-bindgen-shared" 170 | version = "0.2.80" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" 173 | -------------------------------------------------------------------------------- /connect-svelte-and-rust-using-wasm/rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | [lib] 8 | crate-type = ["cdylib", "rlib"] 9 | 10 | 11 | [dependencies] 12 | serde = { version = "1.0.137", features = ["derive"] } 13 | wasm-bindgen = { version= "0.2.63", features = ["serde-serialize"] } 14 | -------------------------------------------------------------------------------- /connect-svelte-and-rust-using-wasm/rust/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod parser; 2 | use wasm_bindgen::prelude::*; 3 | 4 | #[wasm_bindgen] 5 | extern "C" { 6 | fn alert(s: &str); 7 | #[wasm_bindgen(js_name = alert)] 8 | fn alert_usize(a: usize); 9 | } 10 | 11 | #[wasm_bindgen] 12 | pub fn greet() { 13 | alert("Hello in JS from Rust!"); 14 | alert_usize(5); 15 | } 16 | 17 | pub struct OwnerID { 18 | id: usize, 19 | } 20 | 21 | #[wasm_bindgen] 22 | pub struct Car { 23 | pub number: usize, 24 | pub color: usize, // color in hex code 25 | owner: OwnerID, 26 | } 27 | 28 | #[wasm_bindgen] 29 | impl Car { 30 | pub fn new() -> Self { 31 | Car { 32 | number: 0, 33 | color: 0, 34 | owner: OwnerID { id: 0 }, 35 | } 36 | } 37 | pub fn duplicate(&self) -> Self { 38 | Self { 39 | number: self.number + 1, 40 | color: self.color, 41 | owner: OwnerID { id: 0 }, 42 | } 43 | } 44 | 45 | pub fn change_number(&mut self, number: usize) { 46 | self.number = number; 47 | } 48 | 49 | pub fn get_id(&self) -> usize { 50 | self.owner.id 51 | } 52 | } 53 | 54 | #[wasm_bindgen] 55 | pub fn color(a: Car, color: usize) -> Car { 56 | Car { 57 | number: a.number, 58 | color, 59 | owner: OwnerID { id: 0 }, 60 | } 61 | } 62 | 63 | #[wasm_bindgen] 64 | pub fn add(a: usize, b: usize) -> usize { 65 | a + b 66 | } 67 | -------------------------------------------------------------------------------- /connect-svelte-and-rust-using-wasm/rust/src/parser.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use wasm_bindgen::prelude::*; 3 | 4 | // NOTE : This does not handle newlines or spaces or any other exceptions, 5 | // but as the intention of post is to show connecting Rust and JS, 6 | // we consider only ideal conditions and not errors for the parsing functions 7 | #[wasm_bindgen] 8 | pub fn parse(input: &str) -> JsValue { 9 | let mut ret: HashMap> = HashMap::new(); 10 | let (keys, values) = input.split_once(';').unwrap(); 11 | let keys: Vec<_> = keys.split(',').collect(); 12 | let mut temp: Vec> = Vec::with_capacity(keys.len()); 13 | for _ in 0..keys.len() { 14 | temp.push(Vec::new()); 15 | } 16 | for row in values.split(';') { 17 | for (i, v) in row.split(',').enumerate() { 18 | temp[i].push(v.parse().unwrap()); 19 | } 20 | } 21 | for (k, v) in keys.into_iter().zip(temp.into_iter()) { 22 | ret.insert(k.to_owned(), v); 23 | } 24 | JsValue::from_serde(&ret).unwrap() 25 | } 26 | -------------------------------------------------------------------------------- /connect-svelte-and-rust-using-wasm/svelte/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /public/build/ 3 | 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /connect-svelte-and-rust-using-wasm/svelte/README.md: -------------------------------------------------------------------------------- 1 | *Psst — looking for a more complete solution? Check out [SvelteKit](https://kit.svelte.dev), the official framework for building web applications of all sizes, with a beautiful development experience and flexible filesystem-based routing.* 2 | 3 | *Looking for a shareable component template instead? You can [use SvelteKit for that as well](https://kit.svelte.dev/docs#packaging) or the older [sveltejs/component-template](https://github.com/sveltejs/component-template)* 4 | 5 | --- 6 | 7 | # svelte app 8 | 9 | This is a project template for [Svelte](https://svelte.dev) apps. It lives at https://github.com/sveltejs/template. 10 | 11 | To create a new project based on this template using [degit](https://github.com/Rich-Harris/degit): 12 | 13 | ```bash 14 | npx degit sveltejs/template svelte-app 15 | cd svelte-app 16 | ``` 17 | 18 | *Note that you will need to have [Node.js](https://nodejs.org) installed.* 19 | 20 | 21 | ## Get started 22 | 23 | Install the dependencies... 24 | 25 | ```bash 26 | cd svelte-app 27 | npm install 28 | ``` 29 | 30 | ...then start [Rollup](https://rollupjs.org): 31 | 32 | ```bash 33 | npm run dev 34 | ``` 35 | 36 | Navigate to [localhost:8080](http://localhost:8080). You should see your app running. Edit a component file in `src`, save it, and reload the page to see your changes. 37 | 38 | By default, the server will only respond to requests from localhost. To allow connections from other computers, edit the `sirv` commands in package.json to include the option `--host 0.0.0.0`. 39 | 40 | If you're using [Visual Studio Code](https://code.visualstudio.com/) we recommend installing the official extension [Svelte for VS Code](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). If you are using other editors you may need to install a plugin in order to get syntax highlighting and intellisense. 41 | 42 | ## Building and running in production mode 43 | 44 | To create an optimised version of the app: 45 | 46 | ```bash 47 | npm run build 48 | ``` 49 | 50 | You can run the newly built app with `npm run start`. This uses [sirv](https://github.com/lukeed/sirv), which is included in your package.json's `dependencies` so that the app will work when you deploy to platforms like [Heroku](https://heroku.com). 51 | 52 | 53 | ## Single-page app mode 54 | 55 | By default, sirv will only respond to requests that match files in `public`. This is to maximise compatibility with static fileservers, allowing you to deploy your app anywhere. 56 | 57 | If you're building a single-page app (SPA) with multiple routes, sirv needs to be able to respond to requests for *any* path. You can make it so by editing the `"start"` command in package.json: 58 | 59 | ```js 60 | "start": "sirv public --single" 61 | ``` 62 | 63 | ## Using TypeScript 64 | 65 | This template comes with a script to set up a TypeScript development environment, you can run it immediately after cloning the template with: 66 | 67 | ```bash 68 | node scripts/setupTypeScript.js 69 | ``` 70 | 71 | Or remove the script via: 72 | 73 | ```bash 74 | rm scripts/setupTypeScript.js 75 | ``` 76 | 77 | If you want to use `baseUrl` or `path` aliases within your `tsconfig`, you need to set up `@rollup/plugin-alias` to tell Rollup to resolve the aliases. For more info, see [this StackOverflow question](https://stackoverflow.com/questions/63427935/setup-tsconfig-path-in-svelte). 78 | 79 | ## Deploying to the web 80 | 81 | ### With [Vercel](https://vercel.com) 82 | 83 | Install `vercel` if you haven't already: 84 | 85 | ```bash 86 | npm install -g vercel 87 | ``` 88 | 89 | Then, from within your project folder: 90 | 91 | ```bash 92 | cd public 93 | vercel deploy --name my-project 94 | ``` 95 | 96 | ### With [surge](https://surge.sh/) 97 | 98 | Install `surge` if you haven't already: 99 | 100 | ```bash 101 | npm install -g surge 102 | ``` 103 | 104 | Then, from within your project folder: 105 | 106 | ```bash 107 | npm run build 108 | surge public my-project.surge.sh 109 | ``` 110 | -------------------------------------------------------------------------------- /connect-svelte-and-rust-using-wasm/svelte/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-app", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "rollup -c", 7 | "dev": "rollup -c -w", 8 | "start": "sirv public --no-clear" 9 | }, 10 | "devDependencies": { 11 | "@rollup/plugin-commonjs": "^17.0.0", 12 | "@rollup/plugin-node-resolve": "^11.0.0", 13 | "carbon-components-svelte": "^0.64.0", 14 | "rollup": "^2.3.4", 15 | "rollup-plugin-css-only": "^3.1.0", 16 | "rollup-plugin-livereload": "^2.0.0", 17 | "rollup-plugin-svelte": "^7.0.0", 18 | "rollup-plugin-terser": "^7.0.0", 19 | "svelte": "^3.0.0" 20 | }, 21 | "dependencies": { 22 | "@wasm-tool/rollup-plugin-rust": "^2.2.2", 23 | "sirv-cli": "^2.0.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /connect-svelte-and-rust-using-wasm/svelte/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YJDoc2/LogRocket-Blog-Code/e305aa008c0de324590c34c58fe391e313312aaf/connect-svelte-and-rust-using-wasm/svelte/public/favicon.png -------------------------------------------------------------------------------- /connect-svelte-and-rust-using-wasm/svelte/public/global.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | position: relative; 3 | width: 100%; 4 | height: 100%; 5 | } 6 | 7 | body { 8 | color: #333; 9 | margin: 0; 10 | padding: 8px; 11 | box-sizing: border-box; 12 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; 13 | } 14 | 15 | a { 16 | color: rgb(0,100,200); 17 | text-decoration: none; 18 | } 19 | 20 | a:hover { 21 | text-decoration: underline; 22 | } 23 | 24 | a:visited { 25 | color: rgb(0,80,160); 26 | } 27 | 28 | label { 29 | display: block; 30 | } 31 | 32 | input, button, select, textarea { 33 | font-family: inherit; 34 | font-size: inherit; 35 | -webkit-padding: 0.4em 0; 36 | padding: 0.4em; 37 | margin: 0 0 0.5em 0; 38 | box-sizing: border-box; 39 | border: 1px solid #ccc; 40 | border-radius: 2px; 41 | } 42 | 43 | input:disabled { 44 | color: #ccc; 45 | } 46 | 47 | button { 48 | color: #333; 49 | background-color: #f4f4f4; 50 | outline: none; 51 | } 52 | 53 | button:disabled { 54 | color: #999; 55 | } 56 | 57 | button:not(:disabled):active { 58 | background-color: #ddd; 59 | } 60 | 61 | button:focus { 62 | border-color: #666; 63 | } 64 | -------------------------------------------------------------------------------- /connect-svelte-and-rust-using-wasm/svelte/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Svelte app 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /connect-svelte-and-rust-using-wasm/svelte/rollup.config.js: -------------------------------------------------------------------------------- 1 | import svelte from 'rollup-plugin-svelte'; 2 | import commonjs from '@rollup/plugin-commonjs'; 3 | import resolve from '@rollup/plugin-node-resolve'; 4 | import livereload from 'rollup-plugin-livereload'; 5 | import { terser } from 'rollup-plugin-terser'; 6 | import css from 'rollup-plugin-css-only'; 7 | import rust from '@wasm-tool/rollup-plugin-rust'; 8 | 9 | const production = !process.env.ROLLUP_WATCH; 10 | 11 | function serve() { 12 | let server; 13 | 14 | function toExit() { 15 | if (server) server.kill(0); 16 | } 17 | 18 | return { 19 | writeBundle() { 20 | if (server) return; 21 | server = require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], { 22 | stdio: ['ignore', 'inherit', 'inherit'], 23 | shell: true 24 | }); 25 | 26 | process.on('SIGTERM', toExit); 27 | process.on('exit', toExit); 28 | } 29 | }; 30 | } 31 | 32 | export default { 33 | input: 'src/main.js', 34 | output: { 35 | sourcemap: true, 36 | format: 'iife', 37 | name: 'app', 38 | file: 'public/build/bundle.js' 39 | }, 40 | plugins: [ 41 | rust({ 42 | verbose: true, 43 | serverPath: "build/" 44 | }), 45 | svelte({ 46 | compilerOptions: { 47 | // enable run-time checks when not in production 48 | dev: !production 49 | } 50 | }), 51 | // we'll extract any component CSS out into 52 | // a separate file - better for performance 53 | css({ output: 'bundle.css' }), 54 | 55 | // If you have external dependencies installed from 56 | // npm, you'll most likely need these plugins. In 57 | // some cases you'll need additional configuration - 58 | // consult the documentation for details: 59 | // https://github.com/rollup/plugins/tree/master/packages/commonjs 60 | resolve({ 61 | browser: true, 62 | dedupe: ['svelte'] 63 | }), 64 | commonjs(), 65 | 66 | // In dev mode, call `npm run start` once 67 | // the bundle has been generated 68 | !production && serve(), 69 | 70 | // Watch the `public` directory and refresh the 71 | // browser on changes when not in production 72 | !production && livereload('public'), 73 | 74 | // If we're building for production (npm run build 75 | // instead of npm run dev), minify 76 | production && terser() 77 | ], 78 | watch: { 79 | clearScreen: false 80 | } 81 | }; 82 | -------------------------------------------------------------------------------- /connect-svelte-and-rust-using-wasm/svelte/scripts/setupTypeScript.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | /** This script modifies the project to support TS code in .svelte files like: 4 | 5 | 8 | 9 | As well as validating the code for CI. 10 | */ 11 | 12 | /** To work on this script: 13 | rm -rf test-template template && git clone sveltejs/template test-template && node scripts/setupTypeScript.js test-template 14 | */ 15 | 16 | const fs = require("fs") 17 | const path = require("path") 18 | const { argv } = require("process") 19 | 20 | const projectRoot = argv[2] || path.join(__dirname, "..") 21 | 22 | // Add deps to pkg.json 23 | const packageJSON = JSON.parse(fs.readFileSync(path.join(projectRoot, "package.json"), "utf8")) 24 | packageJSON.devDependencies = Object.assign(packageJSON.devDependencies, { 25 | "svelte-check": "^2.0.0", 26 | "svelte-preprocess": "^4.0.0", 27 | "@rollup/plugin-typescript": "^8.0.0", 28 | "typescript": "^4.0.0", 29 | "tslib": "^2.0.0", 30 | "@tsconfig/svelte": "^2.0.0" 31 | }) 32 | 33 | // Add script for checking 34 | packageJSON.scripts = Object.assign(packageJSON.scripts, { 35 | "check": "svelte-check --tsconfig ./tsconfig.json" 36 | }) 37 | 38 | // Write the package JSON 39 | fs.writeFileSync(path.join(projectRoot, "package.json"), JSON.stringify(packageJSON, null, " ")) 40 | 41 | // mv src/main.js to main.ts - note, we need to edit rollup.config.js for this too 42 | const beforeMainJSPath = path.join(projectRoot, "src", "main.js") 43 | const afterMainTSPath = path.join(projectRoot, "src", "main.ts") 44 | fs.renameSync(beforeMainJSPath, afterMainTSPath) 45 | 46 | // Switch the app.svelte file to use TS 47 | const appSveltePath = path.join(projectRoot, "src", "App.svelte") 48 | let appFile = fs.readFileSync(appSveltePath, "utf8") 49 | appFile = appFile.replace(" 49 | 50 | 51 | 52 |

53 | Connecting Rust to Svelte Through WASM ! 54 |

55 | 56 |
57 | 58 |
59 | 68 |
69 | 70 |
71 | 72 |

73 | File Contents Are :
74 | {content} 75 |

76 | 77 |
-------------------------------------------------------------------------------- /connect-svelte-and-rust-using-wasm/svelte/src/main.js: -------------------------------------------------------------------------------- 1 | import App from './App.svelte'; 2 | import wasm from '../../rust/Cargo.toml'; 3 | import "carbon-components-svelte/css/g80.css"; 4 | 5 | const init = async () => { 6 | const bindings = await wasm(); 7 | 8 | const app = new App({ 9 | target: document.body, 10 | props: { 11 | bindings, 12 | }, 13 | }); 14 | }; 15 | 16 | init(); -------------------------------------------------------------------------------- /connect-svelte-and-rust-using-wasm/text.txt: -------------------------------------------------------------------------------- 1 | A,B,C,D;1.5,1.5,5.1,5.1;7.5,5.7,5.5,7.7 -------------------------------------------------------------------------------- /dev-containers-for-easy-dev-setup/.devcontainer/app-frontend/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app-frontend", 3 | "dockerComposeFile":"./docker-compose.yaml", 4 | "service": "frontend", 5 | "workspaceFolder": "/frontend", 6 | "mounts": ["type=bind,source=../../frontend,target=/frontend"], 7 | "shutdownAction": "stopCompose" 8 | } -------------------------------------------------------------------------------- /dev-containers-for-easy-dev-setup/.devcontainer/app-frontend/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | 3 | services: 4 | frontend: 5 | networks: 6 | - app_demo 7 | build: 8 | dockerfile: "./frontend.Dockerfile" 9 | context: "../../" 10 | ports: 11 | - 3000 12 | backend: 13 | networks: 14 | - app_demo 15 | build: 16 | dockerfile: "./backend.Dockerfile" 17 | context: "../../" 18 | ports: 19 | - 5000 20 | db: 21 | image: mongo:7.0.2 22 | volumes: 23 | - type: volume 24 | source: dbdata 25 | target: /var/lib/mongodb 26 | networks: 27 | - app_demo 28 | ports: 29 | - 27017 30 | 31 | networks: 32 | app_demo: 33 | 34 | 35 | volumes: 36 | dbdata: -------------------------------------------------------------------------------- /dev-containers-for-easy-dev-setup/.devcontainer/node/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20 -------------------------------------------------------------------------------- /dev-containers-for-easy-dev-setup/.devcontainer/node/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-test", 3 | "build": { 4 | "dockerfile": "Dockerfile" 5 | } 6 | } -------------------------------------------------------------------------------- /dev-containers-for-easy-dev-setup/.devcontainer/ruby/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:alpine3.18 2 | 3 | RUN apk update && apk add --virtual build-dependencies build-base 4 | RUN gem install rails -------------------------------------------------------------------------------- /dev-containers-for-easy-dev-setup/.devcontainer/ruby/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Ruby-test", 3 | "build": { 4 | "dockerfile": "Dockerfile" 5 | } 6 | } -------------------------------------------------------------------------------- /dev-containers-for-easy-dev-setup/backend.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9.18-alpine3.18 2 | 3 | RUN pip install flask 4 | 5 | COPY ./backend /backend 6 | 7 | WORKDIR /backend 8 | 9 | # we must specify the host as 0.0.0.0 otherwise cannot access outside container 10 | CMD ["flask","--app","/backend/server.py","run","--host","0.0.0.0"] -------------------------------------------------------------------------------- /dev-containers-for-easy-dev-setup/backend/server.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | 3 | app = Flask(__name__) 4 | 5 | @app.route("/") 6 | def hello_world(): 7 | return "

Hello, World!

" -------------------------------------------------------------------------------- /dev-containers-for-easy-dev-setup/docker-compose-manual.yaml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | 3 | services: 4 | frontend: 5 | networks: 6 | - app_demo 7 | build: 8 | dockerfile: "./frontend.Dockerfile" 9 | context: "." 10 | volumes: 11 | - type: bind 12 | source: ./frontend 13 | target: /frontend 14 | ports: 15 | - 3000:3000 16 | backend: 17 | networks: 18 | - app_demo 19 | build: 20 | dockerfile: "./backend.Dockerfile" 21 | context: "." 22 | ports: 23 | - 5000 24 | db: 25 | image: mongo:7.0.2 26 | volumes: 27 | - type: volume 28 | source: dbdata 29 | target: /var/lib/mongodb 30 | networks: 31 | - app_demo 32 | ports: 33 | - 27017 34 | 35 | networks: 36 | app_demo: 37 | 38 | 39 | volumes: 40 | dbdata: -------------------------------------------------------------------------------- /dev-containers-for-easy-dev-setup/frontend.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20 2 | 3 | RUN npm install -g http-server -y 4 | 5 | COPY ./frontend /frontend 6 | 7 | WORKDIR /frontend 8 | 9 | CMD ["http-server",".","-p","3000"] -------------------------------------------------------------------------------- /dev-containers-for-easy-dev-setup/frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Demo App 7 | 8 | 9 |

Hello World!

10 | 11 | -------------------------------------------------------------------------------- /dev-containers-for-easy-dev-setup/ruby-test/test.rb: -------------------------------------------------------------------------------- 1 | puts "Hello Dev-container World!" -------------------------------------------------------------------------------- /excalibur-tutorial/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | test/images/actual.png 7 | test/images/diff-*.png 8 | /test-results/ 9 | /playwright-report/ 10 | /blob-report/ 11 | /playwright/.cache/ 12 | public/assets/* -------------------------------------------------------------------------------- /excalibur-tutorial/Readme.md: -------------------------------------------------------------------------------- 1 | # Excalibur JS tutorial 2 | 3 | Code for game development with excalibur JS tutorial. -------------------------------------------------------------------------------- /excalibur-tutorial/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YJDoc2/LogRocket-Blog-Code/e305aa008c0de324590c34c58fe391e313312aaf/excalibur-tutorial/favicon.ico -------------------------------------------------------------------------------- /excalibur-tutorial/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Excalibur + Vite 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /excalibur-tutorial/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "excalibur-tutorial", 3 | "version": "0.0.0", 4 | "description": "", 5 | "main": "src/main.ts", 6 | "scripts": { 7 | "dev": "vite", 8 | "start": "vite", 9 | "build": "tsc && vite build", 10 | "serve": "vite preview", 11 | "test": "npm run build && npx playwright test", 12 | "test:integration-update": "npx playwright test --update-snapshots" 13 | }, 14 | "repository": {}, 15 | "keywords": [ 16 | "excalibur", 17 | "excaliburjs", 18 | "vite", 19 | "game-engine" 20 | ], 21 | "author": "", 22 | "license": "", 23 | "bugs": {}, 24 | "homepage": "", 25 | "dependencies": { 26 | "@excaliburjs/plugin-tiled": "0.30.2", 27 | "excalibur": "0.30.3" 28 | }, 29 | "devDependencies": { 30 | "@playwright/test": "^1.49.1", 31 | "@types/node": "^22.10.2", 32 | "typescript": "5.7.3", 33 | "vite": "6.1.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /excalibur-tutorial/public/images/sword.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YJDoc2/LogRocket-Blog-Code/e305aa008c0de324590c34c58fe391e313312aaf/excalibur-tutorial/public/images/sword.png -------------------------------------------------------------------------------- /excalibur-tutorial/src/cameraStrategy.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BoundingBox, 3 | Camera, 4 | CameraStrategy, 5 | Engine, 6 | vec, 7 | Vector, 8 | } from "excalibur"; 9 | import { Player } from "./player"; 10 | 11 | export class BoundingBoxAroundActor implements CameraStrategy { 12 | target: Player; 13 | bounds: BoundingBox; 14 | constructor(target: Player, box: BoundingBox) { 15 | this.target = target; 16 | this.bounds = box; 17 | } 18 | action( 19 | target: Player, 20 | camera: Camera, 21 | engine: Engine, 22 | elapsed: number 23 | ): Vector { 24 | let pos = target.center; 25 | const focus = camera.getFocus(); 26 | let focusX = focus.x; 27 | let focusY = focus.y; 28 | let box = this.bounds; 29 | if (pos.x < box.left + engine.halfDrawWidth) { 30 | focusX = box.left + engine.halfDrawWidth; 31 | } else if (pos.x > box.right - engine.halfDrawWidth) { 32 | focusX = box.right - engine.halfDrawWidth; 33 | } else { 34 | focusX = pos.x; 35 | } 36 | 37 | if (pos.y < box.top + engine.halfDrawHeight) { 38 | focusY = box.top + engine.halfDrawHeight; 39 | } else if (pos.y > box.bottom - engine.halfDrawHeight) { 40 | focusY = box.bottom - engine.halfDrawHeight; 41 | } else { 42 | focusY = pos.y; 43 | } 44 | 45 | return vec(focusX, focusY); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /excalibur-tutorial/src/constant.ts: -------------------------------------------------------------------------------- 1 | export const PLAYER_WALK_DISTANCE = 5; 2 | export const PLAYER_ATTACK_RANGE = 80; 3 | export const GOBLIN_WALK_DISTANCE = 3; 4 | export const GOBLIN_DETECTION_DISTANCE = 250; 5 | export const GOBLIN_ATTACK_DISTANCE = 75; 6 | export const GOBLIN_ATTACK_COOLDOWN = 1.5 * 1000; 7 | -------------------------------------------------------------------------------- /excalibur-tutorial/src/files.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.png"; -------------------------------------------------------------------------------- /excalibur-tutorial/src/goblin.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Actor, 3 | Engine, 4 | SpriteSheet, 5 | vec, 6 | Vector, 7 | Animation, 8 | AnimationStrategy, 9 | range, 10 | EventEmitter, 11 | } from "excalibur"; 12 | import { Resources } from "./resources"; 13 | import { 14 | GOBLIN_ATTACK_COOLDOWN, 15 | GOBLIN_ATTACK_DISTANCE, 16 | GOBLIN_DETECTION_DISTANCE, 17 | GOBLIN_WALK_DISTANCE, 18 | PLAYER_ATTACK_RANGE, 19 | } from "./constant"; 20 | import { Player } from "./player"; 21 | 22 | export class Goblin extends Actor { 23 | idleAnimationRight: Animation; 24 | idleAnimationLeft: Animation; 25 | walkingAnimationLeft: Animation; 26 | walkingAnimationRight: Animation; 27 | attackAnimationRight: Animation; 28 | attackAnimationLeft: Animation; 29 | facingRight: boolean; 30 | events: EventEmitter; 31 | health = 3; 32 | attacking = false; 33 | lastAttack = 0; 34 | 35 | player: Player | null = null; 36 | constructor(startPos: Vector, events: EventEmitter) { 37 | super({ 38 | name: "Goblin", 39 | pos: startPos, 40 | width: 100, 41 | height: 100, 42 | z: 7, 43 | scale: vec(1, 1), 44 | }); 45 | this.events = events; 46 | this.facingRight = true; 47 | let spriteSheet = SpriteSheet.fromImageSource({ 48 | image: Resources.Goblin, 49 | grid: { 50 | rows: 8, 51 | columns: 6, 52 | spriteWidth: 192, 53 | spriteHeight: 192, 54 | }, 55 | }); 56 | this.idleAnimationRight = Animation.fromSpriteSheet( 57 | spriteSheet, 58 | range(0, 6), 59 | 100, 60 | AnimationStrategy.Loop 61 | ); 62 | 63 | this.idleAnimationLeft = this.idleAnimationRight.clone(); 64 | this.idleAnimationLeft.flipHorizontal = true; 65 | 66 | this.walkingAnimationRight = Animation.fromSpriteSheet( 67 | spriteSheet, 68 | range(7, 12), 69 | 100, 70 | AnimationStrategy.Loop 71 | ); 72 | this.walkingAnimationLeft = this.walkingAnimationRight.clone(); 73 | this.walkingAnimationLeft.flipHorizontal = true; 74 | 75 | this.attackAnimationRight = Animation.fromSpriteSheet( 76 | spriteSheet, 77 | range(13, 18), 78 | 100, 79 | AnimationStrategy.Freeze 80 | ); 81 | this.attackAnimationRight.events.on("end", (a) => { 82 | this.attacking = false; 83 | }); 84 | this.attackAnimationLeft = this.attackAnimationRight.clone(); 85 | this.attackAnimationLeft.flipHorizontal = true; 86 | this.attackAnimationLeft.events.on("end", (a) => { 87 | this.attacking = false; 88 | }); 89 | } 90 | onInitialize(engine: Engine): void { 91 | this.graphics.use(this.idleAnimationRight); 92 | for (const a of engine.currentScene.actors) { 93 | if (a instanceof Player) { 94 | this.player = a; 95 | } 96 | } 97 | this.events.on("attack", ({ pos, right }) => { 98 | let diff = this.pos.sub(pos).magnitude; 99 | if (diff > PLAYER_ATTACK_RANGE) { 100 | return; 101 | } 102 | 103 | if ((pos.x < this.pos.x && right) || (pos.x > this.pos.x && !right)) { 104 | this.health -= 1; 105 | if (this.health <= 0) { 106 | this.kill(); 107 | } 108 | } 109 | }); 110 | } 111 | 112 | update(engine: Engine, elapsed: number) { 113 | super.update(engine, elapsed); 114 | 115 | let playerPos = this.player?.pos || vec(Infinity, Infinity); 116 | let diff = this.pos.sub(playerPos); 117 | let dist = diff.magnitude; 118 | let now = Date.now(); 119 | 120 | if (dist < GOBLIN_DETECTION_DISTANCE && dist > GOBLIN_ATTACK_DISTANCE) { 121 | let step = diff.normalize().negate().scale(GOBLIN_WALK_DISTANCE); 122 | this.pos = this.pos.add(step); 123 | if (step.x < 0) { 124 | this.facingRight = false; 125 | this.graphics.use(this.walkingAnimationLeft); 126 | } else { 127 | this.facingRight = true; 128 | this.graphics.use(this.walkingAnimationRight); 129 | } 130 | this.attacking = false; 131 | } else if ( 132 | dist < GOBLIN_ATTACK_DISTANCE && 133 | now - this.lastAttack > GOBLIN_ATTACK_COOLDOWN 134 | ) { 135 | if (!this.attacking) { 136 | this.attacking = true; 137 | this.attackAnimationLeft.reset(); 138 | this.attackAnimationRight.reset(); 139 | if (this.facingRight) { 140 | this.graphics.use(this.attackAnimationRight); 141 | } else { 142 | this.graphics.use(this.attackAnimationLeft); 143 | } 144 | this.events.emit("enemy-attack", {}); 145 | this.lastAttack = now; 146 | } 147 | } else if (!this.attacking) { 148 | if (this.facingRight) { 149 | this.graphics.use(this.idleAnimationRight); 150 | } else { 151 | this.graphics.use(this.idleAnimationLeft); 152 | } 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /excalibur-tutorial/src/level.ts: -------------------------------------------------------------------------------- 1 | import { TiledResource } from "@excaliburjs/plugin-tiled"; 2 | import { Scene, SceneActivationContext } from "excalibur"; 3 | 4 | export class Level extends Scene { 5 | name: string; 6 | map: TiledResource; 7 | constructor(name: string, map: TiledResource) { 8 | super(); 9 | this.name = name; 10 | this.map = map; 11 | } 12 | 13 | onActivate(context: SceneActivationContext): void { 14 | this.map.addToScene(this); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /excalibur-tutorial/src/levelIcon.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Actor, 3 | Color, 4 | Engine, 5 | Rectangle, 6 | Vector, 7 | Text, 8 | Font, 9 | FontUnit, 10 | GraphicsGroup, 11 | vec, 12 | } from "excalibur"; 13 | 14 | export class LevelIcon extends Actor { 15 | label: string; 16 | constructor(cb: Function, pos: Vector, label: string) { 17 | super({ 18 | name: "LevelIcon", 19 | pos: pos, 20 | width: 100, 21 | height: 100, 22 | }); 23 | this.label = label; 24 | this.on("pointerdown", () => { 25 | cb(); 26 | }); 27 | } 28 | onInitialize(engine: Engine): void { 29 | const square = new Rectangle({ 30 | width: 75, 31 | height: 75, 32 | color: Color.Magenta, 33 | }); 34 | const title = new Text({ 35 | text: this.label, 36 | font: new Font({ 37 | family: "impact", 38 | size: 16, 39 | unit: FontUnit.Px, 40 | }), 41 | }); 42 | let group = new GraphicsGroup({ 43 | members: [ 44 | { 45 | graphic: square, 46 | offset: vec(0, 0), 47 | }, 48 | { 49 | graphic: title, 50 | offset: vec(7, 25), 51 | }, 52 | ], 53 | }); 54 | this.graphics.add(group); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /excalibur-tutorial/src/levelSelector.ts: -------------------------------------------------------------------------------- 1 | import { Engine, Scene, vec } from "excalibur"; 2 | import { LevelIcon } from "./levelIcon"; 3 | 4 | export class LevelSelector extends Scene { 5 | override onInitialize(engine: Engine): void { 6 | // Scene.onInitialize is where we recommend you perform the composition for your game 7 | let l1 = new LevelIcon( 8 | () => { 9 | engine.goToScene('level1'); 10 | }, 11 | vec(150, 150), 12 | "Level 1" 13 | ); 14 | let l2 = new LevelIcon( 15 | () => { 16 | console.log("clicked level 2"); 17 | }, 18 | vec(250, 150), 19 | "Level 2" 20 | ); 21 | this.add(l1); 22 | this.add(l2); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /excalibur-tutorial/src/main.ts: -------------------------------------------------------------------------------- 1 | import { DisplayMode, Engine } from "excalibur"; 2 | import { Level1Map, loader } from "./resources"; 3 | 4 | import { LevelSelector } from "./levelSelector"; 5 | import { Level } from "./level"; 6 | 7 | // Goal is to keep main.ts small and just enough to configure the engine 8 | const game = new Engine({ 9 | width: 800, // Logical width and height in game pixels 10 | height: 600, 11 | displayMode: DisplayMode.FitScreenAndFill, // Display mode tells excalibur how to fill the window 12 | pixelArt: true, // pixelArt will turn on the correct settings to render pixel art without jaggies or shimmering artifacts 13 | scenes: { 14 | levelSelector: LevelSelector, 15 | level1: new Level("Level 1", Level1Map), 16 | }, 17 | // physics: { 18 | // solver: SolverStrategy.Realistic, 19 | // substep: 5 // Sub step the physics simulation for more robust simulations 20 | // }, 21 | // fixedUpdateTimestep: 16 // Turn on fixed update timestep when consistent physic simulation is important 22 | }); 23 | 24 | game.start("levelSelector", { 25 | loader, 26 | }); 27 | -------------------------------------------------------------------------------- /excalibur-tutorial/src/player.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Actor, 3 | Engine, 4 | Keys, 5 | SpriteSheet, 6 | vec, 7 | Vector, 8 | Animation, 9 | AnimationStrategy, 10 | range, 11 | CollisionType, 12 | BoundingBox, 13 | EventEmitter, 14 | } from "excalibur"; 15 | import { Resources } from "./resources"; 16 | import { PLAYER_WALK_DISTANCE } from "./constant"; 17 | import { BoundingBoxAroundActor } from "./cameraStrategy"; 18 | 19 | export class Player extends Actor { 20 | idleAnimationRight: Animation; 21 | idleAnimationLeft: Animation; 22 | walkingAnimationLeft: Animation; 23 | walkingAnimationRight: Animation; 24 | attackAnimationLeft: Animation; 25 | attackAnimationRight: Animation; 26 | facingRight: boolean; 27 | events: EventEmitter; 28 | attacking: boolean = false; 29 | health = 10; 30 | constructor(startPos: Vector, events: EventEmitter) { 31 | super({ 32 | name: "Player", 33 | pos: startPos, 34 | width: 100, 35 | height: 100, 36 | z: 10, 37 | collisionType: CollisionType.Active, 38 | scale: vec(1, 1), 39 | }); 40 | this.events = events; 41 | this.facingRight = true; 42 | let spriteSheet = SpriteSheet.fromImageSource({ 43 | image: Resources.Knight, 44 | grid: { 45 | rows: 8, 46 | columns: 6, 47 | spriteWidth: 192, 48 | spriteHeight: 192, 49 | }, 50 | }); 51 | this.idleAnimationRight = Animation.fromSpriteSheet( 52 | spriteSheet, 53 | range(0, 5), 54 | 100, 55 | AnimationStrategy.Loop 56 | ); 57 | 58 | this.idleAnimationLeft = this.idleAnimationRight.clone(); 59 | this.idleAnimationLeft.flipHorizontal = true; 60 | 61 | this.walkingAnimationRight = Animation.fromSpriteSheet( 62 | spriteSheet, 63 | range(6, 11), 64 | 100, 65 | AnimationStrategy.Loop 66 | ); 67 | this.walkingAnimationLeft = this.walkingAnimationRight.clone(); 68 | this.walkingAnimationLeft.flipHorizontal = true; 69 | 70 | this.attackAnimationRight = Animation.fromSpriteSheet( 71 | spriteSheet, 72 | range(12, 17), 73 | 100, 74 | AnimationStrategy.Freeze 75 | ); 76 | this.attackAnimationRight.events.on("end", (a) => { 77 | this.events.emit("attack", { pos: this.pos, right: this.facingRight }); 78 | this.attacking = false; 79 | }); 80 | this.attackAnimationLeft = this.attackAnimationRight.clone(); 81 | this.attackAnimationLeft.flipHorizontal = true; 82 | this.attackAnimationLeft.events.on("end", (a) => { 83 | this.events.emit("attack", { pos: this.pos, right: this.facingRight }); 84 | this.attacking = false; 85 | }); 86 | } 87 | onInitialize(engine: Engine): void { 88 | let boundingBox = new BoundingBox( 89 | 0, 90 | 0, 91 | engine.currentScene.tileMaps[0].width, 92 | engine.currentScene.tileMaps[0].height 93 | ); 94 | engine.currentScene.camera.addStrategy( 95 | new BoundingBoxAroundActor(this, boundingBox) 96 | ); 97 | this.graphics.use(this.idleAnimationRight); 98 | this.events.on("enemy-attack", () => { 99 | this.health -= 1; 100 | if (this.health <= 0) { 101 | this.kill(); 102 | } 103 | }); 104 | } 105 | 106 | update(engine: Engine, elapsed: number) { 107 | super.update(engine, elapsed); 108 | 109 | let updateVector = null; 110 | let idle = false; 111 | 112 | let attacking = this.attacking; 113 | 114 | if (engine.input.keyboard.isHeld(Keys.ArrowRight) && !attacking) { 115 | this.facingRight = true; 116 | updateVector = vec(PLAYER_WALK_DISTANCE, 0); 117 | } else if (engine.input.keyboard.isHeld(Keys.ArrowLeft) && !attacking) { 118 | this.facingRight = false; 119 | updateVector = vec(-PLAYER_WALK_DISTANCE, 0); 120 | } else if (engine.input.keyboard.isHeld(Keys.ArrowUp) && !attacking) { 121 | updateVector = vec(0, -PLAYER_WALK_DISTANCE); 122 | } else if (engine.input.keyboard.isHeld(Keys.ArrowDown) && !attacking) { 123 | updateVector = vec(0, PLAYER_WALK_DISTANCE); 124 | } else { 125 | updateVector = vec(0, 0); 126 | idle = true; 127 | } 128 | this.pos = this.pos.add(updateVector); 129 | if (!attacking) { 130 | if (engine.input.keyboard.wasPressed(Keys.Space)) { 131 | this.attacking = true; 132 | this.attackAnimationLeft.reset(); 133 | this.attackAnimationRight.reset(); 134 | if (this.facingRight) { 135 | this.graphics.use(this.attackAnimationRight); 136 | } else { 137 | this.graphics.use(this.attackAnimationLeft); 138 | } 139 | } else if (idle) { 140 | if (this.facingRight) { 141 | this.graphics.use(this.idleAnimationRight); 142 | } else { 143 | this.graphics.use(this.idleAnimationLeft); 144 | } 145 | } else { 146 | if (this.facingRight) { 147 | this.graphics.use(this.walkingAnimationRight); 148 | } else { 149 | this.graphics.use(this.walkingAnimationLeft); 150 | } 151 | } 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /excalibur-tutorial/src/resources.ts: -------------------------------------------------------------------------------- 1 | import { FactoryProps, TiledResource } from "@excaliburjs/plugin-tiled"; 2 | import { ImageSource, Loader, vec, EventEmitter } from "excalibur"; 3 | import { Player } from "./player"; 4 | import { Goblin } from "./goblin"; 5 | 6 | const eventEmitter = new EventEmitter(); 7 | 8 | // It is convenient to put your resources in one place 9 | export const Resources = { 10 | Sword: new ImageSource("./images/sword.png"), // Vite public/ directory serves the root images 11 | Knight: new ImageSource( 12 | "./assets/Factions/Knights/Troops/Warrior/Blue/Warrior_Blue.png" 13 | ), 14 | Goblin: new ImageSource( 15 | "./assets/Factions/Goblins/Troops/Torch/Blue/Torch_Blue.png" 16 | ), 17 | } as const; // the 'as const' is a neat typescript trick to get strong typing on your resources. 18 | // So when you type Resources.Sword -> ImageSource 19 | 20 | // We build a loader and add all of our resources to the boot loader 21 | // You can build your own loader by extending DefaultLoader 22 | export const loader = new Loader(); 23 | for (const res of Object.values(Resources)) { 24 | loader.addResource(res); 25 | } 26 | 27 | export const Level1Map = new TiledResource("./assets/level1.tmx", { 28 | useMapBackgroundColor: true, 29 | entityClassNameFactories: { 30 | Player: (props: FactoryProps) => { 31 | return new Player(vec(props.worldPos.x, props.worldPos.y), eventEmitter); 32 | }, 33 | Goblin: (props: FactoryProps) => { 34 | return new Goblin(vec(props.worldPos.x, props.worldPos.y), eventEmitter); 35 | }, 36 | }, 37 | }); 38 | loader.addResource(Level1Map); 39 | -------------------------------------------------------------------------------- /excalibur-tutorial/src/style.css: -------------------------------------------------------------------------------- 1 | @media (prefers-color-scheme: dark) { 2 | body { 3 | background-color: black; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /excalibur-tutorial/src/tiledLevel.ts: -------------------------------------------------------------------------------- 1 | import { Scene } from "excalibur"; 2 | 3 | export class TiledLevel extends Scene { 4 | constructor() { 5 | super(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /excalibur-tutorial/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /excalibur-tutorial/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ESNext", "DOM"], 7 | "moduleResolution": "Node", 8 | "strict": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "resolveJsonModule": true, 12 | "esModuleInterop": true, 13 | "noEmit": true, 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": false, 16 | "noImplicitReturns": true 17 | }, 18 | "include": ["./src"] 19 | } 20 | -------------------------------------------------------------------------------- /excalibur-tutorial/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | 3 | // if you use tiled maps 4 | // there is a collision between react w/ typescript .tsx 5 | // and tiled tileset files .tsx 6 | // this forces vite to not interpret tsx as react 7 | const tiledPlugin = () => { 8 | return { 9 | name: 'tiled-tileset-plugin', 10 | resolveId: { 11 | order: 'pre', 12 | handler(sourceId, importer, options) { 13 | if (!sourceId.endsWith(".tsx")) return; 14 | return { id: 'tileset:' + sourceId, external: 'relative' } 15 | } 16 | } 17 | }; 18 | } 19 | 20 | export default defineConfig({ 21 | base: './', // optionally give a base path, useful for itch.io to serve relative instead of the default absolut 22 | plugins: [tiledPlugin()], // hint vite that tiled tilesets should be treated as external 23 | // currently excalibur plugins are commonjs 24 | // this forces vite to keep things from bundling ESM together with commonjs 25 | optimizeDeps: { 26 | exclude: ["excalibur"], 27 | }, 28 | build: { 29 | assetsInlineLimit: 0, // excalibur cannot handle inlined xml in prod mode 30 | sourcemap: true, 31 | // Vite uses rollup currently for prod builds so a separate config is needed 32 | // to keep vite from bundling ESM together with commonjs 33 | rollupOptions: { 34 | output: { 35 | format: 'umd' 36 | } 37 | } 38 | } 39 | }); 40 | -------------------------------------------------------------------------------- /generics-in-rust/.gitignore: -------------------------------------------------------------------------------- 1 | **/target/ 2 | -------------------------------------------------------------------------------- /generics-in-rust/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 = "generics-in-rust" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /generics-in-rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "generics-in-rust" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /generics-in-rust/Readme.md: -------------------------------------------------------------------------------- 1 | # Understanding Rust generics and how to use them 2 | 3 | --- 4 | 5 | Blog link : [https://blog.logrocket.com/understanding-rust-generics-how-use/](https://blog.logrocket.com/understanding-rust-generics-how-use/) 6 | -------------------------------------------------------------------------------- /generics-in-rust/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | struct Low; 4 | struct Medium; 5 | struct High; 6 | 7 | struct Heater { 8 | state: PhantomData, 9 | } 10 | 11 | impl Heater { 12 | fn turn_to_medium(self) -> Heater { 13 | Heater { state: PhantomData } 14 | } 15 | } 16 | 17 | impl Heater {} 18 | 19 | fn only_for_medium_heater(h: &mut Heater) {} 20 | 21 | struct Wrapper { 22 | data: DataType, 23 | } 24 | 25 | struct Ref<'a> { 26 | reference: &'a u8, 27 | } 28 | 29 | fn sort(a: Sortable, b: Sortable) -> bool { 30 | a < b 31 | } 32 | fn sort2(a: T, b: T) -> bool 33 | where 34 | T: Ord + Eq, 35 | { 36 | a < b 37 | } 38 | 39 | fn return_reference<'a, 'b>(in1: &'a [usize], in2: &'b [usize]) -> &'a usize { 40 | &in1[0] 41 | } 42 | 43 | fn return_reference2<'a, 'b: 'a>(in1: &'a [usize], in2: &'b [usize]) -> &'a usize { 44 | &in2[0] 45 | } 46 | 47 | fn main() { 48 | println!("Hello, world!"); 49 | let c: Vec = [1, 2, 3].into_iter().collect(); 50 | let c: Vec<_> = [1, 2, 3].into_iter().collect(); 51 | let d1 = Wrapper { data: 5 }; 52 | let d2 = Wrapper { 53 | data: "data".to_owned(), 54 | }; 55 | 56 | let t1: Heater = Heater { state: PhantomData }; 57 | } 58 | -------------------------------------------------------------------------------- /javascript-parser-generators/Readme.md: -------------------------------------------------------------------------------- 1 | Code for post Javascript parser generators. 2 | 3 | Link will be added after post publishing. 4 | -------------------------------------------------------------------------------- /javascript-parser-generators/chevrotain.mjs: -------------------------------------------------------------------------------- 1 | import { createToken, Lexer, EmbeddedActionsParser } from "chevrotain"; 2 | const nameKey = createToken({ name: "nameKey", pattern: /name/ }); 3 | const classKey = createToken({ name: "classKey", pattern: /class/ }); 4 | const irandKey = createToken({ name: "irandKey", pattern: /item_randomness/ }); 5 | const colon = createToken({ name: "colon", pattern: /:/ }); 6 | const whiteSpace = createToken({ 7 | name: "WhiteSpace", 8 | pattern: /\s+/, 9 | group: Lexer.SKIPPED, 10 | }); 11 | const nameString = createToken({ name: "nameString", pattern: /[a-zA-Z]+/ }); 12 | const classString = createToken({ 13 | name: "classString", 14 | pattern: /human|dwarf|wizard/i, 15 | }); 16 | const irandString = createToken({ 17 | name: "randString", 18 | pattern: /very low|low|medium|high|very high/i, 19 | }); 20 | 21 | const allTokens = [ 22 | nameKey, 23 | classKey, 24 | classString, 25 | irandKey, 26 | irandString, 27 | whiteSpace, 28 | colon, 29 | nameString, 30 | ]; 31 | 32 | const lexer = new Lexer(allTokens); 33 | 34 | class ConfigParser extends EmbeddedActionsParser { 35 | constructor() { 36 | super(allTokens); 37 | 38 | const $ = this; 39 | 40 | $.RULE("config", () => { 41 | return $.SUBRULE($.configKeys); 42 | }); 43 | 44 | $.RULE("configKeys", () => { 45 | let obj = {}; 46 | $.MANY(() => { 47 | $.OR([ 48 | { 49 | ALT: () => { 50 | obj.name = $.SUBRULE($.characterName); 51 | }, 52 | }, 53 | { 54 | ALT: () => { 55 | obj.class = $.SUBRULE($.characterClass); 56 | }, 57 | }, 58 | { 59 | ALT: () => { 60 | obj.item_randomness = $.SUBRULE($.itemRandomness); 61 | }, 62 | }, 63 | ]); 64 | }); 65 | return obj; 66 | }); 67 | 68 | $.RULE("characterName", () => { 69 | $.CONSUME(nameKey); 70 | $.CONSUME(colon); 71 | let name = $.CONSUME(nameString).image; 72 | return name; 73 | }); 74 | $.RULE("characterClass", () => { 75 | $.CONSUME(classKey); 76 | $.CONSUME(colon); 77 | let cls = $.CONSUME(classString).image.toLowerCase(); 78 | return cls; 79 | }); 80 | $.RULE("itemRandomness", () => { 81 | $.CONSUME(irandKey); 82 | $.CONSUME(colon); 83 | let temp = $.CONSUME(irandString).image.toLowerCase(); 84 | 85 | let bounds = { 86 | "very low": [0.05, 0.2], 87 | low: [0.2, 0.4], 88 | medium: [0.4, 0.6], 89 | high: [0.6, 0.8], 90 | "very high": [0.8, 1], 91 | }[temp]; 92 | let rand = Math.random(); 93 | return $.ACTION(() => bounds[0] + (bounds[1] - bounds[0]) * rand); 94 | }); 95 | 96 | // very important to call this after all the rules have been defined. 97 | // otherwise the parser may not work correctly as it will lack information 98 | // derived during the self analysis phase. 99 | this.performSelfAnalysis(); 100 | } 101 | } 102 | 103 | const parser = new ConfigParser(); 104 | 105 | // wrapping it all together 106 | const lexResult = lexer.tokenize( 107 | "name: Rincewind\nclass: Wizard\nitem_randomness: very high\n", 108 | ); 109 | // setting a new input will RESET the parser instance's state. 110 | parser.input = lexResult.tokens; 111 | // any top level rule may be used as an entry point 112 | const value = parser.config(); 113 | console.log(value); 114 | -------------------------------------------------------------------------------- /javascript-parser-generators/grammar.js: -------------------------------------------------------------------------------- 1 | // @generated by Peggy 4.0.2. 2 | // 3 | // https://peggyjs.org/ 4 | 5 | "use strict"; 6 | 7 | 8 | 9 | // const { characterClass} = require('../character.js') 10 | 11 | function peg$subclass(child, parent) { 12 | function C() { this.constructor = child; } 13 | C.prototype = parent.prototype; 14 | child.prototype = new C(); 15 | } 16 | 17 | function peg$SyntaxError(message, expected, found, location) { 18 | var self = Error.call(this, message); 19 | // istanbul ignore next Check is a necessary evil to support older environments 20 | if (Object.setPrototypeOf) { 21 | Object.setPrototypeOf(self, peg$SyntaxError.prototype); 22 | } 23 | self.expected = expected; 24 | self.found = found; 25 | self.location = location; 26 | self.name = "SyntaxError"; 27 | return self; 28 | } 29 | 30 | peg$subclass(peg$SyntaxError, Error); 31 | 32 | function peg$padEnd(str, targetLength, padString) { 33 | padString = padString || " "; 34 | if (str.length > targetLength) { return str; } 35 | targetLength -= str.length; 36 | padString += padString.repeat(targetLength); 37 | return str + padString.slice(0, targetLength); 38 | } 39 | 40 | peg$SyntaxError.prototype.format = function(sources) { 41 | var str = "Error: " + this.message; 42 | if (this.location) { 43 | var src = null; 44 | var k; 45 | for (k = 0; k < sources.length; k++) { 46 | if (sources[k].source === this.location.source) { 47 | src = sources[k].text.split(/\r\n|\n|\r/g); 48 | break; 49 | } 50 | } 51 | var s = this.location.start; 52 | var offset_s = (this.location.source && (typeof this.location.source.offset === "function")) 53 | ? this.location.source.offset(s) 54 | : s; 55 | var loc = this.location.source + ":" + offset_s.line + ":" + offset_s.column; 56 | if (src) { 57 | var e = this.location.end; 58 | var filler = peg$padEnd("", offset_s.line.toString().length, ' '); 59 | var line = src[s.line - 1]; 60 | var last = s.line === e.line ? e.column : line.length + 1; 61 | var hatLen = (last - s.column) || 1; 62 | str += "\n --> " + loc + "\n" 63 | + filler + " |\n" 64 | + offset_s.line + " | " + line + "\n" 65 | + filler + " | " + peg$padEnd("", s.column - 1, ' ') 66 | + peg$padEnd("", hatLen, "^"); 67 | } else { 68 | str += "\n at " + loc; 69 | } 70 | } 71 | return str; 72 | }; 73 | 74 | peg$SyntaxError.buildMessage = function(expected, found) { 75 | var DESCRIBE_EXPECTATION_FNS = { 76 | literal: function(expectation) { 77 | return "\"" + literalEscape(expectation.text) + "\""; 78 | }, 79 | 80 | class: function(expectation) { 81 | var escapedParts = expectation.parts.map(function(part) { 82 | return Array.isArray(part) 83 | ? classEscape(part[0]) + "-" + classEscape(part[1]) 84 | : classEscape(part); 85 | }); 86 | 87 | return "[" + (expectation.inverted ? "^" : "") + escapedParts.join("") + "]"; 88 | }, 89 | 90 | any: function() { 91 | return "any character"; 92 | }, 93 | 94 | end: function() { 95 | return "end of input"; 96 | }, 97 | 98 | other: function(expectation) { 99 | return expectation.description; 100 | } 101 | }; 102 | 103 | function hex(ch) { 104 | return ch.charCodeAt(0).toString(16).toUpperCase(); 105 | } 106 | 107 | function literalEscape(s) { 108 | return s 109 | .replace(/\\/g, "\\\\") 110 | .replace(/"/g, "\\\"") 111 | .replace(/\0/g, "\\0") 112 | .replace(/\t/g, "\\t") 113 | .replace(/\n/g, "\\n") 114 | .replace(/\r/g, "\\r") 115 | .replace(/[\x00-\x0F]/g, function(ch) { return "\\x0" + hex(ch); }) 116 | .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return "\\x" + hex(ch); }); 117 | } 118 | 119 | function classEscape(s) { 120 | return s 121 | .replace(/\\/g, "\\\\") 122 | .replace(/\]/g, "\\]") 123 | .replace(/\^/g, "\\^") 124 | .replace(/-/g, "\\-") 125 | .replace(/\0/g, "\\0") 126 | .replace(/\t/g, "\\t") 127 | .replace(/\n/g, "\\n") 128 | .replace(/\r/g, "\\r") 129 | .replace(/[\x00-\x0F]/g, function(ch) { return "\\x0" + hex(ch); }) 130 | .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return "\\x" + hex(ch); }); 131 | } 132 | 133 | function describeExpectation(expectation) { 134 | return DESCRIBE_EXPECTATION_FNS[expectation.type](expectation); 135 | } 136 | 137 | function describeExpected(expected) { 138 | var descriptions = expected.map(describeExpectation); 139 | var i, j; 140 | 141 | descriptions.sort(); 142 | 143 | if (descriptions.length > 0) { 144 | for (i = 1, j = 1; i < descriptions.length; i++) { 145 | if (descriptions[i - 1] !== descriptions[i]) { 146 | descriptions[j] = descriptions[i]; 147 | j++; 148 | } 149 | } 150 | descriptions.length = j; 151 | } 152 | 153 | switch (descriptions.length) { 154 | case 1: 155 | return descriptions[0]; 156 | 157 | case 2: 158 | return descriptions[0] + " or " + descriptions[1]; 159 | 160 | default: 161 | return descriptions.slice(0, -1).join(", ") 162 | + ", or " 163 | + descriptions[descriptions.length - 1]; 164 | } 165 | } 166 | 167 | function describeFound(found) { 168 | return found ? "\"" + literalEscape(found) + "\"" : "end of input"; 169 | } 170 | 171 | return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found."; 172 | }; 173 | 174 | function peg$parse(input, options) { 175 | options = options !== undefined ? options : {}; 176 | 177 | var peg$FAILED = {}; 178 | var peg$source = options.grammarSource; 179 | 180 | var peg$startRuleFunctions = { Config: peg$parseConfig }; 181 | var peg$startRuleFunction = peg$parseConfig; 182 | 183 | var peg$c0 = "name"; 184 | var peg$c1 = ":"; 185 | var peg$c2 = "class"; 186 | var peg$c3 = "item_randomness"; 187 | var peg$c4 = "wizard"; 188 | var peg$c5 = "dwarf"; 189 | var peg$c6 = "human"; 190 | var peg$c7 = "very low"; 191 | var peg$c8 = "low"; 192 | var peg$c9 = "medium"; 193 | var peg$c10 = "high"; 194 | var peg$c11 = "very high"; 195 | 196 | var peg$r0 = /^[a-zA-Z]/; 197 | var peg$r1 = /^[ \t\n\r]/; 198 | 199 | var peg$e0 = peg$otherExpectation("Name"); 200 | var peg$e1 = peg$literalExpectation("name", true); 201 | var peg$e2 = peg$literalExpectation(":", false); 202 | var peg$e3 = peg$classExpectation([["a", "z"], ["A", "Z"]], false, false); 203 | var peg$e4 = peg$literalExpectation("class", true); 204 | var peg$e5 = peg$literalExpectation("item_randomness", true); 205 | var peg$e6 = peg$literalExpectation("Wizard", true); 206 | var peg$e7 = peg$literalExpectation("Dwarf", true); 207 | var peg$e8 = peg$literalExpectation("Human", true); 208 | var peg$e9 = peg$literalExpectation("very low", true); 209 | var peg$e10 = peg$literalExpectation("low", true); 210 | var peg$e11 = peg$literalExpectation("medium", true); 211 | var peg$e12 = peg$literalExpectation("high", true); 212 | var peg$e13 = peg$literalExpectation("very high", true); 213 | var peg$e14 = peg$otherExpectation("whitespace"); 214 | var peg$e15 = peg$classExpectation([" ", "\t", "\n", "\r"], false, false); 215 | 216 | var peg$f0 = function() { return config; }; 217 | var peg$f1 = function(val) { 218 | config.name = val; 219 | }; 220 | var peg$f2 = function(cls) { 221 | // config.class = characterClass[cls]; 222 | config.class = cls 223 | }; 224 | var peg$f3 = function(rand) { 225 | let temp = rand.toLowerCase(); 226 | let bounds = { 227 | "very low" : [ 0.05 , 0.2 ], 228 | "low" : [ 0.2 , 0.4], 229 | "medium" : [ 0.4 , 0.6], 230 | "high" : [ 0.6 , 0.8], 231 | "very high" : [ 0.8 , 1 ] 232 | }[temp]; 233 | let r = Math.random(); 234 | config.itemRand = bounds[0]+(bounds[1]-bounds[0])*r 235 | }; 236 | var peg$currPos = options.peg$currPos | 0; 237 | var peg$savedPos = peg$currPos; 238 | var peg$posDetailsCache = [{ line: 1, column: 1 }]; 239 | var peg$maxFailPos = peg$currPos; 240 | var peg$maxFailExpected = options.peg$maxFailExpected || []; 241 | var peg$silentFails = options.peg$silentFails | 0; 242 | 243 | var peg$result; 244 | 245 | if (options.startRule) { 246 | if (!(options.startRule in peg$startRuleFunctions)) { 247 | throw new Error("Can't start parsing from rule \"" + options.startRule + "\"."); 248 | } 249 | 250 | peg$startRuleFunction = peg$startRuleFunctions[options.startRule]; 251 | } 252 | 253 | function text() { 254 | return input.substring(peg$savedPos, peg$currPos); 255 | } 256 | 257 | function offset() { 258 | return peg$savedPos; 259 | } 260 | 261 | function range() { 262 | return { 263 | source: peg$source, 264 | start: peg$savedPos, 265 | end: peg$currPos 266 | }; 267 | } 268 | 269 | function location() { 270 | return peg$computeLocation(peg$savedPos, peg$currPos); 271 | } 272 | 273 | function expected(description, location) { 274 | location = location !== undefined 275 | ? location 276 | : peg$computeLocation(peg$savedPos, peg$currPos); 277 | 278 | throw peg$buildStructuredError( 279 | [peg$otherExpectation(description)], 280 | input.substring(peg$savedPos, peg$currPos), 281 | location 282 | ); 283 | } 284 | 285 | function error(message, location) { 286 | location = location !== undefined 287 | ? location 288 | : peg$computeLocation(peg$savedPos, peg$currPos); 289 | 290 | throw peg$buildSimpleError(message, location); 291 | } 292 | 293 | function peg$literalExpectation(text, ignoreCase) { 294 | return { type: "literal", text: text, ignoreCase: ignoreCase }; 295 | } 296 | 297 | function peg$classExpectation(parts, inverted, ignoreCase) { 298 | return { type: "class", parts: parts, inverted: inverted, ignoreCase: ignoreCase }; 299 | } 300 | 301 | function peg$anyExpectation() { 302 | return { type: "any" }; 303 | } 304 | 305 | function peg$endExpectation() { 306 | return { type: "end" }; 307 | } 308 | 309 | function peg$otherExpectation(description) { 310 | return { type: "other", description: description }; 311 | } 312 | 313 | function peg$computePosDetails(pos) { 314 | var details = peg$posDetailsCache[pos]; 315 | var p; 316 | 317 | if (details) { 318 | return details; 319 | } else { 320 | if (pos >= peg$posDetailsCache.length) { 321 | p = peg$posDetailsCache.length - 1; 322 | } else { 323 | p = pos; 324 | while (!peg$posDetailsCache[--p]) {} 325 | } 326 | 327 | details = peg$posDetailsCache[p]; 328 | details = { 329 | line: details.line, 330 | column: details.column 331 | }; 332 | 333 | while (p < pos) { 334 | if (input.charCodeAt(p) === 10) { 335 | details.line++; 336 | details.column = 1; 337 | } else { 338 | details.column++; 339 | } 340 | 341 | p++; 342 | } 343 | 344 | peg$posDetailsCache[pos] = details; 345 | 346 | return details; 347 | } 348 | } 349 | 350 | function peg$computeLocation(startPos, endPos, offset) { 351 | var startPosDetails = peg$computePosDetails(startPos); 352 | var endPosDetails = peg$computePosDetails(endPos); 353 | 354 | var res = { 355 | source: peg$source, 356 | start: { 357 | offset: startPos, 358 | line: startPosDetails.line, 359 | column: startPosDetails.column 360 | }, 361 | end: { 362 | offset: endPos, 363 | line: endPosDetails.line, 364 | column: endPosDetails.column 365 | } 366 | }; 367 | if (offset && peg$source && (typeof peg$source.offset === "function")) { 368 | res.start = peg$source.offset(res.start); 369 | res.end = peg$source.offset(res.end); 370 | } 371 | return res; 372 | } 373 | 374 | function peg$fail(expected) { 375 | if (peg$currPos < peg$maxFailPos) { return; } 376 | 377 | if (peg$currPos > peg$maxFailPos) { 378 | peg$maxFailPos = peg$currPos; 379 | peg$maxFailExpected = []; 380 | } 381 | 382 | peg$maxFailExpected.push(expected); 383 | } 384 | 385 | function peg$buildSimpleError(message, location) { 386 | return new peg$SyntaxError(message, null, null, location); 387 | } 388 | 389 | function peg$buildStructuredError(expected, found, location) { 390 | return new peg$SyntaxError( 391 | peg$SyntaxError.buildMessage(expected, found), 392 | expected, 393 | found, 394 | location 395 | ); 396 | } 397 | 398 | function peg$parseConfig() { 399 | var s0, s1, s2, s3; 400 | 401 | s0 = peg$currPos; 402 | s1 = []; 403 | s2 = peg$parseCharacterName(); 404 | if (s2 === peg$FAILED) { 405 | s2 = peg$parseCharacterClass(); 406 | if (s2 === peg$FAILED) { 407 | s2 = peg$parseItemRandom(); 408 | } 409 | } 410 | while (s2 !== peg$FAILED) { 411 | s1.push(s2); 412 | s2 = peg$currPos; 413 | s3 = peg$parse_(); 414 | s3 = peg$parseCharacterName(); 415 | if (s3 === peg$FAILED) { 416 | s3 = peg$parseCharacterClass(); 417 | if (s3 === peg$FAILED) { 418 | s3 = peg$parseItemRandom(); 419 | } 420 | } 421 | if (s3 === peg$FAILED) { 422 | peg$currPos = s2; 423 | s2 = peg$FAILED; 424 | } else { 425 | s2 = s3; 426 | } 427 | } 428 | peg$savedPos = s0; 429 | s1 = peg$f0(); 430 | s0 = s1; 431 | 432 | return s0; 433 | } 434 | 435 | function peg$parseCharacterName() { 436 | var s0, s1, s2, s3, s4, s5, s6, s7; 437 | 438 | peg$silentFails++; 439 | s0 = peg$currPos; 440 | s1 = input.substr(peg$currPos, 4); 441 | if (s1.toLowerCase() === peg$c0) { 442 | peg$currPos += 4; 443 | } else { 444 | s1 = peg$FAILED; 445 | if (peg$silentFails === 0) { peg$fail(peg$e1); } 446 | } 447 | if (s1 !== peg$FAILED) { 448 | s2 = peg$parse_(); 449 | if (input.charCodeAt(peg$currPos) === 58) { 450 | s3 = peg$c1; 451 | peg$currPos++; 452 | } else { 453 | s3 = peg$FAILED; 454 | if (peg$silentFails === 0) { peg$fail(peg$e2); } 455 | } 456 | if (s3 !== peg$FAILED) { 457 | s4 = peg$parse_(); 458 | s5 = peg$currPos; 459 | s6 = []; 460 | s7 = input.charAt(peg$currPos); 461 | if (peg$r0.test(s7)) { 462 | peg$currPos++; 463 | } else { 464 | s7 = peg$FAILED; 465 | if (peg$silentFails === 0) { peg$fail(peg$e3); } 466 | } 467 | if (s7 !== peg$FAILED) { 468 | while (s7 !== peg$FAILED) { 469 | s6.push(s7); 470 | s7 = input.charAt(peg$currPos); 471 | if (peg$r0.test(s7)) { 472 | peg$currPos++; 473 | } else { 474 | s7 = peg$FAILED; 475 | if (peg$silentFails === 0) { peg$fail(peg$e3); } 476 | } 477 | } 478 | } else { 479 | s6 = peg$FAILED; 480 | } 481 | if (s6 !== peg$FAILED) { 482 | s5 = input.substring(s5, peg$currPos); 483 | } else { 484 | s5 = s6; 485 | } 486 | if (s5 !== peg$FAILED) { 487 | peg$savedPos = s0; 488 | s0 = peg$f1(s5); 489 | } else { 490 | peg$currPos = s0; 491 | s0 = peg$FAILED; 492 | } 493 | } else { 494 | peg$currPos = s0; 495 | s0 = peg$FAILED; 496 | } 497 | } else { 498 | peg$currPos = s0; 499 | s0 = peg$FAILED; 500 | } 501 | peg$silentFails--; 502 | if (s0 === peg$FAILED) { 503 | s1 = peg$FAILED; 504 | if (peg$silentFails === 0) { peg$fail(peg$e0); } 505 | } 506 | 507 | return s0; 508 | } 509 | 510 | function peg$parseCharacterClass() { 511 | var s0, s1, s2, s3, s4, s5; 512 | 513 | s0 = peg$currPos; 514 | s1 = input.substr(peg$currPos, 5); 515 | if (s1.toLowerCase() === peg$c2) { 516 | peg$currPos += 5; 517 | } else { 518 | s1 = peg$FAILED; 519 | if (peg$silentFails === 0) { peg$fail(peg$e4); } 520 | } 521 | if (s1 !== peg$FAILED) { 522 | s2 = peg$parse_(); 523 | if (input.charCodeAt(peg$currPos) === 58) { 524 | s3 = peg$c1; 525 | peg$currPos++; 526 | } else { 527 | s3 = peg$FAILED; 528 | if (peg$silentFails === 0) { peg$fail(peg$e2); } 529 | } 530 | if (s3 !== peg$FAILED) { 531 | s4 = peg$parse_(); 532 | s5 = peg$parseClass(); 533 | if (s5 !== peg$FAILED) { 534 | peg$savedPos = s0; 535 | s0 = peg$f2(s5); 536 | } else { 537 | peg$currPos = s0; 538 | s0 = peg$FAILED; 539 | } 540 | } else { 541 | peg$currPos = s0; 542 | s0 = peg$FAILED; 543 | } 544 | } else { 545 | peg$currPos = s0; 546 | s0 = peg$FAILED; 547 | } 548 | 549 | return s0; 550 | } 551 | 552 | function peg$parseItemRandom() { 553 | var s0, s1, s2, s3, s4, s5; 554 | 555 | s0 = peg$currPos; 556 | s1 = input.substr(peg$currPos, 15); 557 | if (s1.toLowerCase() === peg$c3) { 558 | peg$currPos += 15; 559 | } else { 560 | s1 = peg$FAILED; 561 | if (peg$silentFails === 0) { peg$fail(peg$e5); } 562 | } 563 | if (s1 !== peg$FAILED) { 564 | s2 = peg$parse_(); 565 | if (input.charCodeAt(peg$currPos) === 58) { 566 | s3 = peg$c1; 567 | peg$currPos++; 568 | } else { 569 | s3 = peg$FAILED; 570 | if (peg$silentFails === 0) { peg$fail(peg$e2); } 571 | } 572 | if (s3 !== peg$FAILED) { 573 | s4 = peg$parse_(); 574 | s5 = peg$parseRndVal(); 575 | if (s5 !== peg$FAILED) { 576 | peg$savedPos = s0; 577 | s0 = peg$f3(s5); 578 | } else { 579 | peg$currPos = s0; 580 | s0 = peg$FAILED; 581 | } 582 | } else { 583 | peg$currPos = s0; 584 | s0 = peg$FAILED; 585 | } 586 | } else { 587 | peg$currPos = s0; 588 | s0 = peg$FAILED; 589 | } 590 | 591 | return s0; 592 | } 593 | 594 | function peg$parseClass() { 595 | var s0; 596 | 597 | s0 = input.substr(peg$currPos, 6); 598 | if (s0.toLowerCase() === peg$c4) { 599 | peg$currPos += 6; 600 | } else { 601 | s0 = peg$FAILED; 602 | if (peg$silentFails === 0) { peg$fail(peg$e6); } 603 | } 604 | if (s0 === peg$FAILED) { 605 | s0 = input.substr(peg$currPos, 5); 606 | if (s0.toLowerCase() === peg$c5) { 607 | peg$currPos += 5; 608 | } else { 609 | s0 = peg$FAILED; 610 | if (peg$silentFails === 0) { peg$fail(peg$e7); } 611 | } 612 | if (s0 === peg$FAILED) { 613 | s0 = input.substr(peg$currPos, 5); 614 | if (s0.toLowerCase() === peg$c6) { 615 | peg$currPos += 5; 616 | } else { 617 | s0 = peg$FAILED; 618 | if (peg$silentFails === 0) { peg$fail(peg$e8); } 619 | } 620 | } 621 | } 622 | 623 | return s0; 624 | } 625 | 626 | function peg$parseRndVal() { 627 | var s0; 628 | 629 | s0 = input.substr(peg$currPos, 8); 630 | if (s0.toLowerCase() === peg$c7) { 631 | peg$currPos += 8; 632 | } else { 633 | s0 = peg$FAILED; 634 | if (peg$silentFails === 0) { peg$fail(peg$e9); } 635 | } 636 | if (s0 === peg$FAILED) { 637 | s0 = input.substr(peg$currPos, 3); 638 | if (s0.toLowerCase() === peg$c8) { 639 | peg$currPos += 3; 640 | } else { 641 | s0 = peg$FAILED; 642 | if (peg$silentFails === 0) { peg$fail(peg$e10); } 643 | } 644 | if (s0 === peg$FAILED) { 645 | s0 = input.substr(peg$currPos, 6); 646 | if (s0.toLowerCase() === peg$c9) { 647 | peg$currPos += 6; 648 | } else { 649 | s0 = peg$FAILED; 650 | if (peg$silentFails === 0) { peg$fail(peg$e11); } 651 | } 652 | if (s0 === peg$FAILED) { 653 | s0 = input.substr(peg$currPos, 4); 654 | if (s0.toLowerCase() === peg$c10) { 655 | peg$currPos += 4; 656 | } else { 657 | s0 = peg$FAILED; 658 | if (peg$silentFails === 0) { peg$fail(peg$e12); } 659 | } 660 | if (s0 === peg$FAILED) { 661 | s0 = input.substr(peg$currPos, 9); 662 | if (s0.toLowerCase() === peg$c11) { 663 | peg$currPos += 9; 664 | } else { 665 | s0 = peg$FAILED; 666 | if (peg$silentFails === 0) { peg$fail(peg$e13); } 667 | } 668 | } 669 | } 670 | } 671 | } 672 | 673 | return s0; 674 | } 675 | 676 | function peg$parse_() { 677 | var s0, s1; 678 | 679 | peg$silentFails++; 680 | s0 = []; 681 | s1 = input.charAt(peg$currPos); 682 | if (peg$r1.test(s1)) { 683 | peg$currPos++; 684 | } else { 685 | s1 = peg$FAILED; 686 | if (peg$silentFails === 0) { peg$fail(peg$e15); } 687 | } 688 | while (s1 !== peg$FAILED) { 689 | s0.push(s1); 690 | s1 = input.charAt(peg$currPos); 691 | if (peg$r1.test(s1)) { 692 | peg$currPos++; 693 | } else { 694 | s1 = peg$FAILED; 695 | if (peg$silentFails === 0) { peg$fail(peg$e15); } 696 | } 697 | } 698 | peg$silentFails--; 699 | s1 = peg$FAILED; 700 | if (peg$silentFails === 0) { peg$fail(peg$e14); } 701 | 702 | return s0; 703 | } 704 | 705 | 706 | const config = {}; 707 | 708 | peg$result = peg$startRuleFunction(); 709 | 710 | if (options.peg$library) { 711 | return /** @type {any} */ ({ 712 | peg$result, 713 | peg$currPos, 714 | peg$FAILED, 715 | peg$maxFailExpected, 716 | peg$maxFailPos 717 | }); 718 | } 719 | if (peg$result !== peg$FAILED && peg$currPos === input.length) { 720 | return peg$result; 721 | } else { 722 | if (peg$result !== peg$FAILED && peg$currPos < input.length) { 723 | peg$fail(peg$endExpectation()); 724 | } 725 | 726 | throw peg$buildStructuredError( 727 | peg$maxFailExpected, 728 | peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null, 729 | peg$maxFailPos < input.length 730 | ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1) 731 | : peg$computeLocation(peg$maxFailPos, peg$maxFailPos) 732 | ); 733 | } 734 | } 735 | 736 | module.exports = { 737 | StartRules: ["Config"], 738 | SyntaxError: peg$SyntaxError, 739 | parse: peg$parse 740 | }; 741 | -------------------------------------------------------------------------------- /javascript-parser-generators/grammar.pegjs: -------------------------------------------------------------------------------- 1 | {{ 2 | // const { characterClass} = require('../character.js') 3 | }} 4 | 5 | { 6 | const config = {}; 7 | } 8 | 9 | Config = (CharacterName / CharacterClass / ItemRandom)|.., _ | { return config; } 10 | 11 | CharacterName "Name" = "name"i _? ":" _? val:$[a-zA-Z]+{ 12 | config.name = val; 13 | } 14 | 15 | 16 | CharacterClass = "class"i _? ":" _? cls:Class { 17 | // config.class = characterClass[cls]; 18 | config.class = cls 19 | } 20 | 21 | ItemRandom = "item_randomness"i _? ":" _? rand:RndVal{ 22 | let temp = rand.toLowerCase(); 23 | let bounds = { 24 | "very low" : [ 0.05 , 0.2 ], 25 | "low" : [ 0.2 , 0.4], 26 | "medium" : [ 0.4 , 0.6], 27 | "high" : [ 0.6 , 0.8], 28 | "very high" : [ 0.8 , 1 ] 29 | }[temp]; 30 | let r = Math.random(); 31 | config.itemRand = bounds[0]+(bounds[1]-bounds[0])*r 32 | } 33 | 34 | Class = "Wizard"i / "Dwarf"i / "Human"i 35 | RndVal = "very low"i / "low"i / "medium"i / "high"i / "very high"i 36 | _ "whitespace" = [ \t\n\r]* 37 | -------------------------------------------------------------------------------- /javascript-parser-generators/ohm-example.js: -------------------------------------------------------------------------------- 1 | const ohm = require("ohm-js"); 2 | 3 | const configGrammer = ohm.grammar(String.raw` 4 | ConfigGrammer{ 5 | Config 6 | = ListOf< ConfigKey, ""> 7 | ConfigKey 8 | = characterName | CharacterClass | ItemRand 9 | 10 | characterName 11 | = "name" spaces ":" spaces letter+ 12 | CharacterClass 13 | = "class" ":" class 14 | ItemRand 15 | = "item_randomness" ":" randomness 16 | class 17 | = caseInsensitive<"Wizard"> | caseInsensitive<"Dwarf"> | caseInsensitive<"Human"> 18 | randomness 19 | = caseInsensitive<"very low"> | caseInsensitive<"low"> | caseInsensitive<"medium"> 20 | | caseInsensitive<"high"> | caseInsensitive<"very high"> 21 | } 22 | `); 23 | 24 | const userInput = String.raw` 25 | name: Rincewind 26 | class: wizard 27 | item_randomness: Very High 28 | `; 29 | const m = configGrammer.match(userInput.trim()); 30 | console.log(m.succeeded()); 31 | const semantics = configGrammer.createSemantics(); 32 | semantics.addOperation("parse", { 33 | Config(config) { 34 | const obj = {}; 35 | for (const c of config.asIteration().children) { 36 | let v = c.parse(); 37 | obj[v[0]] = v[1]; 38 | } 39 | return obj; 40 | }, 41 | characterName(_1, _2, _3, _4, name) { 42 | return ["name", name.sourceString]; 43 | }, 44 | CharacterClass(_1, _2, cls) { 45 | return ["class", cls.sourceString.toLowerCase()]; 46 | }, 47 | ItemRand(_1, _2, r) { 48 | let temp = r.sourceString.toLowerCase(); 49 | let bounds = { 50 | "very low": [0.05, 0.2], 51 | low: [0.2, 0.4], 52 | medium: [0.4, 0.6], 53 | high: [0.6, 0.8], 54 | "very high": [0.8, 1], 55 | }[temp]; 56 | let rand = Math.random(); 57 | return ["itemRand", bounds[0] + (bounds[1] - bounds[0]) * rand]; 58 | }, 59 | }); 60 | console.log(semantics(m).parse()); 61 | -------------------------------------------------------------------------------- /javascript-parser-generators/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "javascript-parser-generators", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "javascript-parser-generators", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "chevrotain": "^11.0.3", 13 | "ohm-js": "^17.1.0" 14 | } 15 | }, 16 | "node_modules/@chevrotain/cst-dts-gen": { 17 | "version": "11.0.3", 18 | "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz", 19 | "integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==", 20 | "dependencies": { 21 | "@chevrotain/gast": "11.0.3", 22 | "@chevrotain/types": "11.0.3", 23 | "lodash-es": "4.17.21" 24 | } 25 | }, 26 | "node_modules/@chevrotain/gast": { 27 | "version": "11.0.3", 28 | "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz", 29 | "integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==", 30 | "dependencies": { 31 | "@chevrotain/types": "11.0.3", 32 | "lodash-es": "4.17.21" 33 | } 34 | }, 35 | "node_modules/@chevrotain/regexp-to-ast": { 36 | "version": "11.0.3", 37 | "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz", 38 | "integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==" 39 | }, 40 | "node_modules/@chevrotain/types": { 41 | "version": "11.0.3", 42 | "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz", 43 | "integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==" 44 | }, 45 | "node_modules/@chevrotain/utils": { 46 | "version": "11.0.3", 47 | "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz", 48 | "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==" 49 | }, 50 | "node_modules/chevrotain": { 51 | "version": "11.0.3", 52 | "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", 53 | "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", 54 | "dependencies": { 55 | "@chevrotain/cst-dts-gen": "11.0.3", 56 | "@chevrotain/gast": "11.0.3", 57 | "@chevrotain/regexp-to-ast": "11.0.3", 58 | "@chevrotain/types": "11.0.3", 59 | "@chevrotain/utils": "11.0.3", 60 | "lodash-es": "4.17.21" 61 | } 62 | }, 63 | "node_modules/lodash-es": { 64 | "version": "4.17.21", 65 | "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", 66 | "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" 67 | }, 68 | "node_modules/ohm-js": { 69 | "version": "17.1.0", 70 | "resolved": "https://registry.npmjs.org/ohm-js/-/ohm-js-17.1.0.tgz", 71 | "integrity": "sha512-xc3B5dgAjTBQGHaH7B58M2Pmv6WvzrJ/3/7LeUzXNg0/sY3jQPdSd/S2SstppaleO77rifR1tyhdfFGNIwxf2Q==", 72 | "engines": { 73 | "node": ">=0.12.1" 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /javascript-parser-generators/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "javascript-parser-generators", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "ohm-example.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "chevrotain": "^11.0.3", 14 | "ohm-js": "^17.1.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /native-lazy-types/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 = "lazy_static" 7 | version = "1.5.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 10 | 11 | [[package]] 12 | name = "native_lazy" 13 | version = "0.1.0" 14 | dependencies = [ 15 | "lazy_static", 16 | "once_cell", 17 | ] 18 | 19 | [[package]] 20 | name = "once_cell" 21 | version = "1.20.0" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | checksum = "33ea5043e58958ee56f3e15a90aee535795cd7dfd319846288d93c5b57d85cbe" 24 | -------------------------------------------------------------------------------- /native-lazy-types/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "native_lazy" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | lazy_static = "1.5.0" 8 | once_cell = "1.20.0" 9 | -------------------------------------------------------------------------------- /native-lazy-types/README.md: -------------------------------------------------------------------------------- 1 | # How to use the lazy initialization pattern with Rust 1.80 2 | 3 | This is code used in examples in the LogRocket article How to use the lazy initialization pattern with Rust 1.80. 4 | -------------------------------------------------------------------------------- /native-lazy-types/src/lazy_static_example.rs: -------------------------------------------------------------------------------- 1 | use lazy_static::lazy_static; 2 | 3 | lazy_static! { 4 | static ref LOG_LEVEL: String = get_log_level(); 5 | } 6 | 7 | fn get_log_level() -> String { 8 | match std::env::var("LOG_LEVEL") { 9 | Ok(s) => s, 10 | Err(_) => "WARN".to_string(), 11 | } 12 | } 13 | 14 | pub fn _main() { 15 | println!("{}", *LOG_LEVEL); 16 | } 17 | -------------------------------------------------------------------------------- /native-lazy-types/src/main.rs: -------------------------------------------------------------------------------- 1 | mod lazy_static_example; 2 | mod native_lazy_example; 3 | mod native_once_example; 4 | mod once_cell_example; 5 | 6 | fn main() { 7 | lazy_static_example::_main(); 8 | once_cell_example::_main(); 9 | native_lazy_example::_main(); 10 | native_once_example::_main(); 11 | } 12 | -------------------------------------------------------------------------------- /native-lazy-types/src/native_lazy_example.rs: -------------------------------------------------------------------------------- 1 | use std::sync::LazyLock; 2 | 3 | static LOG_LEVEL: LazyLock = LazyLock::new(get_log_level); 4 | 5 | fn get_log_level() -> String { 6 | match std::env::var("LOG_LEVEL") { 7 | Ok(s) => s, 8 | Err(_) => "WARN".to_string(), 9 | } 10 | } 11 | 12 | pub fn _main() { 13 | println!("{}", *LOG_LEVEL); 14 | } 15 | -------------------------------------------------------------------------------- /native-lazy-types/src/native_once_example.rs: -------------------------------------------------------------------------------- 1 | use std::sync::OnceLock; 2 | 3 | static LOG_LEVEL: OnceLock = OnceLock::new(); 4 | 5 | fn get_log_level() -> String { 6 | match std::env::var("LOG_LEVEL") { 7 | Ok(s) => s, 8 | Err(_) => "WARN".to_string(), 9 | } 10 | } 11 | 12 | pub fn _main() { 13 | let log_level = LOG_LEVEL.get_or_init(get_log_level); 14 | println!("{}", log_level); 15 | } 16 | -------------------------------------------------------------------------------- /native-lazy-types/src/once_cell_example.rs: -------------------------------------------------------------------------------- 1 | use once_cell::sync::OnceCell; 2 | 3 | static LOG_LEVEL: OnceCell = OnceCell::new(); 4 | 5 | fn get_log_level() -> String { 6 | match std::env::var("LOG_LEVEL") { 7 | Ok(s) => s, 8 | Err(_) => "WARN".to_string(), 9 | } 10 | } 11 | 12 | pub fn _main() { 13 | let log_level = LOG_LEVEL.get_or_init(get_log_level); 14 | println!("{}", log_level); 15 | } 16 | -------------------------------------------------------------------------------- /parser-with-pest/.gitignore: -------------------------------------------------------------------------------- 1 | /target -------------------------------------------------------------------------------- /parser-with-pest/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 = "block-buffer" 7 | version = "0.10.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" 10 | dependencies = [ 11 | "generic-array", 12 | ] 13 | 14 | [[package]] 15 | name = "cfg-if" 16 | version = "1.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 19 | 20 | [[package]] 21 | name = "cpufeatures" 22 | version = "0.2.5" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" 25 | dependencies = [ 26 | "libc", 27 | ] 28 | 29 | [[package]] 30 | name = "crypto-common" 31 | version = "0.1.6" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 34 | dependencies = [ 35 | "generic-array", 36 | "typenum", 37 | ] 38 | 39 | [[package]] 40 | name = "digest" 41 | version = "0.10.6" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" 44 | dependencies = [ 45 | "block-buffer", 46 | "crypto-common", 47 | ] 48 | 49 | [[package]] 50 | name = "generic-array" 51 | version = "0.14.6" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" 54 | dependencies = [ 55 | "typenum", 56 | "version_check", 57 | ] 58 | 59 | [[package]] 60 | name = "libc" 61 | version = "0.2.138" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" 64 | 65 | [[package]] 66 | name = "once_cell" 67 | version = "1.16.0" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" 70 | 71 | [[package]] 72 | name = "parser-with-pest" 73 | version = "0.1.0" 74 | dependencies = [ 75 | "pest", 76 | "pest_derive", 77 | ] 78 | 79 | [[package]] 80 | name = "pest" 81 | version = "2.5.1" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "cc8bed3549e0f9b0a2a78bf7c0018237a2cdf085eecbbc048e52612438e4e9d0" 84 | dependencies = [ 85 | "thiserror", 86 | "ucd-trie", 87 | ] 88 | 89 | [[package]] 90 | name = "pest_derive" 91 | version = "2.5.1" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "cdc078600d06ff90d4ed238f0119d84ab5d43dbaad278b0e33a8820293b32344" 94 | dependencies = [ 95 | "pest", 96 | "pest_generator", 97 | ] 98 | 99 | [[package]] 100 | name = "pest_generator" 101 | version = "2.5.1" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "28a1af60b1c4148bb269006a750cff8e2ea36aff34d2d96cf7be0b14d1bed23c" 104 | dependencies = [ 105 | "pest", 106 | "pest_meta", 107 | "proc-macro2", 108 | "quote", 109 | "syn", 110 | ] 111 | 112 | [[package]] 113 | name = "pest_meta" 114 | version = "2.5.1" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "fec8605d59fc2ae0c6c1aefc0c7c7a9769732017c0ce07f7a9cfffa7b4404f20" 117 | dependencies = [ 118 | "once_cell", 119 | "pest", 120 | "sha1", 121 | ] 122 | 123 | [[package]] 124 | name = "proc-macro2" 125 | version = "1.0.48" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "e9d89e5dba24725ae5678020bf8f1357a9aa7ff10736b551adbcd3f8d17d766f" 128 | dependencies = [ 129 | "unicode-ident", 130 | ] 131 | 132 | [[package]] 133 | name = "quote" 134 | version = "1.0.22" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "556d0f47a940e895261e77dc200d5eadfc6ef644c179c6f5edfc105e3a2292c8" 137 | dependencies = [ 138 | "proc-macro2", 139 | ] 140 | 141 | [[package]] 142 | name = "sha1" 143 | version = "0.10.5" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" 146 | dependencies = [ 147 | "cfg-if", 148 | "cpufeatures", 149 | "digest", 150 | ] 151 | 152 | [[package]] 153 | name = "syn" 154 | version = "1.0.106" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "09ee3a69cd2c7e06684677e5629b3878b253af05e4714964204279c6bc02cf0b" 157 | dependencies = [ 158 | "proc-macro2", 159 | "quote", 160 | "unicode-ident", 161 | ] 162 | 163 | [[package]] 164 | name = "thiserror" 165 | version = "1.0.38" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" 168 | dependencies = [ 169 | "thiserror-impl", 170 | ] 171 | 172 | [[package]] 173 | name = "thiserror-impl" 174 | version = "1.0.38" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" 177 | dependencies = [ 178 | "proc-macro2", 179 | "quote", 180 | "syn", 181 | ] 182 | 183 | [[package]] 184 | name = "typenum" 185 | version = "1.16.0" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" 188 | 189 | [[package]] 190 | name = "ucd-trie" 191 | version = "0.1.5" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" 194 | 195 | [[package]] 196 | name = "unicode-ident" 197 | version = "1.0.6" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" 200 | 201 | [[package]] 202 | name = "version_check" 203 | version = "0.9.4" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 206 | -------------------------------------------------------------------------------- /parser-with-pest/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "parser-with-pest" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | pest = "2.5.1" 10 | pest_derive = "2.5.1" 11 | -------------------------------------------------------------------------------- /parser-with-pest/Readme.md: -------------------------------------------------------------------------------- 1 | # Parser with Pest 2 | 3 | Code for writing parser with Pest. 4 | This contains the example of writing a basic html parser with Pest in Rust. The implementation is not particularly efficient, but gets the work done for the example. -------------------------------------------------------------------------------- /parser-with-pest/src/document.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | 3 | use crate::Rule; 4 | use pest::iterators::Pair; 5 | 6 | // Stores individual text or tag node 7 | #[derive(Debug)] 8 | pub enum Node { 9 | TagNode(Tag), 10 | TextNode(String), 11 | } 12 | 13 | // For storing attribute info 14 | #[derive(Debug)] 15 | pub struct Attr { 16 | name: String, 17 | value: String, 18 | } 19 | 20 | // Represents a html tag 21 | #[derive(Debug)] 22 | pub struct Tag { 23 | typ: TagType, 24 | name: String, 25 | attrs: Vec, 26 | } 27 | 28 | // represents what kind of tag it is 29 | #[derive(Debug, PartialEq, Eq)] 30 | pub enum TagType { 31 | Start, 32 | End, 33 | SelfClosing, 34 | } 35 | 36 | // represents a document tree 37 | #[derive(Debug)] 38 | pub struct NodeTree { 39 | root: Node, 40 | children: Vec, 41 | } 42 | 43 | impl NodeTree { 44 | fn new(node: Node) -> Self { 45 | Self { 46 | root: node, 47 | children: Vec::new(), 48 | } 49 | } 50 | fn _print_indent(indent: u8) { 51 | for _ in 0..indent { 52 | print!(" "); 53 | } 54 | } 55 | fn _print(tree: &Vec, indent: u8) { 56 | for node in tree { 57 | NodeTree::_print_indent(indent); 58 | NodeTree::_print_node(&node.root); 59 | NodeTree::_print(&node.children, indent + 2) 60 | } 61 | } 62 | fn _print_node(node: &Node) { 63 | match node { 64 | Node::TagNode(tag) => { 65 | print!("- {}", tag.name); 66 | } 67 | Node::TextNode(s) => { 68 | print!("- text({})", s); 69 | } 70 | } 71 | print!("\n"); 72 | } 73 | pub fn print_tree(&self) { 74 | NodeTree::_print_node(&self.root); 75 | NodeTree::_print(&self.children, 2); 76 | } 77 | } 78 | 79 | // this will parse a tag to convert from Pest representation to our representation 80 | pub fn parse_tag(pair: Pair) -> Tag { 81 | // as tag will always have on inner , because the way we have defined the rule 82 | let tag = pair.into_inner().next().unwrap(); 83 | 84 | // map the rule to tag type 85 | let tag_type = match tag.as_rule() { 86 | Rule::start_tag => TagType::Start, 87 | Rule::end_tag => TagType::End, 88 | Rule::self_closing_tag => TagType::SelfClosing, 89 | _ => unreachable!(), 90 | }; 91 | 92 | let mut tag_data = tag.into_inner(); 93 | 94 | // extract name 95 | let name = tag_data.next().unwrap().as_str().to_owned(); 96 | 97 | 98 | let mut attributes = Vec::new(); 99 | 100 | // parse all attributes on a tag, 101 | for attr in tag_data { 102 | if !matches!(attr.as_rule(), Rule::attr) { 103 | unreachable!("as per syntax, the tag name is followed by zero or more attr only"); 104 | } 105 | let mut temp = attr.into_inner(); 106 | // as per the parsing rule, attr has first the name, then the value 107 | let attr_name = temp.next().unwrap().as_str(); 108 | let attr_val = temp.next().unwrap().as_str(); 109 | attributes.push(Attr { 110 | name: attr_name.to_owned(), 111 | value: attr_val.to_owned(), 112 | }); 113 | } 114 | 115 | Tag { 116 | typ: tag_type, 117 | name: name, 118 | attrs: attributes, 119 | } 120 | } 121 | 122 | // parses the list of tags into a document tree 123 | pub fn parse(nodes: Vec) -> NodeTree { 124 | let mut stack = VecDeque::new(); 125 | let mut crr: Option = None; 126 | 127 | // iterate over the nodes 128 | for node in nodes { 129 | match node { 130 | // if it is text, then simply push it as a child of current active node 131 | Node::TextNode(_) => { 132 | crr.as_mut().unwrap().children.push(NodeTree::new(node)); 133 | } 134 | // else check what kind of tag it is 135 | Node::TagNode(ref tag) => match tag.typ { 136 | // if tag is starting, then push current active tag into stack, 137 | // and set this tag as current active 138 | TagType::Start => { 139 | if crr.is_some() { 140 | stack.push_back(crr.unwrap()); 141 | } 142 | crr = Some(NodeTree::new(node)); 143 | } 144 | // if tag is self closing, add it as child of current active node 145 | TagType::SelfClosing => { 146 | crr.as_mut().unwrap().children.push(NodeTree::new(node)); 147 | } 148 | // if it is end tag, we have to take care of some cases 149 | TagType::End => { 150 | // get name of current active tag 151 | let crr_tag_name = match crr.as_ref().unwrap().root { 152 | Node::TagNode(ref tag) => tag.name.clone(), 153 | _ => unreachable!(), // as text is always pushed as child, current has to be a tag 154 | }; 155 | // if closing tag corresponds to current active tag, 156 | // pop tag from stack, add current active as its child, 157 | // and set it as current active 158 | if tag.name == crr_tag_name { 159 | if let Some(mut temp) = stack.pop_back() { 160 | temp.children.push(crr.unwrap()); 161 | crr = Some(temp); 162 | } 163 | } else { 164 | // this is a rouge closing tag. 165 | // Either we have skipped some closing tag, and thus this is 166 | // closing some ancestor tag, eg closing div in '

' 167 | // or this closing tag had no corresponding opening tag. eg

168 | let mut exists = false; 169 | // we first find if corresponding opening tag exists 170 | for node in &stack { 171 | match &node.root { 172 | Node::TagNode(t) => { 173 | if t.name == tag.name { 174 | exists = true; 175 | } 176 | } 177 | _ => unreachable!(), 178 | } 179 | } 180 | // if we don't have corresponding opening tag, we can ignore this closing tag 181 | if !exists { 182 | continue; 183 | } 184 | 185 | // We have some corresponding opening tag 186 | // keep popping trees from stack, set current as children of popped 187 | // and set the popped as current 188 | while let Some(mut tree) = stack.pop_back() { 189 | let match_found = match tree.root { 190 | Node::TagNode(ref ttag) => ttag.name == tag.name, 191 | _ => false, 192 | }; 193 | tree.children.push(crr.unwrap()); 194 | crr = Some(tree); 195 | 196 | // as the matched tag is closed, we have to do another pop 197 | // to set correct current tag 198 | if match_found { 199 | let mut tree = stack.pop_back().unwrap(); 200 | tree.children.push(crr.unwrap()); 201 | crr = Some(tree); 202 | break; 203 | } 204 | } 205 | } 206 | } 207 | } 208 | } 209 | } 210 | return crr.unwrap(); 211 | } 212 | -------------------------------------------------------------------------------- /parser-with-pest/src/html.pest: -------------------------------------------------------------------------------- 1 | document = { SOI ~ ( tag ~ text?)* ~ EOI } 2 | 3 | tag = ${ start_tag | self_closing_tag | end_tag } 4 | 5 | start_tag = { "<" ~ tag_name ~ (WHITESPACE+ ~ attr)* ~ ">" } 6 | end_tag = { "" } 7 | self_closing_tag = { "<" ~ tag_name ~ (WHITESPACE+ ~ attr)* ~ "/>" } 8 | 9 | tag_name = { ASCII_ALPHA+ } 10 | 11 | text = { (ASCII_ALPHANUMERIC | other_symbols | non_tag_start | WHITESPACE )+ } 12 | 13 | non_tag_start = ${ "<" ~ WHITESPACE+} 14 | other_symbols = { "@" | ";" | "," | ">" } 15 | 16 | attr = { attr_name ~ "=" ~ "\"" ~ text ~ "\""} 17 | 18 | attr_name = { ASCII_ALPHA+ } 19 | 20 | WHITESPACE = _{ " " | NEWLINE } -------------------------------------------------------------------------------- /parser-with-pest/src/main.rs: -------------------------------------------------------------------------------- 1 | use pest::Parser; 2 | use pest_derive::Parser; 3 | 4 | #[allow(dead_code)] 5 | mod document; 6 | 7 | const HTML: &str = r#" 8 | 9 | 10 | 11 | 12 |

13 | Hello 14 | 15 |
16 |
17 |

18 |

19 | abc 20 |

21 |

22 | jkl 23 |

24 | 25 |

26 | 27 | 28 | "#; 29 | 30 | #[derive(Parser)] 31 | #[grammar = "html.pest"] 32 | pub struct HtmlParser; 33 | 34 | fn main() { 35 | let parse = HtmlParser::parse(Rule::document, HTML).unwrap(); 36 | let mut nodes = Vec::new(); 37 | for pair in parse.into_iter() { 38 | match pair.as_rule() { 39 | Rule::document => { 40 | for tag in pair.into_inner() { 41 | if matches!(tag.as_rule(), Rule::tag) { 42 | nodes.push(document::Node::TagNode(document::parse_tag(tag))); 43 | } else if matches!(tag.as_rule(), Rule::text) { 44 | nodes.push(document::Node::TextNode(tag.as_span().as_str().to_string())); 45 | } 46 | } 47 | } 48 | _ => unreachable!(), // as we are parsing the document rule, if it is successful, no other rule will be returned 49 | } 50 | } 51 | let doc = document::parse(nodes); 52 | doc.print_tree(); 53 | } 54 | -------------------------------------------------------------------------------- /tdd-in-deno/.gitignore: -------------------------------------------------------------------------------- 1 | .vscode -------------------------------------------------------------------------------- /tdd-in-deno/bdd_test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | afterEach, 3 | beforeEach, 4 | describe, 5 | it, 6 | } from "https://deno.land/std@0.155.0/testing/bdd.ts"; 7 | import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; 8 | 9 | import { 10 | deleteFromInventory, 11 | deleteUser, 12 | fetchUsers, 13 | getDefaultInventory, 14 | insertIntoInventory, 15 | insertUser, 16 | InventoryObject, 17 | User, 18 | } from "./stubs.ts"; 19 | 20 | describe("User DB operations testing", () => { 21 | it("fetches correct user based on username", () => { 22 | const user = fetchUsers("testUser"); 23 | assertEquals(user.length, 3); 24 | }); 25 | 26 | it("inserts user correctly", () => { 27 | const newUser: User = { 28 | name: "newTestUser", 29 | id: "newtestuser", 30 | password: "newtestuser", 31 | }; 32 | insertUser(newUser); 33 | 34 | const user = fetchUsers("new"); 35 | assertEquals(user.length, 1); 36 | assertEquals(user[0].name, "newTestUser"); 37 | }); 38 | 39 | it("deletes user correctly", () => { 40 | deleteUser("newTestUser", "newtestuser"); 41 | const user = fetchUsers("test"); 42 | assertEquals(user.length, 3); 43 | }); 44 | }); 45 | 46 | describe("Inventory operations", () => { 47 | let inv: Array; 48 | 49 | beforeEach(() => { 50 | inv = getDefaultInventory(); 51 | }); 52 | 53 | afterEach(() => { 54 | inv = []; 55 | }); 56 | 57 | it("inserts object into inventory", () => { 58 | insertIntoInventory(inv, { 59 | ojbType: "book", 60 | name: "just Another book", 61 | id: 4, 62 | owner: "testUser3", 63 | }); 64 | 65 | assertEquals(inv.length, 4); 66 | assertEquals(inv[3], { 67 | ojbType: "book", 68 | name: "just Another book", 69 | id: 4, 70 | owner: "testUser3", 71 | }); 72 | }); 73 | 74 | it("deletes object from inventory", () => { 75 | inv = deleteFromInventory(inv, 3); 76 | assertEquals(inv.length, 2); 77 | 78 | const defaultInv = getDefaultInventory(); 79 | 80 | assertEquals(inv[0], defaultInv[0]); 81 | assertEquals(inv[1], defaultInv[1]); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /tdd-in-deno/external_lib_test.ts: -------------------------------------------------------------------------------- 1 | import chai from "https://cdn.skypack.dev/chai@4.3.4?dts"; 2 | import { describe, it } from "https://deno.land/x/deno_mocha/mod.ts"; 3 | 4 | const assert = chai.assert; 5 | const expect = chai.expect; 6 | 7 | import { deleteUser, fetchUsers, insertUser, User } from "./stubs.ts"; 8 | 9 | describe("User DB operations testing using chai/mocha", () => { 10 | it("fetches correct user based on username", () => { 11 | const user = fetchUsers("testUser"); 12 | assert(user.length === 3); 13 | }); 14 | 15 | it("inserts user correctly", () => { 16 | const newUser: User = { 17 | name: "newTestUser", 18 | id: "newtestuser", 19 | password: "newtestuser", 20 | }; 21 | insertUser(newUser); 22 | 23 | const user = fetchUsers("new"); 24 | expect(user).to.have.lengthOf(1); 25 | expect(user[0].name).to.be.a("string"); 26 | expect(user[0].name).to.equal("newTestUser"); 27 | }); 28 | 29 | it("deletes user correctly", () => { 30 | deleteUser("newTestUser", "newtestuser"); 31 | const user = fetchUsers("test"); 32 | expect(user).to.have.lengthOf(3); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /tdd-in-deno/simple_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; 2 | 3 | import { fetchUsers } from "./stubs.ts"; 4 | 5 | Deno.test("return empty array on empty string", () => { 6 | const users = fetchUsers(""); 7 | assertEquals(users.length, 0); 8 | }); 9 | 10 | Deno.test("return all users matching given name", () => { 11 | const users = fetchUsers("test"); 12 | assertEquals(users.length, 3); 13 | assertEquals(users[0].name, "testUser1"); 14 | assertEquals(users[1].name, "testUser2"); 15 | assertEquals(users[2].name, "testUser3"); 16 | }); 17 | 18 | Deno.test("return empty array on not matching name", () => { 19 | const users = fetchUsers("abc"); 20 | assertEquals(users.length, 0); 21 | }); 22 | -------------------------------------------------------------------------------- /tdd-in-deno/stepped_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; 2 | 3 | import { deleteUser, fetchUsers, insertUser, User } from "./stubs.ts"; 4 | 5 | Deno.test("update User Name", async (t) => { 6 | const name = "testUser1"; 7 | const newName = "testUserUpdated"; 8 | let user: User; 9 | 10 | await t.step("fetch user", () => { 11 | const temp = fetchUsers(name); 12 | assertEquals(temp.length, 1); 13 | assertEquals(temp[0].name, name); 14 | user = temp[0]; 15 | }); 16 | 17 | await t.step("update and store", () => { 18 | const newUser: User = { 19 | ...user, 20 | name: newName, 21 | }; 22 | insertUser(newUser); 23 | 24 | let temp = fetchUsers(newName); 25 | 26 | assertEquals(temp.length, 1); 27 | assertEquals(temp[0].name, newName); 28 | assertEquals(temp[0].id, user.id); 29 | assertEquals(temp[0].password, user.password); 30 | 31 | temp = fetchUsers(name); 32 | assertEquals(temp.length, 1); 33 | assertEquals(temp[0].name, name); 34 | }); 35 | 36 | await t.step("delete old user data", () => { 37 | deleteUser(user.name, user.id); 38 | 39 | let temp = fetchUsers(name); 40 | assertEquals(temp.length, 0); 41 | 42 | temp = fetchUsers(newName); 43 | assertEquals(temp.length, 1); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /tdd-in-deno/stubs.ts: -------------------------------------------------------------------------------- 1 | export interface User { 2 | name: string; 3 | id: string; 4 | password: string; 5 | } 6 | 7 | export interface InventoryObject { 8 | ojbType: string; 9 | name: string; 10 | id: number; 11 | owner: string; 12 | } 13 | 14 | let users: Array = [ 15 | { 16 | name: "testUser1", 17 | id: "tu1", 18 | password: "tu1", 19 | }, 20 | { 21 | name: "testUser2", 22 | id: "tu2", 23 | password: "tu2", 24 | }, 25 | { 26 | name: "testUser3", 27 | id: "tu3", 28 | password: "tu3", 29 | }, 30 | ]; 31 | 32 | const defaultInventory: Array = [ 33 | { 34 | ojbType: "book", 35 | name: `It's a nice book`, 36 | id: 1, 37 | owner: "testUser1", 38 | }, 39 | { 40 | ojbType: "sword", 41 | name: `sting`, 42 | id: 2, 43 | owner: "testUser2", 44 | }, 45 | { 46 | ojbType: "ring", 47 | name: `precious`, 48 | id: 3, 49 | owner: "testUser3", 50 | }, 51 | ]; 52 | 53 | export const fetchUsers = (name: string): Array => { 54 | if (name.length === 0) { 55 | return []; 56 | } else { 57 | return users.filter((u) => u.name.includes(name)); 58 | } 59 | }; 60 | 61 | export const insertUser = (u: User) => { 62 | users.push(u); 63 | }; 64 | 65 | export const deleteUser = (name: string, id: string) => { 66 | users = users.filter((u) => u.name !== name || u.id !== id); 67 | }; 68 | 69 | export const getDefaultInventory = () => { 70 | return JSON.parse(JSON.stringify(defaultInventory)); 71 | }; 72 | 73 | export const insertIntoInventory = ( 74 | inv: Array, 75 | obj: InventoryObject, 76 | ) => { 77 | inv.push(obj); 78 | }; 79 | 80 | export const deleteFromInventory = ( 81 | inv: Array, 82 | id: number, 83 | ) => { 84 | return inv.filter((obj) => obj.id !== id); 85 | }; 86 | -------------------------------------------------------------------------------- /tui-libraries-for-interactive-apps/bubbletea/go.mod: -------------------------------------------------------------------------------- 1 | module bubbletea-example 2 | 3 | go 1.22.0 4 | 5 | require ( 6 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 7 | github.com/charmbracelet/bubbletea v0.26.2 // indirect 8 | github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect 9 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 10 | github.com/mattn/go-isatty v0.0.18 // indirect 11 | github.com/mattn/go-localereader v0.0.1 // indirect 12 | github.com/mattn/go-runewidth v0.0.15 // indirect 13 | github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect 14 | github.com/muesli/cancelreader v0.2.2 // indirect 15 | github.com/muesli/reflow v0.3.0 // indirect 16 | github.com/muesli/termenv v0.15.2 // indirect 17 | github.com/rivo/uniseg v0.4.6 // indirect 18 | golang.org/x/sync v0.7.0 // indirect 19 | golang.org/x/sys v0.20.0 // indirect 20 | golang.org/x/term v0.20.0 // indirect 21 | golang.org/x/text v0.3.8 // indirect 22 | ) 23 | -------------------------------------------------------------------------------- /tui-libraries-for-interactive-apps/bubbletea/go.sum: -------------------------------------------------------------------------------- 1 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= 2 | github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 3 | github.com/charmbracelet/bubbletea v0.26.2 h1:Eeb+n75Om9gQ+I6YpbCXQRKHt5Pn4vMwusQpwLiEgJQ= 4 | github.com/charmbracelet/bubbletea v0.26.2/go.mod h1:6I0nZ3YHUrQj7YHIHlM8RySX4ZIthTliMY+W8X8b+Gs= 5 | github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= 6 | github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= 7 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= 8 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 9 | github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= 10 | github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 11 | github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= 12 | github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= 13 | github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= 14 | github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= 15 | github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 16 | github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= 17 | github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= 18 | github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= 19 | github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= 20 | github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= 21 | github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= 22 | github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= 23 | github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= 24 | github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 25 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 26 | github.com/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg= 27 | github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 28 | golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= 29 | golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 30 | golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 31 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 32 | golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= 33 | golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 34 | golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= 35 | golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= 36 | golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= 37 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 38 | -------------------------------------------------------------------------------- /tui-libraries-for-interactive-apps/bubbletea/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | tea "github.com/charmbracelet/bubbletea" 6 | "log" 7 | "os" 8 | ) 9 | 10 | type model struct { 11 | cwd string 12 | entries []string 13 | selected int 14 | } 15 | 16 | func initialModel() model { 17 | entries, err := os.ReadDir(".") 18 | if err != nil { 19 | log.Fatal(err) 20 | } 21 | var dirs = make([]string, len(entries)) 22 | for i, e := range entries { 23 | dirs[i] = e.Name() 24 | } 25 | return model{ 26 | cwd: ".", 27 | entries: dirs, 28 | selected: 0, 29 | } 30 | } 31 | 32 | func (m model) Init() tea.Cmd { 33 | return nil 34 | } 35 | 36 | func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 37 | switch msg := msg.(type) { 38 | 39 | case tea.KeyMsg: 40 | 41 | switch msg.String() { 42 | 43 | case "ctrl+c", "q": 44 | return m, tea.Quit 45 | 46 | case "up": 47 | if m.selected > 0 { 48 | m.selected-- 49 | } 50 | 51 | case "down": 52 | if m.selected < len(m.entries)-1 { 53 | m.selected++ 54 | } 55 | 56 | case "enter", " ": 57 | entry := m.entries[m.selected] 58 | m.selected = 0 59 | m.cwd = m.cwd + "/" + entry 60 | entries, err := os.ReadDir(m.cwd) 61 | if err != nil { 62 | log.Fatal(err) 63 | } 64 | var dirs = make([]string, len(entries)) 65 | for i, e := range entries { 66 | dirs[i] = e.Name() 67 | } 68 | m.entries = dirs 69 | } 70 | } 71 | 72 | return m, nil 73 | } 74 | 75 | func (m model) View() string { 76 | s := "Directory List\n\n" 77 | 78 | for i, dir := range m.entries { 79 | 80 | cursor := " " 81 | if m.selected == i { 82 | cursor = ">" 83 | } 84 | 85 | s += fmt.Sprintf("%s %s\n", cursor, dir) 86 | } 87 | 88 | s += "\nPress q to quit.\n" 89 | 90 | return s 91 | } 92 | 93 | func main() { 94 | p := tea.NewProgram(initialModel()) 95 | if _, err := p.Run(); err != nil { 96 | fmt.Printf("Alas, there's been an error: %v", err) 97 | os.Exit(1) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /tui-libraries-for-interactive-apps/clap/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 = "anstream" 7 | version = "0.6.13" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" 10 | dependencies = [ 11 | "anstyle", 12 | "anstyle-parse", 13 | "anstyle-query", 14 | "anstyle-wincon", 15 | "colorchoice", 16 | "utf8parse", 17 | ] 18 | 19 | [[package]] 20 | name = "anstyle" 21 | version = "1.0.6" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" 24 | 25 | [[package]] 26 | name = "anstyle-parse" 27 | version = "0.2.3" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" 30 | dependencies = [ 31 | "utf8parse", 32 | ] 33 | 34 | [[package]] 35 | name = "anstyle-query" 36 | version = "1.0.2" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" 39 | dependencies = [ 40 | "windows-sys", 41 | ] 42 | 43 | [[package]] 44 | name = "anstyle-wincon" 45 | version = "3.0.2" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" 48 | dependencies = [ 49 | "anstyle", 50 | "windows-sys", 51 | ] 52 | 53 | [[package]] 54 | name = "clap" 55 | version = "4.5.4" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" 58 | dependencies = [ 59 | "clap_builder", 60 | "clap_derive", 61 | ] 62 | 63 | [[package]] 64 | name = "clap-example" 65 | version = "0.1.0" 66 | dependencies = [ 67 | "clap", 68 | ] 69 | 70 | [[package]] 71 | name = "clap_builder" 72 | version = "4.5.2" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" 75 | dependencies = [ 76 | "anstream", 77 | "anstyle", 78 | "clap_lex", 79 | "strsim", 80 | ] 81 | 82 | [[package]] 83 | name = "clap_derive" 84 | version = "4.5.4" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" 87 | dependencies = [ 88 | "heck", 89 | "proc-macro2", 90 | "quote", 91 | "syn", 92 | ] 93 | 94 | [[package]] 95 | name = "clap_lex" 96 | version = "0.7.0" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" 99 | 100 | [[package]] 101 | name = "colorchoice" 102 | version = "1.0.0" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 105 | 106 | [[package]] 107 | name = "heck" 108 | version = "0.5.0" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 111 | 112 | [[package]] 113 | name = "proc-macro2" 114 | version = "1.0.81" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" 117 | dependencies = [ 118 | "unicode-ident", 119 | ] 120 | 121 | [[package]] 122 | name = "quote" 123 | version = "1.0.36" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 126 | dependencies = [ 127 | "proc-macro2", 128 | ] 129 | 130 | [[package]] 131 | name = "strsim" 132 | version = "0.11.1" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 135 | 136 | [[package]] 137 | name = "syn" 138 | version = "2.0.60" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" 141 | dependencies = [ 142 | "proc-macro2", 143 | "quote", 144 | "unicode-ident", 145 | ] 146 | 147 | [[package]] 148 | name = "unicode-ident" 149 | version = "1.0.12" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 152 | 153 | [[package]] 154 | name = "utf8parse" 155 | version = "0.2.1" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 158 | 159 | [[package]] 160 | name = "windows-sys" 161 | version = "0.52.0" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 164 | dependencies = [ 165 | "windows-targets", 166 | ] 167 | 168 | [[package]] 169 | name = "windows-targets" 170 | version = "0.52.5" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" 173 | dependencies = [ 174 | "windows_aarch64_gnullvm", 175 | "windows_aarch64_msvc", 176 | "windows_i686_gnu", 177 | "windows_i686_gnullvm", 178 | "windows_i686_msvc", 179 | "windows_x86_64_gnu", 180 | "windows_x86_64_gnullvm", 181 | "windows_x86_64_msvc", 182 | ] 183 | 184 | [[package]] 185 | name = "windows_aarch64_gnullvm" 186 | version = "0.52.5" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" 189 | 190 | [[package]] 191 | name = "windows_aarch64_msvc" 192 | version = "0.52.5" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" 195 | 196 | [[package]] 197 | name = "windows_i686_gnu" 198 | version = "0.52.5" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" 201 | 202 | [[package]] 203 | name = "windows_i686_gnullvm" 204 | version = "0.52.5" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" 207 | 208 | [[package]] 209 | name = "windows_i686_msvc" 210 | version = "0.52.5" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" 213 | 214 | [[package]] 215 | name = "windows_x86_64_gnu" 216 | version = "0.52.5" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" 219 | 220 | [[package]] 221 | name = "windows_x86_64_gnullvm" 222 | version = "0.52.5" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" 225 | 226 | [[package]] 227 | name = "windows_x86_64_msvc" 228 | version = "0.52.5" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" 231 | -------------------------------------------------------------------------------- /tui-libraries-for-interactive-apps/clap/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "clap-example" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | clap = { version = "4.5.4", features = ["derive"] } 10 | -------------------------------------------------------------------------------- /tui-libraries-for-interactive-apps/clap/src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::{Parser, Subcommand}; 2 | use std::path::PathBuf; 3 | 4 | fn parse_version(v: &str) -> Result { 5 | println!("version : {v}"); 6 | return Ok(Version { 7 | major: 0, 8 | minor: 0, 9 | patch: 0, 10 | }); 11 | } 12 | fn parse_url(url: &str) -> Result { 13 | println!("url: {url}"); 14 | return Ok(url.to_owned()); 15 | } 16 | 17 | #[allow(dead_code)] 18 | #[derive(Debug, Clone, Copy)] 19 | struct Version { 20 | major: u16, 21 | minor: u16, 22 | patch: u16, 23 | } 24 | 25 | #[derive(Debug, Subcommand)] 26 | enum Commands { 27 | /// Find a packge from name 28 | Find { 29 | /// name of the package to find 30 | name: String, 31 | /// specific version to find 32 | #[clap(long,short,value_parser=parse_version)] 33 | version: Option, 34 | /// name of the package to find 35 | #[clap(long,short,value_parser=parse_url)] 36 | registry: Option, 37 | }, 38 | Download { 39 | /// name of the package to find 40 | name: String, 41 | /// output path for the downloaded package 42 | output: PathBuf, 43 | /// display progress or not 44 | #[clap(long, short, default_value_t = false)] 45 | silent: bool, 46 | /// specific version to find 47 | #[clap(long,short,value_parser=parse_version)] 48 | version: Option, 49 | /// name of the package to find 50 | #[clap(long,short,value_parser=parse_url)] 51 | registry: Option, 52 | }, 53 | } 54 | 55 | #[derive(Parser, Debug)] 56 | #[command(version,long_about = None)] 57 | #[command(about = "An example of clap-rs for the blogpost on tuis")] 58 | struct Args { 59 | #[command(subcommand)] 60 | cmd: Commands, 61 | } 62 | 63 | fn main() { 64 | let t = Args::parse(); 65 | dbg!(t); 66 | } 67 | -------------------------------------------------------------------------------- /tui-libraries-for-interactive-apps/enquirer/index.js: -------------------------------------------------------------------------------- 1 | const { prompt } = require("enquirer"); 2 | 3 | const results = prompt([ 4 | { 5 | type: "input", 6 | name: "name", 7 | message: "What is name of your character?", 8 | }, 9 | { 10 | type: "select", 11 | name: "class", 12 | message: "What is your character class?", 13 | choices: [ 14 | { 15 | name: "Dwarf", 16 | value: "dwarf", 17 | }, 18 | { name: "Wizard", value: "wizard" }, 19 | { name: "Dragon", value: "dragon" }, 20 | ], 21 | }, 22 | { 23 | type: "Toggle", 24 | name: "custom", 25 | message: "Do advance customization?", 26 | enabled: "Yes", 27 | disabled: "No", 28 | }, 29 | { 30 | type: "select", 31 | name: "difficulty", 32 | message: "Select difficulty level", 33 | choices: [ 34 | { message: "Easy", value: "1" }, 35 | { message: "Medium", value: "2" }, 36 | { message: "Hard", value: "3" }, 37 | ], 38 | initial: "2", 39 | skip: function () { 40 | return !this.state.answers.custom; 41 | }, 42 | }, 43 | { 44 | type: "select", 45 | name: "random", 46 | message: "Select item randomness level", 47 | choices: [ 48 | { message: "Minimum", value: "1" }, 49 | { message: "Low", value: "2" }, 50 | { message: "Medium", value: "3" }, 51 | { message: "High", value: "4" }, 52 | { message: "Maximum", value: "5" }, 53 | ], 54 | initial: "3", 55 | skip: function () { 56 | return !this.state.answers.custom; 57 | }, 58 | }, 59 | ]); 60 | 61 | async function main() { 62 | const response = await results; 63 | console.log(response); 64 | } 65 | 66 | main(); 67 | -------------------------------------------------------------------------------- /tui-libraries-for-interactive-apps/enquirer/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "enquirer", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "enquirer", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "enquirer": "^2.4.1" 13 | } 14 | }, 15 | "node_modules/ansi-colors": { 16 | "version": "4.1.3", 17 | "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", 18 | "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", 19 | "engines": { 20 | "node": ">=6" 21 | } 22 | }, 23 | "node_modules/ansi-regex": { 24 | "version": "5.0.1", 25 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 26 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 27 | "engines": { 28 | "node": ">=8" 29 | } 30 | }, 31 | "node_modules/enquirer": { 32 | "version": "2.4.1", 33 | "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", 34 | "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", 35 | "dependencies": { 36 | "ansi-colors": "^4.1.1", 37 | "strip-ansi": "^6.0.1" 38 | }, 39 | "engines": { 40 | "node": ">=8.6" 41 | } 42 | }, 43 | "node_modules/strip-ansi": { 44 | "version": "6.0.1", 45 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 46 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 47 | "dependencies": { 48 | "ansi-regex": "^5.0.1" 49 | }, 50 | "engines": { 51 | "node": ">=8" 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tui-libraries-for-interactive-apps/enquirer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "enquirer-example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "enquirer": "^2.4.1" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tui-libraries-for-interactive-apps/gum/main.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | echo "Name of the package to serach for :" 4 | NAME=$(gum input) 5 | echo "Version of package to find (leave empty for any version)" 6 | VERSION=$(gum input --value="*") 7 | echo "Select registry :" 8 | REGISTRY=$(gum choose https://reg{1..3}.com) 9 | 10 | echo "name $NAME, version $VERSION, registry $REGISTRY" 11 | -------------------------------------------------------------------------------- /tui-libraries-for-interactive-apps/huh/go.mod: -------------------------------------------------------------------------------- 1 | module huh_example 2 | 3 | go 1.22.0 4 | 5 | require ( 6 | github.com/atotto/clipboard v0.1.4 // indirect 7 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 8 | github.com/catppuccin/go v0.2.0 // indirect 9 | github.com/charmbracelet/bubbles v0.17.2-0.20240108170749-ec883029c8e6 // indirect 10 | github.com/charmbracelet/bubbletea v0.25.0 // indirect 11 | github.com/charmbracelet/huh v0.3.0 // indirect 12 | github.com/charmbracelet/lipgloss v0.9.1 // indirect 13 | github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect 14 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 15 | github.com/mattn/go-isatty v0.0.20 // indirect 16 | github.com/mattn/go-localereader v0.0.1 // indirect 17 | github.com/mattn/go-runewidth v0.0.15 // indirect 18 | github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect 19 | github.com/muesli/cancelreader v0.2.2 // indirect 20 | github.com/muesli/reflow v0.3.0 // indirect 21 | github.com/muesli/termenv v0.15.2 // indirect 22 | github.com/rivo/uniseg v0.4.4 // indirect 23 | golang.org/x/sync v0.4.0 // indirect 24 | golang.org/x/sys v0.13.0 // indirect 25 | golang.org/x/term v0.13.0 // indirect 26 | golang.org/x/text v0.13.0 // indirect 27 | ) 28 | -------------------------------------------------------------------------------- /tui-libraries-for-interactive-apps/huh/go.sum: -------------------------------------------------------------------------------- 1 | github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= 2 | github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= 3 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= 4 | github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 5 | github.com/catppuccin/go v0.2.0 h1:ktBeIrIP42b/8FGiScP9sgrWOss3lw0Z5SktRoithGA= 6 | github.com/catppuccin/go v0.2.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc= 7 | github.com/charmbracelet/bubbles v0.17.2-0.20240108170749-ec883029c8e6 h1:6nVCV8pqGaeyxetur3gpX3AAaiyKgzjIoCPV3NXKZBE= 8 | github.com/charmbracelet/bubbles v0.17.2-0.20240108170749-ec883029c8e6/go.mod h1:9HxZWlkCqz2PRwsCbYl7a3KXvGzFaDHpYbSYMJ+nE3o= 9 | github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM= 10 | github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg= 11 | github.com/charmbracelet/huh v0.3.0 h1:CxPplWkgW2yUTDDG0Z4S5HH8SJOosWHd4LxCvi0XsKE= 12 | github.com/charmbracelet/huh v0.3.0/go.mod h1:fujUdKX8tC45CCSaRQdw789O6uaCRwx8l2NDyKfC4jA= 13 | github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg= 14 | github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I= 15 | github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= 16 | github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= 17 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= 18 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 19 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 20 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 21 | github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= 22 | github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= 23 | github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= 24 | github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= 25 | github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 26 | github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= 27 | github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= 28 | github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= 29 | github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= 30 | github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= 31 | github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= 32 | github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= 33 | github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= 34 | github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 35 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 36 | github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= 37 | github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 38 | golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= 39 | golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 40 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 41 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 42 | golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= 43 | golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 44 | golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= 45 | golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= 46 | golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= 47 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 48 | -------------------------------------------------------------------------------- /tui-libraries-for-interactive-apps/huh/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/charmbracelet/huh" 7 | "log" 8 | ) 9 | 10 | func validateVersion(v string) error { 11 | if v == "test" { 12 | return errors.New("Test Error") 13 | } 14 | return nil 15 | } 16 | 17 | func main() { 18 | var name string 19 | version := "*" 20 | var registry string 21 | form := huh.NewForm( 22 | huh.NewGroup( 23 | huh.NewInput(). 24 | Title("Package name to search for"). 25 | CharLimit(100). 26 | Value(&name), 27 | huh.NewInput(). 28 | Title("Version"). 29 | CharLimit(100). 30 | Validate(validateVersion). 31 | Value(&version), 32 | huh.NewSelect[string](). 33 | Title("Select Registry to search in"). 34 | Options( 35 | huh.NewOption("Registry 1", "https://reg1.com"), 36 | huh.NewOption("Registry 2", "https://reg2.com"), 37 | huh.NewOption("Registry 3", "https://reg3.com"), 38 | ). 39 | Value(®istry), 40 | ), 41 | ) 42 | err := form.Run() 43 | if err != nil { 44 | log.Fatal(err) 45 | } 46 | 47 | fmt.Println(name, version, registry) 48 | } 49 | -------------------------------------------------------------------------------- /tui-libraries-for-interactive-apps/ink-example/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.yml] 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /tui-libraries-for-interactive-apps/ink-example/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /tui-libraries-for-interactive-apps/ink-example/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /tui-libraries-for-interactive-apps/ink-example/.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /tui-libraries-for-interactive-apps/ink-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ink-example", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "bin": "dist/cli.js", 6 | "type": "module", 7 | "engines": { 8 | "node": ">=16" 9 | }, 10 | "scripts": { 11 | "build": "babel --out-dir=dist source", 12 | "dev": "babel --out-dir=dist --watch source", 13 | "test": "prettier --check . && xo && ava" 14 | }, 15 | "files": [ 16 | "dist" 17 | ], 18 | "dependencies": { 19 | "ink": "^4.1.0", 20 | "meow": "^11.0.0", 21 | "react": "^18.2.0" 22 | }, 23 | "devDependencies": { 24 | "@babel/cli": "^7.21.0", 25 | "@babel/preset-react": "^7.18.6", 26 | "@vdemedes/prettier-config": "^2.0.1", 27 | "ava": "^5.2.0", 28 | "chalk": "^5.2.0", 29 | "eslint-config-xo-react": "^0.27.0", 30 | "eslint-plugin-react": "^7.32.2", 31 | "eslint-plugin-react-hooks": "^4.6.0", 32 | "import-jsx": "^5.0.0", 33 | "ink-testing-library": "^3.0.0", 34 | "prettier": "^2.8.7", 35 | "xo": "^0.53.1" 36 | }, 37 | "ava": { 38 | "environmentVariables": { 39 | "NODE_NO_WARNINGS": "1" 40 | }, 41 | "nodeArguments": [ 42 | "--loader=import-jsx" 43 | ] 44 | }, 45 | "xo": { 46 | "extends": "xo-react", 47 | "prettier": true, 48 | "rules": { 49 | "react/prop-types": "off" 50 | } 51 | }, 52 | "prettier": "@vdemedes/prettier-config", 53 | "babel": { 54 | "presets": [ 55 | "@babel/preset-react" 56 | ] 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tui-libraries-for-interactive-apps/ink-example/readme.md: -------------------------------------------------------------------------------- 1 | # ink-example 2 | 3 | > This readme is automatically generated by [create-ink-app](https://github.com/vadimdemedes/create-ink-app) 4 | 5 | ## Install 6 | 7 | ```bash 8 | $ npm install --global ink-example 9 | ``` 10 | 11 | ## CLI 12 | 13 | ``` 14 | $ ink-example --help 15 | 16 | Usage 17 | $ ink-example 18 | 19 | Options 20 | --name Your name 21 | 22 | Examples 23 | $ ink-example --name=Jane 24 | Hello, Jane 25 | ``` 26 | -------------------------------------------------------------------------------- /tui-libraries-for-interactive-apps/ink-example/source/app.js: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | import {Text, Box, Newline, useInput, useApp} from 'ink'; 3 | 4 | export default function App() { 5 | const [text, setText] = useState(' '); 6 | const {exit} = useApp(); 7 | useInput((input, key) => { 8 | if (key.ctrl && input === 'd') { 9 | // save the file 10 | exit(); 11 | } else { 12 | let newText; 13 | if (key.return) { 14 | newText = text + '\n '; 15 | } else { 16 | newText = text + input; 17 | } 18 | setText(newText); 19 | } 20 | }); 21 | return ( 22 | <> 23 | 24 | 25 | Input your text 26 | 27 | 28 | 29 | {text.split('\n').map((t, i, arr) => ( 30 | 31 | {'>'} 32 | {t} 33 | {i == arr.length - 1 ? '_' : ''} 34 | 35 | ))} 36 | 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /tui-libraries-for-interactive-apps/ink-example/source/cli.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {render} from 'ink'; 3 | import App from './app.js'; 4 | 5 | render(); 6 | -------------------------------------------------------------------------------- /tui-libraries-for-interactive-apps/ink-example/test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import chalk from 'chalk'; 3 | import test from 'ava'; 4 | import {render} from 'ink-testing-library'; 5 | import App from './source/app.js'; 6 | 7 | test('greet unknown user', t => { 8 | const {lastFrame} = render(); 9 | 10 | t.is(lastFrame(), `Hello, ${chalk.green('Stranger')}`); 11 | }); 12 | 13 | test('greet user with a name', t => { 14 | const {lastFrame} = render(); 15 | 16 | t.is(lastFrame(), `Hello, ${chalk.green('Jane')}`); 17 | }); 18 | -------------------------------------------------------------------------------- /tui-libraries-for-interactive-apps/ratatui-example/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 = "ahash" 7 | version = "0.8.11" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" 10 | dependencies = [ 11 | "cfg-if", 12 | "once_cell", 13 | "version_check", 14 | "zerocopy", 15 | ] 16 | 17 | [[package]] 18 | name = "allocator-api2" 19 | version = "0.2.18" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" 22 | 23 | [[package]] 24 | name = "autocfg" 25 | version = "1.3.0" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 28 | 29 | [[package]] 30 | name = "bitflags" 31 | version = "2.5.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" 34 | 35 | [[package]] 36 | name = "cassowary" 37 | version = "0.3.0" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" 40 | 41 | [[package]] 42 | name = "castaway" 43 | version = "0.2.2" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc" 46 | dependencies = [ 47 | "rustversion", 48 | ] 49 | 50 | [[package]] 51 | name = "cfg-if" 52 | version = "1.0.0" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 55 | 56 | [[package]] 57 | name = "compact_str" 58 | version = "0.7.1" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" 61 | dependencies = [ 62 | "castaway", 63 | "cfg-if", 64 | "itoa", 65 | "ryu", 66 | "static_assertions", 67 | ] 68 | 69 | [[package]] 70 | name = "crossterm" 71 | version = "0.27.0" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" 74 | dependencies = [ 75 | "bitflags", 76 | "crossterm_winapi", 77 | "libc", 78 | "mio", 79 | "parking_lot", 80 | "signal-hook", 81 | "signal-hook-mio", 82 | "winapi", 83 | ] 84 | 85 | [[package]] 86 | name = "crossterm_winapi" 87 | version = "0.9.1" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" 90 | dependencies = [ 91 | "winapi", 92 | ] 93 | 94 | [[package]] 95 | name = "either" 96 | version = "1.12.0" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" 99 | 100 | [[package]] 101 | name = "hashbrown" 102 | version = "0.14.5" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 105 | dependencies = [ 106 | "ahash", 107 | "allocator-api2", 108 | ] 109 | 110 | [[package]] 111 | name = "heck" 112 | version = "0.4.1" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 115 | 116 | [[package]] 117 | name = "itertools" 118 | version = "0.12.1" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" 121 | dependencies = [ 122 | "either", 123 | ] 124 | 125 | [[package]] 126 | name = "itoa" 127 | version = "1.0.11" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 130 | 131 | [[package]] 132 | name = "libc" 133 | version = "0.2.155" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" 136 | 137 | [[package]] 138 | name = "lock_api" 139 | version = "0.4.12" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 142 | dependencies = [ 143 | "autocfg", 144 | "scopeguard", 145 | ] 146 | 147 | [[package]] 148 | name = "log" 149 | version = "0.4.21" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" 152 | 153 | [[package]] 154 | name = "lru" 155 | version = "0.12.3" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" 158 | dependencies = [ 159 | "hashbrown", 160 | ] 161 | 162 | [[package]] 163 | name = "mio" 164 | version = "0.8.11" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" 167 | dependencies = [ 168 | "libc", 169 | "log", 170 | "wasi", 171 | "windows-sys", 172 | ] 173 | 174 | [[package]] 175 | name = "once_cell" 176 | version = "1.19.0" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 179 | 180 | [[package]] 181 | name = "parking_lot" 182 | version = "0.12.2" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" 185 | dependencies = [ 186 | "lock_api", 187 | "parking_lot_core", 188 | ] 189 | 190 | [[package]] 191 | name = "parking_lot_core" 192 | version = "0.9.10" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 195 | dependencies = [ 196 | "cfg-if", 197 | "libc", 198 | "redox_syscall", 199 | "smallvec", 200 | "windows-targets 0.52.5", 201 | ] 202 | 203 | [[package]] 204 | name = "paste" 205 | version = "1.0.15" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 208 | 209 | [[package]] 210 | name = "proc-macro2" 211 | version = "1.0.83" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" 214 | dependencies = [ 215 | "unicode-ident", 216 | ] 217 | 218 | [[package]] 219 | name = "quote" 220 | version = "1.0.36" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 223 | dependencies = [ 224 | "proc-macro2", 225 | ] 226 | 227 | [[package]] 228 | name = "ratatui" 229 | version = "0.26.3" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "f44c9e68fd46eda15c646fbb85e1040b657a58cdc8c98db1d97a55930d991eef" 232 | dependencies = [ 233 | "bitflags", 234 | "cassowary", 235 | "compact_str", 236 | "crossterm", 237 | "itertools", 238 | "lru", 239 | "paste", 240 | "stability", 241 | "strum", 242 | "unicode-segmentation", 243 | "unicode-truncate", 244 | "unicode-width", 245 | ] 246 | 247 | [[package]] 248 | name = "ratatui-example" 249 | version = "0.1.0" 250 | dependencies = [ 251 | "crossterm", 252 | "ratatui", 253 | ] 254 | 255 | [[package]] 256 | name = "redox_syscall" 257 | version = "0.5.1" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" 260 | dependencies = [ 261 | "bitflags", 262 | ] 263 | 264 | [[package]] 265 | name = "rustversion" 266 | version = "1.0.17" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" 269 | 270 | [[package]] 271 | name = "ryu" 272 | version = "1.0.18" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 275 | 276 | [[package]] 277 | name = "scopeguard" 278 | version = "1.2.0" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 281 | 282 | [[package]] 283 | name = "signal-hook" 284 | version = "0.3.17" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" 287 | dependencies = [ 288 | "libc", 289 | "signal-hook-registry", 290 | ] 291 | 292 | [[package]] 293 | name = "signal-hook-mio" 294 | version = "0.2.3" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" 297 | dependencies = [ 298 | "libc", 299 | "mio", 300 | "signal-hook", 301 | ] 302 | 303 | [[package]] 304 | name = "signal-hook-registry" 305 | version = "1.4.2" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 308 | dependencies = [ 309 | "libc", 310 | ] 311 | 312 | [[package]] 313 | name = "smallvec" 314 | version = "1.13.2" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 317 | 318 | [[package]] 319 | name = "stability" 320 | version = "0.2.0" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "2ff9eaf853dec4c8802325d8b6d3dffa86cc707fd7a1a4cdbf416e13b061787a" 323 | dependencies = [ 324 | "quote", 325 | "syn", 326 | ] 327 | 328 | [[package]] 329 | name = "static_assertions" 330 | version = "1.1.0" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 333 | 334 | [[package]] 335 | name = "strum" 336 | version = "0.26.2" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" 339 | dependencies = [ 340 | "strum_macros", 341 | ] 342 | 343 | [[package]] 344 | name = "strum_macros" 345 | version = "0.26.2" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" 348 | dependencies = [ 349 | "heck", 350 | "proc-macro2", 351 | "quote", 352 | "rustversion", 353 | "syn", 354 | ] 355 | 356 | [[package]] 357 | name = "syn" 358 | version = "2.0.65" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "d2863d96a84c6439701d7a38f9de935ec562c8832cc55d1dde0f513b52fad106" 361 | dependencies = [ 362 | "proc-macro2", 363 | "quote", 364 | "unicode-ident", 365 | ] 366 | 367 | [[package]] 368 | name = "unicode-ident" 369 | version = "1.0.12" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 372 | 373 | [[package]] 374 | name = "unicode-segmentation" 375 | version = "1.11.0" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" 378 | 379 | [[package]] 380 | name = "unicode-truncate" 381 | version = "1.0.0" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "5a5fbabedabe362c618c714dbefda9927b5afc8e2a8102f47f081089a9019226" 384 | dependencies = [ 385 | "itertools", 386 | "unicode-width", 387 | ] 388 | 389 | [[package]] 390 | name = "unicode-width" 391 | version = "0.1.12" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" 394 | 395 | [[package]] 396 | name = "version_check" 397 | version = "0.9.4" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 400 | 401 | [[package]] 402 | name = "wasi" 403 | version = "0.11.0+wasi-snapshot-preview1" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 406 | 407 | [[package]] 408 | name = "winapi" 409 | version = "0.3.9" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 412 | dependencies = [ 413 | "winapi-i686-pc-windows-gnu", 414 | "winapi-x86_64-pc-windows-gnu", 415 | ] 416 | 417 | [[package]] 418 | name = "winapi-i686-pc-windows-gnu" 419 | version = "0.4.0" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 422 | 423 | [[package]] 424 | name = "winapi-x86_64-pc-windows-gnu" 425 | version = "0.4.0" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 428 | 429 | [[package]] 430 | name = "windows-sys" 431 | version = "0.48.0" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 434 | dependencies = [ 435 | "windows-targets 0.48.5", 436 | ] 437 | 438 | [[package]] 439 | name = "windows-targets" 440 | version = "0.48.5" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 443 | dependencies = [ 444 | "windows_aarch64_gnullvm 0.48.5", 445 | "windows_aarch64_msvc 0.48.5", 446 | "windows_i686_gnu 0.48.5", 447 | "windows_i686_msvc 0.48.5", 448 | "windows_x86_64_gnu 0.48.5", 449 | "windows_x86_64_gnullvm 0.48.5", 450 | "windows_x86_64_msvc 0.48.5", 451 | ] 452 | 453 | [[package]] 454 | name = "windows-targets" 455 | version = "0.52.5" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" 458 | dependencies = [ 459 | "windows_aarch64_gnullvm 0.52.5", 460 | "windows_aarch64_msvc 0.52.5", 461 | "windows_i686_gnu 0.52.5", 462 | "windows_i686_gnullvm", 463 | "windows_i686_msvc 0.52.5", 464 | "windows_x86_64_gnu 0.52.5", 465 | "windows_x86_64_gnullvm 0.52.5", 466 | "windows_x86_64_msvc 0.52.5", 467 | ] 468 | 469 | [[package]] 470 | name = "windows_aarch64_gnullvm" 471 | version = "0.48.5" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 474 | 475 | [[package]] 476 | name = "windows_aarch64_gnullvm" 477 | version = "0.52.5" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" 480 | 481 | [[package]] 482 | name = "windows_aarch64_msvc" 483 | version = "0.48.5" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 486 | 487 | [[package]] 488 | name = "windows_aarch64_msvc" 489 | version = "0.52.5" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" 492 | 493 | [[package]] 494 | name = "windows_i686_gnu" 495 | version = "0.48.5" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 498 | 499 | [[package]] 500 | name = "windows_i686_gnu" 501 | version = "0.52.5" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" 504 | 505 | [[package]] 506 | name = "windows_i686_gnullvm" 507 | version = "0.52.5" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" 510 | 511 | [[package]] 512 | name = "windows_i686_msvc" 513 | version = "0.48.5" 514 | source = "registry+https://github.com/rust-lang/crates.io-index" 515 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 516 | 517 | [[package]] 518 | name = "windows_i686_msvc" 519 | version = "0.52.5" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" 522 | 523 | [[package]] 524 | name = "windows_x86_64_gnu" 525 | version = "0.48.5" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 528 | 529 | [[package]] 530 | name = "windows_x86_64_gnu" 531 | version = "0.52.5" 532 | source = "registry+https://github.com/rust-lang/crates.io-index" 533 | checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" 534 | 535 | [[package]] 536 | name = "windows_x86_64_gnullvm" 537 | version = "0.48.5" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 540 | 541 | [[package]] 542 | name = "windows_x86_64_gnullvm" 543 | version = "0.52.5" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" 546 | 547 | [[package]] 548 | name = "windows_x86_64_msvc" 549 | version = "0.48.5" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 552 | 553 | [[package]] 554 | name = "windows_x86_64_msvc" 555 | version = "0.52.5" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" 558 | 559 | [[package]] 560 | name = "zerocopy" 561 | version = "0.7.34" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" 564 | dependencies = [ 565 | "zerocopy-derive", 566 | ] 567 | 568 | [[package]] 569 | name = "zerocopy-derive" 570 | version = "0.7.34" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" 573 | dependencies = [ 574 | "proc-macro2", 575 | "quote", 576 | "syn", 577 | ] 578 | -------------------------------------------------------------------------------- /tui-libraries-for-interactive-apps/ratatui-example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ratatui-example" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | crossterm = "0.27.0" 8 | ratatui = "0.26.3" 9 | -------------------------------------------------------------------------------- /tui-libraries-for-interactive-apps/ratatui-example/src/main.rs: -------------------------------------------------------------------------------- 1 | use crossterm::{ 2 | event::{self, KeyCode, KeyEventKind}, 3 | terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, 4 | ExecutableCommand, 5 | }; 6 | use ratatui::{prelude::*, widgets::*}; 7 | use std::{ 8 | io::{stdout, Result}, 9 | path::PathBuf, 10 | }; 11 | 12 | fn main() -> Result<()> { 13 | stdout().execute(EnterAlternateScreen)?; 14 | enable_raw_mode()?; 15 | let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?; 16 | terminal.clear()?; 17 | 18 | let mut cwd = PathBuf::from("."); 19 | let mut selected = 0; 20 | let mut state = ListState::default(); 21 | let mut entries: Vec = std::fs::read_dir(cwd.clone()) 22 | .unwrap() 23 | .map(|entry| entry.unwrap().file_name()) 24 | .map(|s| s.into_string().unwrap()) 25 | .collect::>(); 26 | 27 | loop { 28 | if event::poll(std::time::Duration::from_millis(16))? { 29 | if let event::Event::Key(key) = event::read()? { 30 | if key.kind == KeyEventKind::Press { 31 | match key.code { 32 | KeyCode::Char('q') => { 33 | break; 34 | } 35 | KeyCode::Up => selected = (entries.len() + selected - 1) % entries.len(), 36 | KeyCode::Down => selected = (selected + 1) % entries.len(), 37 | KeyCode::Enter => { 38 | cwd = cwd.join(entries[selected].clone()); 39 | entries = std::fs::read_dir(cwd.clone()) 40 | .unwrap() 41 | .map(|entry| entry.unwrap().file_name()) 42 | .map(|s| s.into_string().unwrap()) 43 | .collect::>(); 44 | selected = 0; 45 | } 46 | _ => {} 47 | } 48 | } 49 | } 50 | } 51 | let list = List::new(entries.clone()) 52 | .block(Block::bordered().title("Directory Entries")) 53 | .style(Style::default().fg(Color::White)) 54 | .highlight_style( 55 | Style::default() 56 | .add_modifier(Modifier::BOLD) 57 | .bg(Color::White) 58 | .fg(Color::Black), 59 | ) 60 | .highlight_symbol(">"); 61 | 62 | terminal.draw(|frame| { 63 | let area = frame.size(); 64 | state.select(Some(selected)); 65 | frame.render_stateful_widget(list, area, &mut state); 66 | })?; 67 | } 68 | 69 | stdout().execute(LeaveAlternateScreen)?; 70 | disable_raw_mode()?; 71 | Ok(()) 72 | } 73 | -------------------------------------------------------------------------------- /tui-libraries-for-interactive-apps/textual/app.py: -------------------------------------------------------------------------------- 1 | import os 2 | from textual.app import App 3 | from textual.widgets import Header, Footer, Button, Static 4 | 5 | 6 | def create_id(s:str): 7 | return s.replace(".","_").replace("@","_") 8 | 9 | class DirDisplay(Static): 10 | directory = "." 11 | dir_list = [Button(x,id=x) for x in os.listdir(".")] 12 | 13 | def on_button_pressed(self, event): 14 | self.directory = os.path.join(self.directory,str(event.button.label)) 15 | self.dir_list = []; 16 | for dir in os.listdir(self.directory): 17 | self.dir_list.append(Button(dir,id=create_id(dir))) 18 | self.remove_children() 19 | self.mount_all(self.dir_list) 20 | 21 | def compose(self): 22 | return self.dir_list 23 | 24 | class Explorer(App): 25 | 26 | def compose(self): 27 | yield Header() 28 | yield DirDisplay() 29 | yield Footer() 30 | 31 | if __name__ == "__main__": 32 | # print(os.listdir(".")) 33 | app = Explorer() 34 | app.run() 35 | --------------------------------------------------------------------------------