Split cli
and lang
off in hopes of allowing testing of cli
This commit is contained in:
parent
5c1fcc5ae0
commit
47565f2706
34 changed files with 379 additions and 449 deletions
23
Cargo.lock
generated
23
Cargo.lock
generated
|
@ -374,6 +374,8 @@ dependencies = [
|
|||
"clap_complete",
|
||||
"colored",
|
||||
"file-owner",
|
||||
"ghee-cli",
|
||||
"ghee-lang",
|
||||
"lazy_static",
|
||||
"nom",
|
||||
"path-absolutize",
|
||||
|
@ -388,6 +390,27 @@ dependencies = [
|
|||
"xdg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ghee-cli"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"ghee-lang",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ghee-lang"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"nom",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.1"
|
||||
|
|
11
Cargo.toml
11
Cargo.toml
|
@ -7,6 +7,9 @@ default-run = "ghee"
|
|||
license = "GPL-3.0"
|
||||
build = "build.rs"
|
||||
|
||||
[workspace]
|
||||
members = [ "cli", "lang" ]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.75"
|
||||
atty = "0.2.14"
|
||||
|
@ -14,6 +17,8 @@ btrfsutil = { git = "https://github.com/cezarmathe/btrfsutil-rs" }
|
|||
clap = { version = "4.3.19", features = ["derive"] }
|
||||
colored = "2.0.4"
|
||||
file-owner = "0.1.2"
|
||||
ghee-cli = { path = "cli" }
|
||||
ghee-lang = { path = "lang" }
|
||||
lazy_static = "1.4.0"
|
||||
nom = "7.1.3"
|
||||
path-absolutize = "3.1.1"
|
||||
|
@ -27,8 +32,14 @@ xattr = { version = "1.0.0", default-features = false }
|
|||
xdg = "2.5.2"
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = "1.0.75"
|
||||
clap = { version = "4.3.19", features = ["derive"] }
|
||||
clap_complete = "4.3.2"
|
||||
ghee-lang = { path = "lang" }
|
||||
nom = "7.1.3"
|
||||
serde = { version = "1.0.163", features = ["derive"] }
|
||||
serde_json = "1.0.96"
|
||||
thiserror = "1.0.44"
|
||||
|
||||
[profile.release]
|
||||
strip = true
|
||||
|
|
2
build.rs
2
build.rs
|
@ -4,7 +4,7 @@ use clap_complete::Shell;
|
|||
use std::env;
|
||||
use std::io::Error;
|
||||
|
||||
include!("src/cli.rs");
|
||||
include!("cli/src/lib.rs");
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
let outdir = match env::var_os("OUT_DIR") {
|
||||
|
|
9
cli/Cargo.toml
Normal file
9
cli/Cargo.toml
Normal file
|
@ -0,0 +1,9 @@
|
|||
[package]
|
||||
name = "ghee-cli"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
clap = "*"
|
||||
ghee-lang = { path = "../lang" }
|
||||
thiserror = "*"
|
237
cli/src/lib.rs
Normal file
237
cli/src/lib.rs
Normal file
|
@ -0,0 +1,237 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use clap::{builder::ValueParser, Parser, Subcommand};
|
||||
use thiserror::Error;
|
||||
|
||||
use ghee_lang::{Assignment, AssignmentParser, Predicate, PredicateParser, Value, Xattr};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
#[command(propagate_version = true)]
|
||||
pub struct Cli {
|
||||
/// Subcommand to run. If none is provided, a REPL will be launched
|
||||
#[command(subcommand)]
|
||||
pub command: Option<Commands>,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum TableOpenErr {
|
||||
#[error("Table path could not be opened because it doesn't exist")]
|
||||
NoSuchPath,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum Commands {
|
||||
/// Copy xattr values from one path to another.
|
||||
Cp {
|
||||
#[arg(help = "Path to copy xattrs from")]
|
||||
src: PathBuf,
|
||||
#[arg(help = "Path to copy xattrs to")]
|
||||
dest: PathBuf,
|
||||
#[arg(name = "field", short, long, help = "xattrs to copy")]
|
||||
fields: Vec<Xattr>,
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
},
|
||||
/// Move xattr values from one path to another.
|
||||
Mv {
|
||||
#[arg(help = "Path to move xattrs from")]
|
||||
src: PathBuf,
|
||||
#[arg(help = "Path to move xattrs to")]
|
||||
dest: PathBuf,
|
||||
#[arg(name = "field", short, long, help = "xattrs to move")]
|
||||
fields: Vec<Xattr>,
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
},
|
||||
/// Remove xattr values.
|
||||
Rm {
|
||||
#[arg(name = "path", help = "Paths to remove xattrs from", required = true)]
|
||||
paths: Vec<PathBuf>,
|
||||
#[arg(name = "field", short, long, help = "xattrs to remove")]
|
||||
fields: Vec<Xattr>,
|
||||
#[arg(long, help = "Process paths nonrecursively; defaults to false")]
|
||||
flat: bool,
|
||||
#[arg(
|
||||
long,
|
||||
help = "Don't complain when an xattr doesn't exist; defaults to false"
|
||||
)]
|
||||
force: bool,
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
},
|
||||
|
||||
/// Get and print xattr values for one or more paths.
|
||||
Get {
|
||||
#[arg(name = "path", help = "Paths to get xattrs from", required = true)]
|
||||
paths: Vec<PathBuf>,
|
||||
#[arg(name = "field", short, long, help = "xattrs to get")]
|
||||
fields: Vec<Xattr>,
|
||||
#[arg(short, long, help = "Output JSON; lossily decodes as UTF-8")]
|
||||
json: bool,
|
||||
#[arg(name = "where", short, long, help = "WHERE clause to filter results by", value_parser = ValueParser::new(PredicateParser{}))]
|
||||
where_: Vec<Predicate>,
|
||||
#[arg(long, help = "Process paths nonrecursively; defaults to false")]
|
||||
flat: bool,
|
||||
#[arg(short, long, help = "Include user.ghee prefix in output")]
|
||||
all: bool,
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
help = "Sort directory contents as paths are walked; disable for potential speedup on large listings",
|
||||
default_value_t = true
|
||||
)]
|
||||
sort: bool,
|
||||
#[arg(short, long, help = "Return records even when apparently empty")]
|
||||
visit_empty: bool,
|
||||
},
|
||||
|
||||
/// Set xattr values
|
||||
Set {
|
||||
#[arg(name = "paths", help = "Paths to set xattrs on", required = true)]
|
||||
paths: Vec<PathBuf>,
|
||||
#[arg(name = "set", short, long, help = "k=v pairs to set", value_parser = ValueParser::new(AssignmentParser{}))]
|
||||
field_assignments: Vec<Assignment>,
|
||||
#[arg(long, help = "Process paths nonrecursively; defaults to false")]
|
||||
flat: bool,
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
},
|
||||
|
||||
/// Insert records into a table, updating related indices
|
||||
Ins {
|
||||
#[arg(help = "Path of the table to insert into")]
|
||||
table_path: PathBuf,
|
||||
#[arg(
|
||||
help = "Path of the records, JSON, one record per line; if omitted, the same format is taken from stdin"
|
||||
)]
|
||||
records_path: Option<PathBuf>,
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
},
|
||||
|
||||
/// Remove record from a table, updating related indices
|
||||
Del {
|
||||
#[arg(help = "Path of the table to delete from")]
|
||||
table_path: PathBuf,
|
||||
#[arg(name = "where", short, long, help = "WHERE clauses specifying what to delete", value_parser = ValueParser::new(PredicateParser{}))]
|
||||
where_: Vec<Predicate>,
|
||||
#[arg(help = "Primary key values, subkeys space separated")]
|
||||
key: Vec<Value>,
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
},
|
||||
|
||||
/// Index tables
|
||||
Idx {
|
||||
#[arg(help = "Path to recursively index")]
|
||||
src: PathBuf,
|
||||
#[arg(help = "Path to output the new index to")]
|
||||
dest: Option<PathBuf>,
|
||||
#[arg(name = "key", short, long, help = "xattrs to index by")]
|
||||
keys: Vec<Xattr>,
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
},
|
||||
|
||||
/// List directory contents as seen by Ghee
|
||||
Ls {
|
||||
#[arg(help = "Paths to list the contents of; current directory by default")]
|
||||
paths: Vec<PathBuf>,
|
||||
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
help = "Sort directory contents; disable for potential speedup on large listings",
|
||||
default_value_t = true
|
||||
)]
|
||||
sort: bool,
|
||||
},
|
||||
|
||||
/// Initialize a directory as a Ghee table with specified primary key, then optionally insert records
|
||||
Init {
|
||||
#[arg(help = "Directory to initialize as a Ghee table")]
|
||||
dir: PathBuf,
|
||||
|
||||
#[arg(
|
||||
name = "key",
|
||||
short,
|
||||
long,
|
||||
help = "xattrs the table will be indexed by"
|
||||
)]
|
||||
keys: Vec<Xattr>,
|
||||
|
||||
#[arg(
|
||||
help = "Path of the records to insert, JSON, one record per line; if omitted, the same format is taken from stdin"
|
||||
)]
|
||||
records_path: Option<PathBuf>,
|
||||
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
},
|
||||
|
||||
/// Like `init`, but creates the directory first, or errors if one exists already
|
||||
Create {
|
||||
#[arg(help = "Directory to create and initialize as a Ghee table")]
|
||||
dir: PathBuf,
|
||||
|
||||
#[arg(
|
||||
name = "key",
|
||||
short,
|
||||
long,
|
||||
help = "xattrs the table will be indexed by"
|
||||
)]
|
||||
keys: Vec<Xattr>,
|
||||
|
||||
#[arg(
|
||||
help = "Path of the records to insert, JSON, one record per line; if omitted, the same format is taken from stdin"
|
||||
)]
|
||||
records_path: Option<PathBuf>,
|
||||
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
},
|
||||
|
||||
/// Commit a table, storing its contents as a BTRFS snapshot
|
||||
Commit {
|
||||
#[arg(help = "Directory of the table to commit, or the current directory by default")]
|
||||
dir: Option<PathBuf>,
|
||||
|
||||
#[arg(short, long, help = "A message describing the changes being committed")]
|
||||
message: Option<String>,
|
||||
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
},
|
||||
|
||||
/// Print the commit log
|
||||
Log {
|
||||
#[arg(
|
||||
help = "Directory of the table whose log to print, or the current directory by default"
|
||||
)]
|
||||
dir: Option<PathBuf>,
|
||||
},
|
||||
|
||||
/// Create a file, inferring xattrs from path if part of a table
|
||||
Touch {
|
||||
#[arg(help = "Path at which to create the file")]
|
||||
path: PathBuf,
|
||||
#[arg(short, long, help = "Create parent directories")]
|
||||
parents: bool,
|
||||
},
|
||||
|
||||
/// Print the status of files in the table, relative to the most recent commit (if any)
|
||||
Status {
|
||||
#[arg(
|
||||
help = "Path of a file in a table; the whole table's status will be listed; defaults to the current directory"
|
||||
)]
|
||||
path: Option<PathBuf>,
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
help = "Sort directory contents; disable for potential speedup on large listings",
|
||||
default_value_t = true
|
||||
)]
|
||||
sort: bool,
|
||||
},
|
||||
}
|
14
lang/Cargo.toml
Normal file
14
lang/Cargo.toml
Normal file
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "ghee-lang"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "*"
|
||||
clap = "*"
|
||||
thiserror = "*"
|
||||
nom = "*"
|
||||
serde = "*"
|
||||
serde_json = "*"
|
|
@ -9,7 +9,7 @@ use nom::{character::complete::char, combinator::map, multi::separated_list1, IR
|
|||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{xattr_values, xattr_values_from_path, Record};
|
||||
use crate::Record;
|
||||
|
||||
use super::{
|
||||
value::Value,
|
||||
|
@ -106,28 +106,6 @@ impl Key {
|
|||
self.subkeys.iter()
|
||||
}
|
||||
|
||||
/// Return the value of this key for the specified path
|
||||
/// TODO Get only the relevant xattrs
|
||||
pub fn value_for_path(&self, path: &PathBuf) -> Result<Vec<Value>> {
|
||||
let xattrs = xattr_values(path)?;
|
||||
|
||||
let mut values: Vec<Value> = Vec::with_capacity(self.subkeys.len());
|
||||
|
||||
for subkey in self.subkeys.iter() {
|
||||
let value =
|
||||
xattrs
|
||||
.get(subkey)
|
||||
.cloned()
|
||||
.ok_or_else(|| KeyErr::SubkeyValueNotFoundOnPath {
|
||||
subkey: subkey.clone(),
|
||||
path: path.clone(),
|
||||
})?;
|
||||
values.push(value);
|
||||
}
|
||||
|
||||
Ok(values)
|
||||
}
|
||||
|
||||
pub fn value_for_record(&self, record: &Record) -> Result<Vec<Value>> {
|
||||
let mut values: Vec<Value> = Vec::with_capacity(self.subkeys.len());
|
||||
|
||||
|
@ -155,11 +133,6 @@ impl Key {
|
|||
|
||||
Ok(base_path)
|
||||
}
|
||||
|
||||
/// The xattrs inferred from the path itself
|
||||
pub fn record_for_path(&self, base_path: &PathBuf, path: &PathBuf) -> Result<Record> {
|
||||
xattr_values_from_path(self, base_path, path)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: ToString, I: IntoIterator<Item = S>> From<I> for Key {
|
||||
|
@ -217,7 +190,7 @@ pub fn parse_key(i: &[u8]) -> IResult<&[u8], Key> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::parser::{key::Key, xattr::parse_xattr};
|
||||
use crate::{xattr::parse_xattr, Key};
|
||||
|
||||
#[test]
|
||||
fn test_from_string_iter() {
|
|
@ -1,9 +1,11 @@
|
|||
use std::collections::BTreeMap;
|
||||
|
||||
use nom::{
|
||||
bytes::complete::take_while, character::complete::char, character::is_space,
|
||||
multi::separated_list0, sequence::delimited, IResult,
|
||||
};
|
||||
|
||||
use self::value::{parse_value_not_all_consuming, Value};
|
||||
pub type Record = BTreeMap<Xattr, Value>;
|
||||
|
||||
pub fn space(i: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
take_while(move |x| is_space(x))(i)
|
||||
|
@ -49,10 +51,16 @@ fn test_array() {
|
|||
);
|
||||
}
|
||||
|
||||
pub mod assignment;
|
||||
pub mod index;
|
||||
pub mod key;
|
||||
pub mod predicate;
|
||||
pub mod relation;
|
||||
pub mod value;
|
||||
pub mod xattr;
|
||||
mod assignment;
|
||||
mod key;
|
||||
mod predicate;
|
||||
mod relation;
|
||||
mod value;
|
||||
mod xattr;
|
||||
|
||||
pub use assignment::*;
|
||||
pub use key::*;
|
||||
pub use predicate::*;
|
||||
pub use relation::*;
|
||||
pub use value::*;
|
||||
pub use xattr::*;
|
236
src/bin/ghee.rs
236
src/bin/ghee.rs
|
@ -1,253 +1,23 @@
|
|||
use std::{env::current_dir, ffi::OsString, path::PathBuf};
|
||||
|
||||
use clap::{builder::ValueParser, Parser, Subcommand};
|
||||
use clap::Parser;
|
||||
use ghee_cli::{Cli, Commands};
|
||||
use ghee_lang::Key;
|
||||
use rustyline::error::ReadlineError;
|
||||
use rustyline::DefaultEditor;
|
||||
use thiserror::Error;
|
||||
|
||||
use ghee::{
|
||||
cmd::{commit, cp, create, del, get, idx, init, ins, log, ls, mv, rm, set, status, touch},
|
||||
parser::{
|
||||
assignment::{Assignment, AssignmentParser},
|
||||
key::Key,
|
||||
predicate::{Predicate, PredicateParser},
|
||||
value::Value,
|
||||
xattr::Xattr,
|
||||
},
|
||||
APP_NAME, PKG_NAME, XDG_DIRS,
|
||||
};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
#[command(propagate_version = true)]
|
||||
struct Cli {
|
||||
/// Subcommand to run. If none is provided, a REPL will be launched
|
||||
#[command(subcommand)]
|
||||
command: Option<Commands>,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum TableOpenErr {
|
||||
#[error("Table path could not be opened because it doesn't exist")]
|
||||
NoSuchPath,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
/// Copy xattr values from one path to another.
|
||||
Cp {
|
||||
#[arg(help = "Path to copy xattrs from")]
|
||||
src: PathBuf,
|
||||
#[arg(help = "Path to copy xattrs to")]
|
||||
dest: PathBuf,
|
||||
#[arg(name = "field", short, long, help = "xattrs to copy")]
|
||||
fields: Vec<Xattr>,
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
},
|
||||
/// Move xattr values from one path to another.
|
||||
Mv {
|
||||
#[arg(help = "Path to move xattrs from")]
|
||||
src: PathBuf,
|
||||
#[arg(help = "Path to move xattrs to")]
|
||||
dest: PathBuf,
|
||||
#[arg(name = "field", short, long, help = "xattrs to move")]
|
||||
fields: Vec<Xattr>,
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
},
|
||||
/// Remove xattr values.
|
||||
Rm {
|
||||
#[arg(name = "path", help = "Paths to remove xattrs from", required = true)]
|
||||
paths: Vec<PathBuf>,
|
||||
#[arg(name = "field", short, long, help = "xattrs to remove")]
|
||||
fields: Vec<Xattr>,
|
||||
#[arg(long, help = "Process paths nonrecursively; defaults to false")]
|
||||
flat: bool,
|
||||
#[arg(
|
||||
long,
|
||||
help = "Don't complain when an xattr doesn't exist; defaults to false"
|
||||
)]
|
||||
force: bool,
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
},
|
||||
|
||||
/// Get and print xattr values for one or more paths.
|
||||
Get {
|
||||
#[arg(name = "path", help = "Paths to get xattrs from", required = true)]
|
||||
paths: Vec<PathBuf>,
|
||||
#[arg(name = "field", short, long, help = "xattrs to get")]
|
||||
fields: Vec<Xattr>,
|
||||
#[arg(short, long, help = "Output JSON; lossily decodes as UTF-8")]
|
||||
json: bool,
|
||||
#[arg(name = "where", short, long, help = "WHERE clause to filter results by", value_parser = ValueParser::new(PredicateParser{}))]
|
||||
where_: Vec<Predicate>,
|
||||
#[arg(long, help = "Process paths nonrecursively; defaults to false")]
|
||||
flat: bool,
|
||||
#[arg(short, long, help = "Include user.ghee prefix in output")]
|
||||
all: bool,
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
help = "Sort directory contents as paths are walked; disable for potential speedup on large listings",
|
||||
default_value_t = true
|
||||
)]
|
||||
sort: bool,
|
||||
#[arg(short, long, help = "Return records even when apparently empty")]
|
||||
visit_empty: bool,
|
||||
},
|
||||
|
||||
/// Set xattr values
|
||||
Set {
|
||||
#[arg(name = "paths", help = "Paths to set xattrs on", required = true)]
|
||||
paths: Vec<PathBuf>,
|
||||
#[arg(name = "set", short, long, help = "k=v pairs to set", value_parser = ValueParser::new(AssignmentParser{}))]
|
||||
field_assignments: Vec<Assignment>,
|
||||
#[arg(long, help = "Process paths nonrecursively; defaults to false")]
|
||||
flat: bool,
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
},
|
||||
|
||||
/// Insert records into a table, updating related indices
|
||||
Ins {
|
||||
#[arg(help = "Path of the table to insert into")]
|
||||
table_path: PathBuf,
|
||||
#[arg(
|
||||
help = "Path of the records, JSON, one record per line; if omitted, the same format is taken from stdin"
|
||||
)]
|
||||
records_path: Option<PathBuf>,
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
},
|
||||
|
||||
/// Remove record from a table, updating related indices
|
||||
Del {
|
||||
#[arg(help = "Path of the table to delete from")]
|
||||
table_path: PathBuf,
|
||||
#[arg(name = "where", short, long, help = "WHERE clauses specifying what to delete", value_parser = ValueParser::new(PredicateParser{}))]
|
||||
where_: Vec<Predicate>,
|
||||
#[arg(help = "Primary key values, subkeys space separated")]
|
||||
key: Vec<Value>,
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
},
|
||||
|
||||
/// Index tables
|
||||
Idx {
|
||||
#[arg(help = "Path to recursively index")]
|
||||
src: PathBuf,
|
||||
#[arg(help = "Path to output the new index to")]
|
||||
dest: Option<PathBuf>,
|
||||
#[arg(name = "key", short, long, help = "xattrs to index by")]
|
||||
keys: Vec<Xattr>,
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
},
|
||||
|
||||
/// List directory contents as seen by Ghee
|
||||
Ls {
|
||||
#[arg(help = "Paths to list the contents of; current directory by default")]
|
||||
paths: Vec<PathBuf>,
|
||||
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
help = "Sort directory contents; disable for potential speedup on large listings",
|
||||
default_value_t = true
|
||||
)]
|
||||
sort: bool,
|
||||
},
|
||||
|
||||
/// Initialize a directory as a Ghee table with specified primary key, then optionally insert records
|
||||
Init {
|
||||
#[arg(help = "Directory to initialize as a Ghee table")]
|
||||
dir: PathBuf,
|
||||
|
||||
#[arg(
|
||||
name = "key",
|
||||
short,
|
||||
long,
|
||||
help = "xattrs the table will be indexed by"
|
||||
)]
|
||||
keys: Vec<Xattr>,
|
||||
|
||||
#[arg(
|
||||
help = "Path of the records to insert, JSON, one record per line; if omitted, the same format is taken from stdin"
|
||||
)]
|
||||
records_path: Option<PathBuf>,
|
||||
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
},
|
||||
|
||||
/// Like `init`, but creates the directory first, or errors if one exists already
|
||||
Create {
|
||||
#[arg(help = "Directory to create and initialize as a Ghee table")]
|
||||
dir: PathBuf,
|
||||
|
||||
#[arg(
|
||||
name = "key",
|
||||
short,
|
||||
long,
|
||||
help = "xattrs the table will be indexed by"
|
||||
)]
|
||||
keys: Vec<Xattr>,
|
||||
|
||||
#[arg(
|
||||
help = "Path of the records to insert, JSON, one record per line; if omitted, the same format is taken from stdin"
|
||||
)]
|
||||
records_path: Option<PathBuf>,
|
||||
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
},
|
||||
|
||||
/// Commit a table, storing its contents as a BTRFS snapshot
|
||||
Commit {
|
||||
#[arg(help = "Directory of the table to commit, or the current directory by default")]
|
||||
dir: Option<PathBuf>,
|
||||
|
||||
#[arg(short, long, help = "A message describing the changes being committed")]
|
||||
message: Option<String>,
|
||||
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
},
|
||||
|
||||
/// Print the commit log
|
||||
Log {
|
||||
#[arg(
|
||||
help = "Directory of the table whose log to print, or the current directory by default"
|
||||
)]
|
||||
dir: Option<PathBuf>,
|
||||
},
|
||||
|
||||
/// Create a file, inferring xattrs from path if part of a table
|
||||
Touch {
|
||||
#[arg(help = "Path at which to create the file")]
|
||||
path: PathBuf,
|
||||
#[arg(short, long, help = "Create parent directories")]
|
||||
parents: bool,
|
||||
},
|
||||
|
||||
/// Print the status of files in the table, relative to the most recent commit (if any)
|
||||
Status {
|
||||
#[arg(
|
||||
help = "Path of a file in a table; the whole table's status will be listed; defaults to the current directory"
|
||||
)]
|
||||
path: Option<PathBuf>,
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
help = "Sort directory contents; disable for potential speedup on large listings",
|
||||
default_value_t = true
|
||||
)]
|
||||
sort: bool,
|
||||
},
|
||||
}
|
||||
|
||||
fn repl() -> rustyline::Result<()> {
|
||||
println!("{} {}", *APP_NAME, env!("CARGO_PKG_VERSION"));
|
||||
println!();
|
||||
|
|
78
src/cli.rs
78
src/cli.rs
|
@ -1,78 +0,0 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
#[command(propagate_version = true)]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
/// Copy xattr values from one path to another.
|
||||
Cp {
|
||||
#[arg(help = "Path to copy xattrs from")]
|
||||
src: PathBuf,
|
||||
#[arg(help = "Path to copy xattrs to")]
|
||||
dest: PathBuf,
|
||||
#[arg(name = "field", short, long, help = "xattrs to copy")]
|
||||
fields: Vec<String>,
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
},
|
||||
/// Move xattr values from one path to another.
|
||||
Mv {
|
||||
#[arg(help = "Path to move xattrs from")]
|
||||
src: PathBuf,
|
||||
#[arg(help = "Path to move xattrs to")]
|
||||
dest: PathBuf,
|
||||
#[arg(name = "field", short, long, help = "xattrs to move")]
|
||||
fields: Vec<String>,
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
},
|
||||
/// Remove xattr values.
|
||||
Rm {
|
||||
#[arg(name = "path", help = "Paths to remove xattrs from", required = true)]
|
||||
paths: Vec<PathBuf>,
|
||||
#[arg(name = "field", short, long, help = "xattrs to remove")]
|
||||
fields: Vec<String>,
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
},
|
||||
|
||||
/// Get and print xattr values for one or more paths.
|
||||
Get {
|
||||
#[arg(name = "path", help = "Paths to get xattrs from", required = true)]
|
||||
paths: Vec<PathBuf>,
|
||||
#[arg(name = "field", short, long, help = "xattrs to get")]
|
||||
fields: Vec<String>,
|
||||
#[arg(short, long, help = "Output JSON; lossily decodes as UTF-8")]
|
||||
json: bool,
|
||||
},
|
||||
|
||||
/// Set xattr values
|
||||
Set {
|
||||
#[arg(name = "paths", help = "Paths to set xattrs on", required = true)]
|
||||
paths: Vec<PathBuf>,
|
||||
#[arg(name = "set", short, long, help = "k=v pairs to set")]
|
||||
field_assignments: Vec<String>,
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
},
|
||||
|
||||
/// Index tables
|
||||
Idx {
|
||||
#[arg(help = "Path to recursively index")]
|
||||
src: PathBuf,
|
||||
#[arg(help = "Path to output the new index to")]
|
||||
dest: PathBuf,
|
||||
#[arg(name = "key", short, long, help = "xattrs to index by")]
|
||||
keys: Vec<String>,
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
},
|
||||
}
|
|
@ -2,7 +2,7 @@ use anyhow::Result;
|
|||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::parser::xattr::Xattr;
|
||||
use ghee_lang::Xattr;
|
||||
|
||||
use super::cp_or_mv::{cp_or_mv, CopyOrMove};
|
||||
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
use anyhow::{Context, Result};
|
||||
use ghee_lang::{Assignment, Xattr};
|
||||
|
||||
use std::{fmt::Formatter, path::PathBuf};
|
||||
|
||||
use crate::{
|
||||
list_xattrs,
|
||||
parser::{assignment::Assignment, xattr::Xattr},
|
||||
xattr_value,
|
||||
};
|
||||
use crate::{list_xattrs, xattr_value};
|
||||
|
||||
use super::{rm, set};
|
||||
|
||||
|
@ -78,7 +75,9 @@ pub(crate) fn cp_or_mv(
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{parser::value::Value, test_support::Scenario, xattr_values};
|
||||
use ghee_lang::Value;
|
||||
|
||||
use crate::{test_support::Scenario, xattr_values};
|
||||
|
||||
use super::{cp_or_mv, CopyOrMove};
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
use anyhow::Result;
|
||||
use btrfsutil::subvolume::Subvolume;
|
||||
use ghee_lang::Key;
|
||||
use thiserror::Error;
|
||||
|
||||
use std::fs::{create_dir, create_dir_all};
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::parser::key::Key;
|
||||
use crate::GheeErr;
|
||||
|
||||
use super::init;
|
||||
|
@ -54,7 +54,9 @@ pub fn create(dir: &PathBuf, key: &Key, verbose: bool) -> Result<()> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{parser::key::Key, test_support::TempDirAuto};
|
||||
use ghee_lang::Key;
|
||||
|
||||
use crate::test_support::TempDirAuto;
|
||||
|
||||
use super::create;
|
||||
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
use anyhow::Result;
|
||||
use ghee_lang::{Key, Predicate, Value};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{
|
||||
parser::{key::Key, predicate::Predicate, value::Value},
|
||||
table_info,
|
||||
walk::walk_records,
|
||||
xattr_values, Record,
|
||||
};
|
||||
use crate::{table_info, walk::walk_records, xattr_values, Record};
|
||||
|
||||
use std::{collections::BTreeMap, fs::remove_file, path::PathBuf};
|
||||
|
||||
|
@ -139,12 +135,10 @@ pub fn del(
|
|||
mod test {
|
||||
use std::{cell::Cell, rc::Rc};
|
||||
|
||||
use ghee_lang::{parse_predicate, Value};
|
||||
|
||||
use crate::{
|
||||
parser::{predicate::parse_predicate, value::Value},
|
||||
record_count, table_info,
|
||||
test_support::Scenario,
|
||||
walk::walk_records,
|
||||
xattr_values,
|
||||
record_count, table_info, test_support::Scenario, walk::walk_records, xattr_values,
|
||||
};
|
||||
|
||||
use super::{del, unlink_record};
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
use anyhow::Result;
|
||||
use ghee_lang::{Predicate, Value, Xattr};
|
||||
use serde::Serialize;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{
|
||||
parser::{predicate::Predicate, value::Value, xattr::Xattr},
|
||||
walk::walk_records,
|
||||
};
|
||||
use crate::walk::walk_records;
|
||||
|
||||
use std::{collections::BTreeMap, io::Write, path::PathBuf};
|
||||
|
||||
|
@ -92,7 +90,9 @@ pub fn get(
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{cmd::create, parser::key::Key, test_support::TempDirAuto};
|
||||
use ghee_lang::Key;
|
||||
|
||||
use crate::{cmd::create, test_support::TempDirAuto};
|
||||
|
||||
use super::get;
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use anyhow::Result;
|
||||
use ghee_lang::Key;
|
||||
use ghee_lang::Xattr;
|
||||
use thiserror::Error;
|
||||
|
||||
use std::fs::hard_link;
|
||||
|
@ -10,11 +12,8 @@ use std::path::Path;
|
|||
use walkdir::WalkDir;
|
||||
|
||||
use crate::declare_closure_indices;
|
||||
use crate::parser::xattr::Xattr;
|
||||
use crate::paths::sub_idx_path;
|
||||
|
||||
use crate::parser::key::Key;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::init;
|
||||
|
@ -106,7 +105,9 @@ pub fn idx(src: &PathBuf, dest: Option<&PathBuf>, keys: &Key, verbose: bool) ->
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{cmd::init, parser::key::Key, table_info, test_support::TempDirAuto};
|
||||
use ghee_lang::Key;
|
||||
|
||||
use crate::{cmd::init, table_info, test_support::TempDirAuto};
|
||||
|
||||
use super::idx;
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use std::{collections::BTreeMap, path::PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
use ghee_lang::Key;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{parser::key::Key, set_table_info, table_info, GheeErr, TableInfo};
|
||||
use crate::{set_table_info, table_info, GheeErr, TableInfo};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum TableInitErr {
|
||||
|
@ -46,11 +47,9 @@ pub fn init(dir: &PathBuf, key: &Key, verbose: bool) -> Result<()> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{
|
||||
parser::{key::Key, xattr::parse_xattr},
|
||||
table_info,
|
||||
test_support::TempDirAuto,
|
||||
};
|
||||
use ghee_lang::{parse_xattr, Key};
|
||||
|
||||
use crate::{table_info, test_support::TempDirAuto};
|
||||
|
||||
use super::init;
|
||||
|
||||
|
|
|
@ -5,12 +5,10 @@ use std::{
|
|||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use ghee_lang::{Key, Value, Xattr};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{
|
||||
parser::{key::Key, value::Value, xattr::Xattr},
|
||||
table_info, Record,
|
||||
};
|
||||
use crate::{table_info, Record};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum InsErr {
|
||||
|
@ -170,7 +168,9 @@ pub fn ins_records(
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{parser::value::Value, test_support::Scenario, xattr_values};
|
||||
use ghee_lang::Value;
|
||||
|
||||
use crate::{test_support::Scenario, xattr_values};
|
||||
|
||||
#[test]
|
||||
fn test_ins() {
|
||||
|
|
|
@ -2,11 +2,11 @@ use std::path::PathBuf;
|
|||
|
||||
use anyhow::Result;
|
||||
use colored::Colorize;
|
||||
use ghee_lang::Value;
|
||||
use xattr;
|
||||
|
||||
use crate::{
|
||||
parser::value::Value, paths::table_snapshot_path, xattr_value, XATTR_MOST_RECENT_SNAPSHOT,
|
||||
XATTR_SNAPSHOT_MESSAGE,
|
||||
paths::table_snapshot_path, xattr_value, XATTR_MOST_RECENT_SNAPSHOT, XATTR_SNAPSHOT_MESSAGE,
|
||||
};
|
||||
|
||||
pub fn log(dir: &PathBuf) -> Result<()> {
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
use anyhow::Result;
|
||||
use ghee_lang::Xattr;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::parser::xattr::Xattr;
|
||||
|
||||
use super::cp_or_mv::{cp_or_mv, CopyOrMove};
|
||||
|
||||
/** Move xattrs from `src` to `dest` */
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
use anyhow::Result;
|
||||
use ghee_lang::{Key, Xattr};
|
||||
use thiserror::Error;
|
||||
|
||||
use std::{collections::BTreeSet, fs::remove_file, path::PathBuf};
|
||||
|
||||
use crate::{
|
||||
containing_table_info,
|
||||
parser::{key::Key, xattr::Xattr},
|
||||
walk::walk_paths,
|
||||
xattr_values,
|
||||
};
|
||||
use crate::{containing_table_info, walk::walk_paths, xattr_values};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum RmErr {
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
use anyhow::{Context, Result};
|
||||
use ghee_lang::{Assignment, Value, Xattr};
|
||||
use path_absolutize::*;
|
||||
use thiserror::Error;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use crate::parser::value::Value;
|
||||
use crate::{containing_table_info, xattr_values, Record};
|
||||
|
||||
use crate::parser::xattr::Xattr;
|
||||
|
||||
use crate::parser::assignment::Assignment;
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::fs::{create_dir_all, hard_link, remove_file};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
@ -146,9 +142,10 @@ mod test {
|
|||
path::PathBuf,
|
||||
};
|
||||
|
||||
use ghee_lang::{parse_assignment, Value, Xattr};
|
||||
|
||||
use crate::{
|
||||
cmd::ins::ins_records,
|
||||
parser::{assignment::parse_assignment, value::Value, xattr::Xattr},
|
||||
test_support::{Scenario, TempDirAuto},
|
||||
xattr_values, Record,
|
||||
};
|
||||
|
|
|
@ -66,9 +66,10 @@ pub fn status(path: &PathBuf, sort: bool) -> Result<()> {
|
|||
mod test {
|
||||
use std::path::Path;
|
||||
|
||||
use ghee_lang::Key;
|
||||
|
||||
use crate::{
|
||||
cmd::create,
|
||||
parser::key::Key,
|
||||
test_support::{CurrentDirGuard, TempDirAuto},
|
||||
};
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ use std::{
|
|||
use anyhow::Result;
|
||||
use path_absolutize::Absolutize;
|
||||
|
||||
use crate::{containing_table_info, write_xattr_values};
|
||||
use crate::{containing_table_info, write_xattr_values, xattr_values_from_path};
|
||||
|
||||
/**
|
||||
* Create a file at `path`; if part of a table, initialize its
|
||||
|
@ -25,7 +25,7 @@ pub fn touch(path: &PathBuf, create_parents: bool) -> Result<()> {
|
|||
File::create(&path)?;
|
||||
|
||||
if let Some(info) = containing_table_info(&path)? {
|
||||
let xattrs = info.key.record_for_path(info.path(), &path)?;
|
||||
let xattrs = xattr_values_from_path(&info.key, info.path(), &path)?;
|
||||
|
||||
write_xattr_values(path, &xattrs)?;
|
||||
}
|
||||
|
@ -35,7 +35,9 @@ pub fn touch(path: &PathBuf, create_parents: bool) -> Result<()> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{cmd::init, parser::key::Key, test_support::TempDirAuto, xattr_values};
|
||||
use ghee_lang::Key;
|
||||
|
||||
use crate::{cmd::init, test_support::TempDirAuto, xattr_values};
|
||||
|
||||
use super::touch;
|
||||
|
||||
|
|
24
src/lib.rs
24
src/lib.rs
|
@ -1,9 +1,7 @@
|
|||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
pub mod cli;
|
||||
pub mod cmd;
|
||||
pub mod parser;
|
||||
pub mod paths;
|
||||
#[cfg(test)]
|
||||
mod test_support;
|
||||
|
@ -15,12 +13,7 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use parser::{
|
||||
key::Key,
|
||||
predicate::Predicate,
|
||||
value::{parse_value, Value},
|
||||
xattr::{parse_xattr, Xattr},
|
||||
};
|
||||
use ghee_lang::{parse_value, parse_xattr, Key, Namespace, Predicate, Record, Value, Xattr};
|
||||
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -28,8 +21,6 @@ use thiserror::Error;
|
|||
use walkdir::{DirEntry, WalkDir};
|
||||
use xdg::BaseDirectories;
|
||||
|
||||
use crate::parser::xattr::Namespace;
|
||||
|
||||
/// Uppercase the first character in a string
|
||||
/// https://stackoverflow.com/a/38406885/5374919
|
||||
fn uppercase_first(s: &str) -> String {
|
||||
|
@ -415,8 +406,6 @@ pub fn set_table_info<P: AsRef<Path>>(dir: P, info: &TableInfo) -> Result<()> {
|
|||
)
|
||||
}
|
||||
|
||||
pub type Record = BTreeMap<Xattr, Value>;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum IndexListPushErr {
|
||||
#[error("Table info not found at {0}; can only push to initialized tables")]
|
||||
|
@ -473,17 +462,12 @@ mod test {
|
|||
fs::{create_dir_all, File},
|
||||
};
|
||||
|
||||
use ghee_lang::{parse_predicate, parse_xattr, Key, Namespace, Value, Xattr};
|
||||
|
||||
use crate::{
|
||||
best_index,
|
||||
cmd::{idx, init},
|
||||
containing_table_info, declare_indices, index_list_push,
|
||||
parser::{
|
||||
key::Key,
|
||||
predicate::parse_predicate,
|
||||
value::Value,
|
||||
xattr::{parse_xattr, Namespace, Xattr},
|
||||
},
|
||||
set_table_info, table_info,
|
||||
containing_table_info, declare_indices, index_list_push, set_table_info, table_info,
|
||||
test_support::{Scenario, TempDirAuto},
|
||||
write_xattr_values, xattr_values, xattr_values_from_path, Record, TableInfo,
|
||||
};
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
use anyhow::Result;
|
||||
use ghee_lang::{Key, Namespace};
|
||||
|
||||
use std::{ffi::OsString, path::PathBuf};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::parser::{key::Key, xattr::Namespace};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum SubIdxPathErr {
|
||||
#[error("The provided key was empty")]
|
||||
|
|
|
@ -6,12 +6,10 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use ghee_lang::{Key, Xattr};
|
||||
use tempdir::TempDir;
|
||||
|
||||
use crate::{
|
||||
cmd::{idx, init, ins},
|
||||
parser::{key::Key, xattr::Xattr},
|
||||
};
|
||||
use crate::cmd::{idx, init, ins};
|
||||
|
||||
/** A test scenario
|
||||
*
|
||||
|
|
20
src/walk.rs
20
src/walk.rs
|
@ -3,16 +3,11 @@ use std::path::PathBuf;
|
|||
use anyhow::Result;
|
||||
use walkdir::{DirEntry, WalkDir};
|
||||
|
||||
use ghee_lang::{Key, Namespace, Predicate, Value, Xattr};
|
||||
|
||||
use crate::{
|
||||
best_index, containing_table_info, is_hidden,
|
||||
parser::{
|
||||
key::Key,
|
||||
predicate::Predicate,
|
||||
value::Value,
|
||||
xattr::{Namespace, Xattr},
|
||||
},
|
||||
table_info, xattr_values, xattr_values_from_path, Record, TableInfo, DEFAULT_KEY,
|
||||
XATTR_GHEE_LOWER, XATTR_GHEE_UPPER,
|
||||
best_index, containing_table_info, is_hidden, table_info, xattr_values, xattr_values_from_path,
|
||||
Record, TableInfo, DEFAULT_KEY, XATTR_GHEE_LOWER, XATTR_GHEE_UPPER,
|
||||
};
|
||||
|
||||
pub struct PathVisit<'a, 'b, 'c> {
|
||||
|
@ -228,14 +223,11 @@ mod test {
|
|||
rc::Rc,
|
||||
};
|
||||
|
||||
use ghee_lang::{parse_assignment, parse_predicate, Key, Predicate};
|
||||
|
||||
use crate::{
|
||||
cmd::{init, set},
|
||||
declare_indices,
|
||||
parser::{
|
||||
assignment::parse_assignment,
|
||||
key::Key,
|
||||
predicate::{parse_predicate, Predicate},
|
||||
},
|
||||
test_support::{Scenario, TempDirAuto},
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue