From 21129c487433225ae837556199abd1806bcc41a2 Mon Sep 17 00:00:00 2001 From: NobodyForNothing <82763757+NobodyForNothing@users.noreply.github.com> Date: Sun, 30 Jun 2024 17:35:42 +0200 Subject: [PATCH] implement ini parser --- rust/Cargo.lock | 248 +++++++++++++++++++++++++++++++++++++++ rust/Cargo.toml | 4 +- rust/ini-conf/Cargo.toml | 7 ++ rust/ini-conf/src/lib.rs | 110 +++++++++++++++++ rust/vcs/src/lib.rs | 184 +++++++++++++++++++++++++++++ 5 files changed, 551 insertions(+), 2 deletions(-) create mode 100644 rust/ini-conf/Cargo.toml create mode 100644 rust/ini-conf/src/lib.rs create mode 100644 rust/vcs/src/lib.rs diff --git a/rust/Cargo.lock b/rust/Cargo.lock index e5bd68b..60fc9bf 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -2,6 +2,254 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "anstream" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" + +[[package]] +name = "anstyle-parse" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "clap" +version = "4.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" + +[[package]] +name = "colorchoice" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" + [[package]] name = "dir_size" version = "0.1.0" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "ini-conf" +version = "0.1.0" +dependencies = [ + "log", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "vcs" +version = "0.1.0" +dependencies = [ + "clap", + "log", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 662f592..323fbd5 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -2,5 +2,5 @@ resolver = "2" members = [ - "dir_size", -] \ No newline at end of file + "dir_size", "ini-conf", "vcs", +] diff --git a/rust/ini-conf/Cargo.toml b/rust/ini-conf/Cargo.toml new file mode 100644 index 0000000..a5e3b41 --- /dev/null +++ b/rust/ini-conf/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "ini-conf" +version = "0.1.0" +edition = "2021" + +[dependencies] +log = "0.4.22" diff --git a/rust/ini-conf/src/lib.rs b/rust/ini-conf/src/lib.rs new file mode 100644 index 0000000..8dbaed2 --- /dev/null +++ b/rust/ini-conf/src/lib.rs @@ -0,0 +1,110 @@ +use std::fs; +use std::path::PathBuf; +use log::warn; + +pub struct IniFile { + path: PathBuf, + sections: Vec
, +} +impl IniFile { + /// Open an existing file + pub fn open(path: PathBuf) -> Result { + if path.is_file() { + if let Ok(data) = fs::read_to_string(&path) { + if let Some(sections) = Self::parse(data) { + Ok(IniFile{ + path, + sections, + }) + } else { + Err(IniFileOpenError::FormatError) + } + } else { + Err(IniFileOpenError::IOError) + } + } else { + Err(IniFileOpenError::IOError) + } + } + + fn parse(data: String) -> Option> { + let mut sections = Vec::new(); + let mut curr_sect: Option
= None; + for token in tokenize(&data) { + match token { + IniToken::Empty => {} + IniToken::Comment(_) => {} + IniToken::SectionHeader(name) => { + if let Some(curr_sect) = curr_sect { + sections.push(curr_sect); + } + curr_sect = Some(Section::new(name)); + } + IniToken::KeyValuePair(key, value) => { + if let Some(ref mut curr_sect) = curr_sect { + curr_sect.kv_pairs.push((key, value)); + } else { + return None; + } + } + IniToken::Unknown(line) => { + warn!("Unrecognized token: {line}"); + } + } + } + Some(sections) + } +} + +pub struct Section { + name: String, + pub(crate) kv_pairs: Vec<(String, String)>, +} + +impl Section { + pub(crate) fn new(name: String) -> Self { + Section { + name, + kv_pairs: Vec::new(), + } + } +} + +pub enum IniFileOpenError { + /// File is doesn't exist, is read protected, ect... + IOError, + /// File is in invalid format. + FormatError, +} + + +fn tokenize(data: &String) -> Vec { + let mut tokens = Vec::new(); + for line in data.lines() { + let line = line.trim(); + let t = if line.starts_with(";") || line.starts_with("#") { + IniToken::Comment(line.to_string()) + } else if line.starts_with("[") && line.ends_with("]") { + let header = &line[1..(line.len()-1)]; + IniToken::SectionHeader(header.to_string()) + } else if line.is_empty() { + IniToken::Empty + } else if let Some(kv) = line.split_once("="){ + let key= kv.0.trim().to_string(); + let value= kv.1.trim().to_string(); + IniToken::KeyValuePair(key, value) + } else { + IniToken::Unknown(line.to_string()) + }; + tokens.push(t); + } + tokens +} + +enum IniToken { + Empty, + Comment(String), + SectionHeader(String), + KeyValuePair(String, String), + Unknown(String), +} diff --git a/rust/vcs/src/lib.rs b/rust/vcs/src/lib.rs new file mode 100644 index 0000000..a1ff7ea --- /dev/null +++ b/rust/vcs/src/lib.rs @@ -0,0 +1,184 @@ +use std::{fs, os}; +use std::fs::{create_dir, create_dir_all}; +use std::path::{Path, PathBuf}; +use log::{log, warn}; + +pub struct Repository { + /// Where the files meant to be in version control live. + work_tree: PathBuf, + /// Storage of vcs data + git_dir: PathBuf, + + config: RepoConfig, +} + +impl Repository { + /// Load an existing repository at [path]. + /// + /// [force] (default false) ignores missing directories and always returns + /// [Ok]. + pub fn new(path: PathBuf, force: Option) -> Result { + let force = force.unwrap_or(false); + + let mut instance = Repository { + work_tree: path.clone(), + git_dir: path.join(".git"), + config: RepoConfig::default(), + }; + + if instance.git_dir.is_dir() || force { + let path = instance.repo_path(vec!("config"), None, Some(true)); + if path.is_some() || force { + instance.config = RepoConfig::read(path.unwrap()); + if instance.config.repository_format_version <= 0 || force { + Ok(instance) + } else { + Err(RepositoryLoadError::UnsupportedRepositoryFormatVersion { + actual: instance.config.repository_format_version, + supported: 0, + }) + } + } else { + Err(RepositoryLoadError::ConfigurationFileMissing) + } + } else { + Err(RepositoryLoadError::NotAGitRepository) + } + } + + pub fn init(path: PathBuf) -> Result { + let repo = Self::new(path, Some(true)).ok().expect("Force is passed"); + if repo.work_tree.is_file() || repo.work_tree.is_symlink() { + Err(RepositoryInitError::NotADirectory) + } else if repo.git_dir.read_dir().is_ok_and(|dir| dir.count() > 0) { + Err(RepositoryInitError::AlreadyInitialized) + } else { + let success: Option<()> = { + if !repo.git_dir.exists() { + create_dir_all(&repo.git_dir).ok()?; + } + + repo.repo_path(vec!["branches"], Some(true), Some(false))?; + repo.repo_path(vec!["objects"], Some(true), Some(false))?; + repo.repo_path(vec!["refs", "tags"], Some(true), Some(false))?; + repo.repo_path(vec!["refs", "heads"], Some(true), Some(false))?; + + let desc = repo.repo_path(vec!["description"], Some(false), Some(true))?; + fs::write(desc, "Unnamed repository; edit this file 'description' to name the repository.\n")?; + + let head = repo.repo_path(vec!["HEAD"], Some(false), Some(true))?; + fs::write(head, "ref: refs/heads/master\n")?; + + let config = repo.repo_path(vec!["config"], Some(false), Some(true))?; + repo.config.write(config)?; + + Some(()) + }; + if success.is_none() { + Err(RepositoryInitError::IOError) + } else { + Ok(repo) + } + } + } + + /// Compute path under repo's gitdir. + /// + /// If [mkdir] is true directories specified in the path will be created. + /// If [has_file] is true no directory will be created for the last item in + /// [path_list]. [mkdir] and [has_file] default to false. + fn repo_path>(&self, path_list: Vec

, mkdir: Option, has_file: Option) -> Option { + if path_list.is_empty() { + panic!("repo_path needs a path to join") + } + + let mkdir = mkdir.unwrap_or(false); + let has_file = has_file.unwrap_or(false); + + let dir_path_list = if has_file { + &path_list[0..path_list.len() - 1] + } else { + &path_list + }; + + let mut res_path = self.git_dir.clone(); + for path in dir_path_list { + res_path = res_path.join(path) + } + if mkdir { + if res_path.exists() && !res_path.is_dir() { + panic!("Tried to create dir where there already was a file: {}", &res_path); + } + if std::fs::create_dir_all(&res_path).is_err() { + warn!("Failed to create {}", &res_path); + } + } + + if res_path.exists() { + if has_file { + res_path = res_path.join(path_list.last().expect("repo_path won't accept empty paths.")) + } + Some(res_path) + } else { + None + } + } +} + +#[derive(Debug)] +enum RepositoryLoadError { + NotAGitRepository, + ConfigurationFileMissing, + UnsupportedRepositoryFormatVersion { + /// Version from config file. + actual: u8, + /// Highest version supported by the program. + supported: u8, + }, +} + +#[derive(Debug)] +enum RepositoryInitError { + NotADirectory, + AlreadyInitialized, + IOError, +} + +struct RepoConfig { + /// The version of the gitdir format. + /// + /// - 0 means the initial format + /// - 1 the same with extensions + repository_format_version: u8, + /// Disable tracking of file modes (permissions) changes in the work tree. + file_mode: bool, + /// Indicates whether this repository has a worktree. + bare: bool, + // Always assume worktree is at `../`. +} +impl RepoConfig { + fn read(path: PathBuf) -> Self { + Self::default() // TODO + } + + fn write(&self, path: PathBuf) -> Option<()> { + let txt = format!("[core]\n repositoryformatversion = {}\n filemode = {}\n bare = {}\n", + self.repository_format_version, + self.file_mode, + self.bare, + ); + + fs::write(path, txt)?; + + Some(()) + } +} +impl Default for RepoConfig { + fn default() -> Self { + RepoConfig { + repository_format_version: 0, + file_mode: false, + bare: false, + } + } +} \ No newline at end of file