Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

file subcommand split into read, put and del, filepath can now also be inline content, when it is, for put and del the updated contents will be printed to stdout. #8

Merged
merged 2 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion dev_scripts/utils.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ run_parallel () {
}

py_install_if_missing () {
if ! python -c "import $1" &> /dev/null; then
# Make a version replacing dashes with underscores for the import check:
with_underscores=$(echo $1 | sed 's/-/_/g')
if ! python -c "import $with_underscores" &> /dev/null; then
echo "$1 is not installed. Installing..."
python -m pip install $1
fi
Expand Down
4 changes: 2 additions & 2 deletions py_rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion py_rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ valico = '4.0.0'

[dependencies.bitbazaar]
features = ['cli']
version = '0.0.26'
version = '0.0.28'

[dependencies.clap]
features = ['derive', 'string']
Expand Down
4 changes: 3 additions & 1 deletion py_rust/src/arg_matcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ pub fn arg_matcher(arg: args::Args) -> Result<(), Zerr> {
args::Command::Var(read_var) => Ok(var::read_var(&arg, read_var)?),
args::Command::Init(init) => Ok(init::init(init)?),
args::Command::ReplaceMatcher(replace) => Ok(replace_matcher::replace(&arg, replace)?),
args::Command::File(fargs) => Ok(read_write::handle_file_cmd(&arg, fargs)?),
args::Command::Read(fargs) => Ok(read_write::handle_file_cmd(&arg, fargs.into())?),
args::Command::Put(fargs) => Ok(read_write::handle_file_cmd(&arg, fargs.into())?),
args::Command::Del(fargs) => Ok(read_write::handle_file_cmd(&arg, fargs.into())?),
args::Command::Version { output_format: _ } => {
println!("zetch {}", get_version_info());
Ok(())
Expand Down
95 changes: 68 additions & 27 deletions py_rust/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,24 @@ pub struct Args {
pub enum Command {
/// Render all templates found whilst traversing the given root (default).
Render(RenderCommand),

/// Read a finalised context variable from the config file.
Var(VarCommand),

/// Read sections of json/toml/yaml/yml files various file types from the command line, outputting in json.
Read(ReadCommand),
/// Put/modify sections of json/toml/yaml/yml files, preserving comments and existing formatting where possible.
Put(PutCommand),
/// Delete sections of json/toml/yaml/yml files, preserving comments and existing formatting where possible.
#[clap(aliases = &["delete"])]
Del(DelCommand),

/// Initialize the config file in the current directory.
Init(InitCommand),

/// Replace a template matcher with another, e.g. zetch -> zet
ReplaceMatcher(ReplaceMatcherCommand),
/// Read a finalised context variable from the config file.
Var(ReadVarCommand),
/// Write to various file types from the command line, preserving comments and existing formatting where possible.
File(FileCommand),

/// Display zetch's version
Version {
#[arg(long, value_enum, default_value = "text")]
Expand Down Expand Up @@ -116,7 +126,7 @@ pub enum ReadOutputFormat {
}

#[derive(Clone, Debug, clap::Parser)]
pub struct ReadVarCommand {
pub struct VarCommand {
/// The context variable from the config file to read.
#[clap()]
pub var: String,
Expand All @@ -129,44 +139,75 @@ pub struct ReadVarCommand {
pub output: ReadOutputFormat,
}

#[derive(Clone, Debug, clap::Parser)]
pub struct FileCommand {
/// The file to write to.
pub filepath: PathBuf,
/// Shared arguments for read, put and del commands.
#[derive(Clone, Debug, clap::Args)]
pub struct FileSharedArgs {
/// The filepath to read/modify, or the file contents as a string.
///
/// When the source provided is a string,
/// put and del will output the modified contents to stdout,
/// rather than writing to the file.
pub source: String,

/// The '.' separated path to write to. E.g. 'context.env.foo.default' or '.' for the file root. Any contents at this path will be overwritten.
pub path: String,
/// The '.' separated path from the input to read, delete or put to.
///
/// E.g. 'context.env.foo.default'.
pub content_path: String,

/// The value to write to the given path.
#[clap(short = 'p', long = "put")]
pub put: Option<String>,
/// The filetype being read, should be specified when the filetype cannot be inferred automatically.
#[clap(long = "json", default_value = "false")]
pub json: bool,

/// The filetype being read, should be specified when the filetype cannot be inferred automatically.
#[clap(long = "yaml", alias = "yml", default_value = "false")]
pub yaml: bool,

#[clap(short = 'd', long = "delete")]
pub delete: bool,
/// The filetype being read, should be specified when the filetype cannot be inferred automatically.
#[clap(long = "toml", default_value = "false")]
pub toml: bool,
}

/// By default all values will be treated as strings, use this flag to coerce the value as a different type. Same usage as coerce in config.
#[derive(Clone, Debug, clap::Parser)]
pub struct ReadCommand {
#[clap(flatten)]
pub shared: FileSharedArgs,

/// By default all values will be treated as strings, use this flag to coerce the value as a different type.
///
/// Hint: same usage as coerce in config.
#[clap(long = "coerce")]
pub coerce: Option<Coerce>,

/// Read only: the output format to print in.
/// The output format to print to stdout in.
///
/// - raw (default) -> same as json except simple string output is printed without quotes, to allow for easier command chaining.
///
/// - json -> json compatible output.
#[arg(short, long, default_value = "raw")]
pub output: ReadOutputFormat,
}

/// The filetype being read, should be specified when the filetype cannot be inferred automatically.
#[clap(long = "json", default_value = "false")]
pub json: bool,
#[derive(Clone, Debug, clap::Parser)]
pub struct PutCommand {
#[clap(flatten)]
pub shared: FileSharedArgs,

/// The filetype being read, should be specified when the filetype cannot be inferred automatically.
#[clap(long = "yaml", alias = "yml", default_value = "false")]
pub yaml: bool,
/// The value to write to the given path.
///
/// By default treated as a string, use the --coerce flag to coerce the value as a different type.
pub put: String,

/// The filetype being read, should be specified when the filetype cannot be inferred automatically.
#[clap(long = "toml", default_value = "false")]
pub toml: bool,
/// By default all values will be treated as strings, use this flag to coerce the value as a different type.
///
/// Hint: same usage as coerce in config.
#[clap(long = "coerce")]
pub coerce: Option<Coerce>,
}

#[derive(Clone, Debug, clap::Parser)]
pub struct DelCommand {
#[clap(flatten)]
pub shared: FileSharedArgs,
}

#[derive(Clone, Debug, clap::Parser)]
Expand Down
1 change: 1 addition & 0 deletions py_rust/src/config/tasks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ impl Task {
})?;

// Create the bash environment:
error!("CONFIG DIR: {}", config_dir.display());
let mut bash = Bash::new().chdir(config_dir);
bash = bash.env(IN_TASK_ENV_VAR, "1");
if let Some(cached_config_loc) = cached_config_loc {
Expand Down
9 changes: 5 additions & 4 deletions py_rust/src/read_write/delete.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
use super::{filetype::FileType, langs, raise_invalid_path, traverser::TravNode};
use crate::{args::FileCommand, prelude::*};
use super::{filetype::FileType, langs, raise_invalid_path, source::Source, traverser::TravNode};
use crate::{args::DelCommand, prelude::*};

/// Handle deletions.
///
/// Note the file should already be checked to be valid for the given type and so the initial load should raise InternalError if it fails (aka it shouldn't fail.)
pub fn handle_delete(
fargs: &FileCommand,
_fargs: &DelCommand,
path: &[&str],
ft: FileType,
file_contents: String,
source: Source,
) -> Result<(), Zerr> {
let mut manager = langs::Manager::new(ft, &file_contents)?;

Expand Down Expand Up @@ -57,7 +58,7 @@ pub fn handle_delete(
};

// Rewrite the file:
std::fs::write(&fargs.filepath, manager.rewrite()?).change_context(Zerr::InternalError)?;
source.write(&manager.rewrite()?)?;

Ok(())
}
46 changes: 33 additions & 13 deletions py_rust/src/read_write/filetype.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use strum::IntoEnumIterator;

use crate::{args::FileCommand, prelude::*};
use super::source::Source;
use crate::{args::FileSharedArgs, prelude::*};

/// Supported filetypes to process.
#[derive(Debug, strum::EnumIter, Copy, Clone)]
Expand All @@ -10,6 +11,8 @@ pub enum FileType {
Toml,
}

pub static VALID_FILE_EXTS_AND_OPTS: &[&str] = &["json", "yaml", "yml", "toml"];

impl FileType {
fn validate_file(&self, contents: &str) -> Result<(), Zerr> {
match self {
Expand All @@ -35,38 +38,55 @@ impl FileType {
}
}

pub fn valid_ft_opts_str() -> String {
let mut s = "".to_string();
for (index, ft) in VALID_FILE_EXTS_AND_OPTS.iter().enumerate() {
if index == VALID_FILE_EXTS_AND_OPTS.len() - 1 {
s.push_str(&format!("or '--{}'", ft));
} else {
s.push_str(&format!("'--{}', ", ft));
}
}
s
}

/// Infers the filetype and validates the file is readable as that type.
pub fn get_filetype(
_args: &crate::args::Args,
fargs: &FileCommand,
sargs: &FileSharedArgs,
file_contents: &str,
source: &Source,
) -> Result<FileType, Zerr> {
let ft = if [fargs.json, fargs.yaml, fargs.toml]
let ft = if [sargs.json, sargs.yaml, sargs.toml]
.iter()
.filter(|x| **x)
.count()
> 1
{
return Err(zerr!(
Zerr::FileCmdUsageError,
"Only one of '--json', '--yaml' or '--toml' can be specified."
"Only one of {} can be specified.",
valid_ft_opts_str()
));
} else if fargs.json {
} else if sargs.json {
FileType::Json
} else if fargs.yaml {
} else if sargs.yaml {
FileType::Yaml
} else if fargs.toml {
} else if sargs.toml {
FileType::Toml
} else {
// No specific given, need to infer:

// First try to infer from the file extension:
let maybe_ft = if let Some(ext) = fargs.filepath.extension() {
let maybe_ft = if let Some(ext) = source.file_ext() {
let ext = ext.to_str().ok_or_else(|| {
zerr!(
Zerr::InternalError,
zerr_int!(
"Could not read file extension for file: '{}'.",
fargs.filepath.display()
if let Some(fp) = source.filepath() {
fp.display().to_string()
} else {
"(contents passed in from command line)".to_string()
}
)
})?;

Expand Down Expand Up @@ -106,9 +126,9 @@ pub fn get_filetype(
Zerr::FileCmdUsageError,
"{}",
if num_succ == 0 {
"Filetype could not be inferred automatically when file extension is unknown, parsing failed using all filetype parsers."
"Filetype could not be inferred automatically when file extension is unknown, parsing failed using all filetype parsers.".to_string()
} else {
"Filetype could not be inferred automatically when file extension is unknown, multiple filetype parsers can decode the contents.\nSpecify e.g. --json, --yaml or --toml to disambiguate."
format!("Filetype could not be inferred automatically when file extension is unknown, multiple filetype parsers can decode the contents.\nSpecify e.g. {} to disambiguate.", valid_ft_opts_str())
}
);

Expand Down
Loading
Loading