typst local package (tlp) manager

Implement init and rm

+73
+7
README.md
··· 8 8 9 9 ## Commands 10 10 - `tlp new <name>`: Creates a new local package with the given name in the current directory and links it. 11 + - `tlp init`: Creates a new local package with the name from typst.toml in the current directory. Also initializes a git repository, if not one already. 11 12 - `tlp path <name>`: Get the full path to the local package with the given name (in case you forgot, or need it for other automation) 12 13 - `tlp bump <name> [--major|--patch]`: Increase the version of the local package with the given name. 13 14 This will create a new git tag for the latest version, 14 15 then increase said version by modifying the `typst.toml` and renaming the latest-version symlink, 15 16 and finally add a new `git worktree` for the previously latest version. 17 + - `tlp rm <name>`: Removes the local package with the given name. 16 18 17 19 All commands support the `--package-path` option (also available via the `TYPST_PACKAGE_PATH` env variable), 18 20 which works exactly like it does in `typst` to specify where to look instead of (the equivalent of) `~/.local/share/typst/packages`. ··· 23 25 24 26 Alternatively, you can compile this program with the `git2` feature; 25 27 Then, it will use `libgit2` instead of invoking `git`, so the `--git` option will have no effect. 28 + 29 + 30 + ## ToDo 31 + - [ ] `tlp init`: Populate a version for each tag 32 + - [ ] `tlp init`: Check if there are any `@local/` in the whole code, check if they are available, if not print warnings.
+8
src/args.rs
··· 20 20 /// The name of the local package 21 21 name: String, 22 22 }, 23 + /// Initialize a local package 24 + Init {}, 23 25 /// Print the path of a local package 24 26 #[clap(name = "path")] 25 27 PrintPath { ··· 36 38 /// Increase the patch instead of the minor version 37 39 #[clap(long)] 38 40 patch: bool, 41 + }, 42 + /// Remove a local package 43 + #[clap(name = "rm")] 44 + Remove { 45 + /// The name of the local package 46 + name: String, 39 47 }, 40 48 }
+58
src/main.rs
··· 81 81 eprintln!("which is linked to {pkg_dir:?}"); 82 82 eprintln!("you can import it with `#import \"@local/{name}:0.1.0\"`"); 83 83 } 84 + Init {} => { 85 + println!("The current directory is {}", current_dir.display()); 86 + let typst_toml = current_dir.join("typst.toml"); 87 + let name = extract_name(&typst_toml).whatever_context("failed to get package name")?; 88 + verify_name(&name)?; 89 + let ver = 90 + extract_version(&typst_toml).whatever_context("failed to get package version")?; 91 + let current_ver = format!("{}.{}.{}", ver[0], ver[1], ver[2]); 92 + let pkg_dir = packages_local.join(&name); 93 + if pkg_dir.exists() { 94 + whatever!("a local package called {name:?} already exists"); 95 + } 96 + fs_err::create_dir(&pkg_dir).whatever_context("failed to create package directory")?; 97 + if !current_dir.join(".git").exists() { 98 + eprintln!("Initializing git in the current repo!"); 99 + git::init(&current_dir, &git).whatever_context("failed to create repository")?; 100 + git::commit(&current_dir, "*", "Initial commit", &git, true) 101 + .whatever_context("failed to create initial commit")?; 102 + } 103 + symlink_dir(&current_dir, &pkg_dir.join("source")) 104 + .whatever_context("failed to create latest version symlink")?; 105 + symlink_dir(&current_dir, &pkg_dir.join(&current_ver)) 106 + .whatever_context("failed to create latest version symlink")?; 107 + eprintln!("linked current directory to {pkg_dir:?}"); 108 + eprintln!("you can import it with `#import \"@local/{name}:{current_ver}\"`"); 109 + } 84 110 PrintPath { name } => { 85 111 verify_name(&name)?; 86 112 let pkg_dir = packages_local.join(&name); ··· 155 181 .whatever_context("failed to commit typst.toml")?; 156 182 eprintln!("bumped version of local package {name:?} from {old_ver} to {new_ver}."); 157 183 } 184 + Remove { name } => { 185 + verify_name(&name)?; 186 + let pkg_dir = packages_local.join(&name); 187 + if !pkg_dir.exists() { 188 + whatever!("no local package {name:?} found"); 189 + } 190 + if !pkg_dir.join("source").is_symlink() { 191 + whatever!( 192 + "could not find the source code! Might not be added by tlp, remove aborted." 193 + ) 194 + } 195 + fs_err::remove_dir_all(pkg_dir) 196 + .whatever_context("failed to remove the package directory")?; 197 + println!("Removed local package: {name:?}"); 198 + } 158 199 } 159 200 160 201 Ok(()) 202 + } 203 + 204 + fn extract_name(typst_toml: &Path) -> Result<String, Whatever> { 205 + let file = fs_err::read_to_string(typst_toml) 206 + .whatever_context("could not find a typst.toml file the current directory")?; 207 + let pkg = toml::from_str::<toml::Table>(&file).whatever_context("failed to parse file")?; 208 + let name = pkg 209 + .get("package") 210 + .whatever_context("no `[package]` table")? 211 + .as_table() 212 + .whatever_context("`package` is not a table")? 213 + .get("name") 214 + .whatever_context("`[package]` table has no `name` key")? 215 + .as_str() 216 + .whatever_context("`package.name` is not a string")?; 217 + // name.whatever_context("invalid `package.version`") 218 + Ok(name.to_string()) 161 219 } 162 220 163 221 fn extract_version(typst_toml: &Path) -> Result<[u32; 3], Whatever> {