alpm_pkginfo/commands.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
use std::{
fs::{File, create_dir_all, read_to_string},
io::{self, IsTerminal, Read, Write},
path::PathBuf,
str::FromStr,
};
use erased_serde::Serialize;
use crate::{
Error,
PackageInfoV1,
PackageInfoV2,
cli::{CreateCommand, OutputFormat},
};
/// Create a file according to a PKGINFO schema
///
/// ## Errors
///
/// Returns an error if one of the provided arguments is not valid, if creating output directory or
/// file is not possible, or if the output file can not be written to.
pub fn create_file(command: CreateCommand) -> Result<(), Error> {
let (data, output) = match command {
CreateCommand::V1 { args } => (
PackageInfoV1::new(
args.pkgname,
args.pkgbase,
args.pkgver,
args.pkgdesc,
args.url,
args.builddate,
args.packager,
args.size,
args.arch,
args.license,
args.replaces,
args.group,
args.conflict,
args.provides,
args.backup,
args.depend,
args.optdepend,
args.makedepend,
args.checkdepend,
)
.to_string(),
args.output,
),
CreateCommand::V2 { args, xdata } => (
PackageInfoV2::new(
args.pkgname,
args.pkgbase,
args.pkgver,
args.pkgdesc,
args.url,
args.builddate,
args.packager,
args.size,
args.arch,
args.license,
args.replaces,
args.group,
args.conflict,
args.provides,
args.backup,
args.depend,
args.optdepend,
args.makedepend,
args.checkdepend,
xdata,
)?
.to_string(),
args.output,
),
};
// create any parent directories if necessary
if let Some(output_dir) = output.0.parent() {
create_dir_all(output_dir).map_err(|e| {
Error::IoPathError(output_dir.to_path_buf(), "creating output directory", e)
})?;
}
let mut out = File::create(&output.0)
.map_err(|e| Error::IoPathError(output.0.clone(), "creating output file", e))?;
let _ = out
.write(data.as_bytes())
.map_err(|e| Error::IoPathError(output.0, "writing to output file", e))?;
Ok(())
}
/// Parses a file according to a PKGINFO schema.
///
/// Returns a serializable PackageInfo if the file is valid, otherwise an error is returned.
///
/// NOTE: If a command is piped to this process, the input is read from stdin.
/// See [`IsTerminal`] for more information about how terminal detection works.
///
/// [`IsTerminal`]: https://doc.rust-lang.org/stable/std/io/trait.IsTerminal.html
///
/// ## Errors
///
/// Returns an error if the file can not be read, if the file can not be parsed, or if the file is
/// not valid according to the schema.
pub fn parse(file: Option<PathBuf>) -> Result<Box<dyn Serialize>, Error> {
let contents = if let Some(file) = &file {
read_to_string(file)
.map_err(|e| Error::IoPathError(file.clone(), "reading file contents", e))?
} else if !io::stdin().is_terminal() {
let mut buffer = Vec::new();
let mut stdin = io::stdin();
stdin.read_to_end(&mut buffer).map_err(|e| {
Error::IoPathError(PathBuf::from("/dev/stdin"), "reading from stdin", e)
})?;
String::from_utf8(buffer)?.to_string()
} else {
return Err(Error::NoInputFile);
};
match PackageInfoV2::from_str(&contents) {
Ok(pkg_info) => Ok(Box::new(pkg_info)),
Err(_) => Ok(Box::new(PackageInfoV1::from_str(&contents)?)),
}
}
/// Validate a file according to a PKGINFO schema.
///
/// This is a thin wrapper around [`parse`] that only checks if the file is valid.
///
/// ## Errors
///
/// Returns an error if parsing `file` fails.
pub fn validate(file: Option<PathBuf>) -> Result<(), Error> {
let _ = parse(file)?;
Ok(())
}
/// Formats a file according to a PKGINFO schema.
///
/// Validates and prints the parsed file in the specified output format to stdout.
///
/// The output will be pretty-printed if the `pretty` flag is set to `true` and if the format
/// supports it.
///
/// ## Errors
///
/// Returns an error if parsing of `file` fails or if the output format can not be created.
pub fn format(
file: Option<PathBuf>,
output_format: OutputFormat,
pretty: bool,
) -> Result<(), Error> {
let pkg_info = parse(file)?;
match output_format {
OutputFormat::Json => {
println!(
"{}",
if pretty {
serde_json::to_string_pretty(&pkg_info)?
} else {
serde_json::to_string(&pkg_info)?
}
);
}
}
Ok(())
}