Compare commits

..

3 Commits

Author SHA1 Message Date
b4e0b53cde fix: select 2026-01-14 17:38:15 +08:00
6cd87aa653 chore: tidy 2026-01-14 17:38:12 +08:00
a8683e720b fix(codegen): string escape 2026-01-12 17:44:19 +08:00
9 changed files with 151 additions and 159 deletions

View File

@@ -46,44 +46,50 @@ export const resolvePath = (path: NixValue): string => {
return Deno.core.ops.op_resolve_path(path_str); return Deno.core.ops.op_resolve_path(path_str);
}; };
/** export const select = (obj: NixValue, attrpath: NixValue[]): NixValue => {
* Select an attribute from an attribute set let attrs = forceAttrs(obj);
* Used by codegen for attribute access (e.g., obj.key)
*
* @param obj - Attribute set to select from
* @param key - Key to select
* @returns The value at obj[key]
* @throws Error if obj is null/undefined or key not found
*/
export const select = (obj: NixValue, key: NixValue): NixValue => {
const forced_obj = forceAttrs(obj);
const forced_key = forceString(key);
if (!(forced_key in forced_obj)) { for (const attr of attrpath.slice(0, -1)) {
throw new Error(`Attribute '${forced_key}' not found`); const key = forceString(attr)
if (!(key in attrs)) {
throw new Error(`Attribute '${key}' not found`);
}
const cur = force(attrs[forceString(attr)]);
if (!isAttrs(cur)) {
// throw new Error(`Attribute '${forced_key}' not found`);
// FIXME: error
throw new Error(`Attribute not found`);
}
attrs = cur;
} }
return forced_obj[forced_key]; const last = forceString(attrpath[attrpath.length - 1])
if (!(last in attrs)) {
throw new Error(`Attribute '${last}' not found`);
}
return attrs[last];
}; };
/** export const selectWithDefault = (obj: NixValue, attrpath: NixValue[], default_val: NixValue): NixValue => {
* Select an attribute with a default value let attrs = forceAttrs(obj);
* Used for Nix's `obj.key or default` syntax
*
* @param obj - Attribute set to select from
* @param key - Key to select
* @param default_val - Value to return if key not found (will be forced if it's a thunk)
* @returns obj[key] if exists, otherwise force(default_val)
*/
export const selectWithDefault = (obj: NixValue, key: NixValue, default_val: NixValue): NixValue => {
const attrs = forceAttrs(obj);
const forced_key = forceString(key);
if (!(forced_key in attrs)) { for (const attr of attrpath.slice(0, -1)) {
return force(default_val); const key = forceString(attr)
if (!(key in attrs)) {
return default_val
}
const cur = force(attrs[key]);
if (!isAttrs(cur)) {
return default_val;
}
attrs = cur;
} }
return attrs[forced_key]; const last = forceString(attrpath[attrpath.length - 1]);
if (last in attrs) {
return attrs[last]
}
return default_val;
}; };
export const hasAttr = (obj: NixValue, attrpath: NixValue[]): NixBool => { export const hasAttr = (obj: NixValue, attrpath: NixValue[]): NixBool => {
@@ -93,14 +99,14 @@ export const hasAttr = (obj: NixValue, attrpath: NixValue[]): NixBool => {
let attrs = obj; let attrs = obj;
for (const attr of attrpath.slice(0, -1)) { for (const attr of attrpath.slice(0, -1)) {
const cur = attrs[forceString(attr)]; const cur = force(attrs[forceString(attr)]);
if (!isAttrs(cur)) { if (!isAttrs(cur)) {
return false; return false;
} }
attrs = cur; attrs = cur;
} }
return true; return forceString(attrpath[attrpath.length - 1]) in attrs;
}; };
/** /**

View File

@@ -11,21 +11,27 @@ pub(crate) trait CodegenContext {
fn get_sym(&self, id: SymId) -> &str; fn get_sym(&self, id: SymId) -> &str;
} }
fn escape_quote_string(s: &str) -> String { trait EscapeQuote {
let mut escaped = String::with_capacity(s.len() + 2); fn escape_quote(&self) -> String;
escaped.push('"'); }
for c in s.chars() {
match c { impl EscapeQuote for str {
'\\' => escaped.push_str("\\\\"), fn escape_quote(&self) -> String {
'\"' => escaped.push_str("\\\""), let mut escaped = String::with_capacity(self.len() + 2);
'\n' => escaped.push_str("\\n"), escaped.push('"');
'\r' => escaped.push_str("\\r"), for c in self.chars() {
'\t' => escaped.push_str("\\t"), match c {
_ => escaped.push(c), '\\' => escaped.push_str("\\\\"),
'\"' => escaped.push_str("\\\""),
'\n' => escaped.push_str("\\n"),
'\r' => escaped.push_str("\\r"),
'\t' => escaped.push_str("\\t"),
_ => escaped.push(c),
}
} }
escaped.push('"');
escaped
} }
escaped.push('"');
escaped
} }
impl<Ctx: CodegenContext> Compile<Ctx> for Ir { impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
@@ -33,7 +39,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
match self { match self {
Ir::Int(int) => format!("{int}n"), // Generate BigInt literal Ir::Int(int) => format!("{int}n"), // Generate BigInt literal
Ir::Float(float) => float.to_string(), Ir::Float(float) => float.to_string(),
Ir::Str(s) => escape_quote_string(&s.val), Ir::Str(s) => s.val.escape_quote(),
Ir::Path(p) => { Ir::Path(p) => {
// Path needs runtime resolution for interpolated paths // Path needs runtime resolution for interpolated paths
let path_expr = ctx.get_ir(p.expr).compile(ctx); let path_expr = ctx.get_ir(p.expr).compile(ctx);
@@ -62,7 +68,9 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
format!("expr{}", expr_id.0) format!("expr{}", expr_id.0)
} }
Ir::Builtins(_) => "Nix.builtins".to_string(), Ir::Builtins(_) => "Nix.builtins".to_string(),
&Ir::Builtin(Builtin(name)) => format!("Nix.builtins[\"{}\"]", ctx.get_sym(name)), &Ir::Builtin(Builtin(name)) => {
format!("Nix.builtins[{}]", ctx.get_sym(name).escape_quote())
}
Ir::ConcatStrings(x) => x.compile(ctx), Ir::ConcatStrings(x) => x.compile(ctx),
Ir::HasAttr(x) => x.compile(ctx), Ir::HasAttr(x) => x.compile(ctx),
&Ir::Assert(Assert { assertion, expr }) => { &Ir::Assert(Assert { assertion, expr }) => {
@@ -145,7 +153,7 @@ impl Func {
let required = if let Some(req) = &self.param.required { let required = if let Some(req) = &self.param.required {
let keys: Vec<_> = req let keys: Vec<_> = req
.iter() .iter()
.map(|&sym| format!("\"{}\"", ctx.get_sym(sym))) .map(|&sym| ctx.get_sym(sym).escape_quote())
.collect(); .collect();
format!("[{}]", keys.join(",")) format!("[{}]", keys.join(","))
} else { } else {
@@ -156,7 +164,7 @@ impl Func {
let allowed = if let Some(allow) = &self.param.allowed { let allowed = if let Some(allow) = &self.param.allowed {
let keys: Vec<_> = allow let keys: Vec<_> = allow
.iter() .iter()
.map(|&sym| format!("\"{}\"", ctx.get_sym(sym))) .map(|&sym| ctx.get_sym(sym).escape_quote())
.collect(); .collect();
format!("[{}]", keys.join(",")) format!("[{}]", keys.join(","))
} else { } else {
@@ -217,46 +225,20 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Let {
impl<Ctx: CodegenContext> Compile<Ctx> for Select { impl<Ctx: CodegenContext> Compile<Ctx> for Select {
fn compile(&self, ctx: &Ctx) -> String { fn compile(&self, ctx: &Ctx) -> String {
let expr = ctx.get_ir(self.expr).compile(ctx); let lhs = ctx.get_ir(self.expr).compile(ctx);
let attrpath = self
let mut result = expr; .attrpath
let attr_count = self.attrpath.len(); .iter()
.map(|attr| match attr {
for (i, attr) in self.attrpath.iter().enumerate() { Attr::Str(sym) => ctx.get_sym(*sym).escape_quote(),
let is_last = i == attr_count - 1; Attr::Dynamic(expr_id) => ctx.get_ir(*expr_id).compile(ctx),
result = match attr { })
Attr::Str(sym) => { .join(",");
let key = ctx.get_sym(*sym); if let Some(default) = self.default {
if let Some(default) = self.default format!("Nix.selectWithDefault({lhs}, [{attrpath}], {})", ctx.get_ir(default).compile(ctx))
&& is_last } else {
{ format!("Nix.select({lhs}, [{attrpath}])")
let default_val = ctx.get_ir(default).compile(ctx);
format!(
"Nix.selectWithDefault({}, \"{}\", {})",
result, key, default_val
)
} else {
format!("Nix.select({}, \"{}\")", result, key)
}
}
Attr::Dynamic(expr_id) => {
let key = ctx.get_ir(*expr_id).compile(ctx);
if let Some(default) = self.default
&& is_last
{
let default_val = ctx.get_ir(default).compile(ctx);
format!(
"Nix.selectWithDefault({}, {}, {})",
result, key, default_val
)
} else {
format!("Nix.select({}, {})", result, key)
}
}
};
} }
result
} }
} }
@@ -267,7 +249,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for AttrSet {
for (&sym, &expr) in &self.stcs { for (&sym, &expr) in &self.stcs {
let key = ctx.get_sym(sym); let key = ctx.get_sym(sym);
let value = ctx.get_ir(expr).compile(ctx); let value = ctx.get_ir(expr).compile(ctx);
attrs.push(format!("{}: {}", escape_quote_string(key), value)); attrs.push(format!("{}: {}", key.escape_quote(), value));
} }
for (key_expr, value_expr) in &self.dyns { for (key_expr, value_expr) in &self.dyns {
@@ -310,9 +292,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for HasAttr {
.rhs .rhs
.iter() .iter()
.map(|attr| match attr { .map(|attr| match attr {
Attr::Str(sym) => { Attr::Str(sym) => ctx.get_sym(*sym).escape_quote(),
format!("\"{}\"", ctx.get_sym(*sym))
}
Attr::Dynamic(expr_id) => ctx.get_ir(*expr_id).compile(ctx), Attr::Dynamic(expr_id) => ctx.get_ir(*expr_id).compile(ctx),
}) })
.join(","); .join(",");

View File

@@ -105,8 +105,11 @@ impl DowngradeContext for DowngradeCtx<'_> {
.rposition(|t| t.current_binding.is_some()); .rposition(|t| t.current_binding.is_some());
// Record dependency if both exist // Record dependency if both exist
if let (Some(expr_idx), Some(curr_idx)) = (expr_tracker_idx, current_tracker_idx) { if let (Some(expr_idx), Some(curr_idx)) =
let current_binding = self.dep_tracker_stack[curr_idx].current_binding.unwrap(); (expr_tracker_idx, current_tracker_idx)
{
let current_binding =
self.dep_tracker_stack[curr_idx].current_binding.unwrap();
let owner_binding = self.dep_tracker_stack[curr_idx].owner_binding; let owner_binding = self.dep_tracker_stack[curr_idx].owner_binding;
// If referencing from inner scope to outer scope // If referencing from inner scope to outer scope
@@ -120,13 +123,13 @@ impl DowngradeContext for DowngradeCtx<'_> {
tracker.graph.add_edge(from_node, to_node, ()); tracker.graph.add_edge(from_node, to_node, ());
} else if curr_idx > expr_idx { } else if curr_idx > expr_idx {
// Cross-scope reference: use owner_binding if available // Cross-scope reference: use owner_binding if available
if let Some(owner) = owner_binding { if let Some(owner) = owner_binding
if let (Some(&from_node), Some(&to_node)) = ( && let (Some(&from_node), Some(&to_node)) = (
tracker.expr_to_node.get(&owner), tracker.expr_to_node.get(&owner),
tracker.expr_to_node.get(&expr), tracker.expr_to_node.get(&expr),
) { )
tracker.graph.add_edge(from_node, to_node, ()); {
} tracker.graph.add_edge(from_node, to_node, ());
} }
} }
} }
@@ -273,7 +276,9 @@ impl DowngradeContext for DowngradeCtx<'_> {
} }
fn get_current_binding(&self) -> Option<ExprId> { fn get_current_binding(&self) -> Option<ExprId> {
self.dep_tracker_stack.last().and_then(|t| t.current_binding) self.dep_tracker_stack
.last()
.and_then(|t| t.current_binding)
} }
fn set_current_binding(&mut self, expr: Option<ExprId>) { fn set_current_binding(&mut self, expr: Option<ExprId>) {

View File

@@ -121,10 +121,7 @@ impl Error {
Self::new(ErrorKind::DowngradeError(msg)) Self::new(ErrorKind::DowngradeError(msg))
} }
pub fn eval_error(msg: String, backtrace: Option<String>) -> Self { pub fn eval_error(msg: String, backtrace: Option<String>) -> Self {
Self::new(ErrorKind::EvalError { Self::new(ErrorKind::EvalError { msg, backtrace })
msg,
backtrace
})
} }
pub fn internal(msg: String) -> Self { pub fn internal(msg: String) -> Self {
Self::new(ErrorKind::InternalError(msg)) Self::new(ErrorKind::InternalError(msg))

View File

@@ -57,20 +57,16 @@ pub fn op_fetch_url(
let cache = FetcherCache::new().map_err(|e| NixError::from(e.to_string()))?; let cache = FetcherCache::new().map_err(|e| NixError::from(e.to_string()))?;
let downloader = Downloader::new(); let downloader = Downloader::new();
let file_name = name.unwrap_or_else(|| { let file_name =
url.rsplit('/') name.unwrap_or_else(|| url.rsplit('/').next().unwrap_or("download").to_string());
.next()
.unwrap_or("download")
.to_string()
});
if let Some(ref hash) = expected_hash { if let Some(ref hash) = expected_hash
if let Some(cached) = cache.get_url(&url, hash) { && let Some(cached) = cache.get_url(&url, hash)
return Ok(FetchUrlResult { {
store_path: cached.to_string_lossy().to_string(), return Ok(FetchUrlResult {
hash: hash.clone(), store_path: cached.to_string_lossy().to_string(),
}); hash: hash.clone(),
} });
} }
let data = downloader let data = downloader
@@ -111,11 +107,6 @@ pub fn op_fetch_tarball(
let dir_name = name.unwrap_or_else(|| "source".to_string()); let dir_name = name.unwrap_or_else(|| "source".to_string());
let is_nar_hash = expected_hash
.as_ref()
.map(|h| h.starts_with("sha256-"))
.unwrap_or(false);
if let Some(ref hash) = expected_hash { if let Some(ref hash) = expected_hash {
let normalized = normalize_hash(hash); let normalized = normalize_hash(hash);
if let Some(cached) = cache.get_tarball(&url, &normalized) { if let Some(cached) = cache.get_tarball(&url, &normalized) {
@@ -134,17 +125,16 @@ pub fn op_fetch_tarball(
let extracted_path = archive::extract_archive(&data, &temp_dir.path().to_path_buf()) let extracted_path = archive::extract_archive(&data, &temp_dir.path().to_path_buf())
.map_err(|e| NixError::from(e.to_string()))?; .map_err(|e| NixError::from(e.to_string()))?;
let nar_hash = nar::compute_nar_hash(&extracted_path) let nar_hash =
.map_err(|e| NixError::from(e.to_string()))?; nar::compute_nar_hash(&extracted_path).map_err(|e| NixError::from(e.to_string()))?;
if let Some(ref expected) = expected_hash { if let Some(ref expected) = expected_hash {
let normalized_expected = normalize_hash(expected); let normalized_expected = normalize_hash(expected);
let hash_to_compare = if is_nar_hash { &nar_hash } else { &nar_hash };
if *hash_to_compare != normalized_expected { if nar_hash != normalized_expected {
return Err(NixError::from(format!( return Err(NixError::from(format!(
"hash mismatch for '{}': expected {}, got {}", "hash mismatch for '{}': expected {}, got {}",
url, normalized_expected, hash_to_compare url, normalized_expected, nar_hash
))); )));
} }
} }
@@ -173,8 +163,17 @@ pub fn op_fetch_git(
let cache = FetcherCache::new().map_err(|e| NixError::from(e.to_string()))?; let cache = FetcherCache::new().map_err(|e| NixError::from(e.to_string()))?;
let dir_name = name.unwrap_or_else(|| "source".to_string()); let dir_name = name.unwrap_or_else(|| "source".to_string());
git::fetch_git(&cache, &url, git_ref.as_deref(), rev.as_deref(), shallow, submodules, all_refs, &dir_name) git::fetch_git(
.map_err(|e| NixError::from(e.to_string())) &cache,
&url,
git_ref.as_deref(),
rev.as_deref(),
shallow,
submodules,
all_refs,
&dir_name,
)
.map_err(|e| NixError::from(e.to_string()))
} }
#[op2] #[op2]
@@ -187,17 +186,15 @@ pub fn op_fetch_hg(
let cache = FetcherCache::new().map_err(|e| NixError::from(e.to_string()))?; let cache = FetcherCache::new().map_err(|e| NixError::from(e.to_string()))?;
let dir_name = name.unwrap_or_else(|| "source".to_string()); let dir_name = name.unwrap_or_else(|| "source".to_string());
hg::fetch_hg(&cache, &url, rev.as_deref(), &dir_name) hg::fetch_hg(&cache, &url, rev.as_deref(), &dir_name).map_err(|e| NixError::from(e.to_string()))
.map_err(|e| NixError::from(e.to_string()))
} }
fn normalize_hash(hash: &str) -> String { fn normalize_hash(hash: &str) -> String {
if hash.starts_with("sha256-") { if hash.starts_with("sha256-")
if let Some(b64) = hash.strip_prefix("sha256-") { && let Some(b64) = hash.strip_prefix("sha256-")
if let Ok(bytes) = base64_decode(b64) { && let Ok(bytes) = base64_decode(b64)
return hex::encode(bytes); {
} return hex::encode(bytes);
}
} }
hash.to_string() hash.to_string()
} }
@@ -212,7 +209,9 @@ fn base64_decode(input: &str) -> Result<Vec<u8>, String> {
let mut bits = 0; let mut bits = 0;
for c in input.bytes() { for c in input.bytes() {
let value = ALPHABET.iter().position(|&x| x == c) let value = ALPHABET
.iter()
.position(|&x| x == c)
.ok_or_else(|| format!("Invalid base64 character: {}", c as char))?; .ok_or_else(|| format!("Invalid base64 character: {}", c as char))?;
buffer = (buffer << 6) | (value as u32); buffer = (buffer << 6) | (value as u32);

View File

@@ -1,6 +1,6 @@
use std::fs::{self, File}; use std::fs::{self, File};
use std::io::Cursor; use std::io::Cursor;
use std::path::PathBuf; use std::path::{Path, PathBuf};
use flate2::read::GzDecoder; use flate2::read::GzDecoder;
@@ -48,7 +48,7 @@ impl ArchiveFormat {
} }
} }
pub fn extract_archive(data: &[u8], dest: &PathBuf) -> Result<PathBuf, ArchiveError> { pub fn extract_archive(data: &[u8], dest: &Path) -> Result<PathBuf, ArchiveError> {
let format = ArchiveFormat::detect("", data); let format = ArchiveFormat::detect("", data);
let temp_dir = dest.join("_extract_temp"); let temp_dir = dest.join("_extract_temp");
@@ -65,34 +65,34 @@ pub fn extract_archive(data: &[u8], dest: &PathBuf) -> Result<PathBuf, ArchiveEr
strip_single_toplevel(&temp_dir, dest) strip_single_toplevel(&temp_dir, dest)
} }
fn extract_tar_gz(data: &[u8], dest: &PathBuf) -> Result<(), ArchiveError> { fn extract_tar_gz(data: &[u8], dest: &Path) -> Result<(), ArchiveError> {
let decoder = GzDecoder::new(Cursor::new(data)); let decoder = GzDecoder::new(Cursor::new(data));
let mut archive = tar::Archive::new(decoder); let mut archive = tar::Archive::new(decoder);
archive.unpack(dest)?; archive.unpack(dest)?;
Ok(()) Ok(())
} }
fn extract_tar_xz(data: &[u8], dest: &PathBuf) -> Result<(), ArchiveError> { fn extract_tar_xz(data: &[u8], dest: &Path) -> Result<(), ArchiveError> {
let decoder = xz2::read::XzDecoder::new(Cursor::new(data)); let decoder = xz2::read::XzDecoder::new(Cursor::new(data));
let mut archive = tar::Archive::new(decoder); let mut archive = tar::Archive::new(decoder);
archive.unpack(dest)?; archive.unpack(dest)?;
Ok(()) Ok(())
} }
fn extract_tar_bz2(data: &[u8], dest: &PathBuf) -> Result<(), ArchiveError> { fn extract_tar_bz2(data: &[u8], dest: &Path) -> Result<(), ArchiveError> {
let decoder = bzip2::read::BzDecoder::new(Cursor::new(data)); let decoder = bzip2::read::BzDecoder::new(Cursor::new(data));
let mut archive = tar::Archive::new(decoder); let mut archive = tar::Archive::new(decoder);
archive.unpack(dest)?; archive.unpack(dest)?;
Ok(()) Ok(())
} }
fn extract_tar(data: &[u8], dest: &PathBuf) -> Result<(), ArchiveError> { fn extract_tar(data: &[u8], dest: &Path) -> Result<(), ArchiveError> {
let mut archive = tar::Archive::new(Cursor::new(data)); let mut archive = tar::Archive::new(Cursor::new(data));
archive.unpack(dest)?; archive.unpack(dest)?;
Ok(()) Ok(())
} }
fn extract_zip(data: &[u8], dest: &PathBuf) -> Result<(), ArchiveError> { fn extract_zip(data: &[u8], dest: &Path) -> Result<(), ArchiveError> {
let cursor = Cursor::new(data); let cursor = Cursor::new(data);
let mut archive = zip::ZipArchive::new(cursor)?; let mut archive = zip::ZipArchive::new(cursor)?;
@@ -122,7 +122,7 @@ fn extract_zip(data: &[u8], dest: &PathBuf) -> Result<(), ArchiveError> {
Ok(()) Ok(())
} }
fn strip_single_toplevel(temp_dir: &PathBuf, dest: &PathBuf) -> Result<PathBuf, ArchiveError> { fn strip_single_toplevel(temp_dir: &Path, dest: &Path) -> Result<PathBuf, ArchiveError> {
let entries: Vec<_> = fs::read_dir(temp_dir)? let entries: Vec<_> = fs::read_dir(temp_dir)?
.filter_map(|e| e.ok()) .filter_map(|e| e.ok())
.filter(|e| !e.file_name().to_string_lossy().starts_with('.')) .filter(|e| !e.file_name().to_string_lossy().starts_with('.'))
@@ -131,7 +131,7 @@ fn strip_single_toplevel(temp_dir: &PathBuf, dest: &PathBuf) -> Result<PathBuf,
let source_dir = if entries.len() == 1 && entries[0].file_type()?.is_dir() { let source_dir = if entries.len() == 1 && entries[0].file_type()?.is_dir() {
entries[0].path() entries[0].path()
} else { } else {
temp_dir.clone() temp_dir.to_path_buf()
}; };
let final_dest = dest.join("content"); let final_dest = dest.join("content");
@@ -149,7 +149,7 @@ fn strip_single_toplevel(temp_dir: &PathBuf, dest: &PathBuf) -> Result<PathBuf,
Ok(final_dest) Ok(final_dest)
} }
fn copy_dir_recursive(src: &PathBuf, dst: &PathBuf) -> Result<(), std::io::Error> { fn copy_dir_recursive(src: &Path, dst: &Path) -> Result<(), std::io::Error> {
fs::create_dir_all(dst)?; fs::create_dir_all(dst)?;
for entry in fs::read_dir(src)? { for entry in fs::read_dir(src)? {
@@ -194,7 +194,9 @@ impl std::fmt::Display for ArchiveError {
match self { match self {
ArchiveError::IoError(e) => write!(f, "I/O error: {}", e), ArchiveError::IoError(e) => write!(f, "I/O error: {}", e),
ArchiveError::ZipError(e) => write!(f, "ZIP error: {}", e), ArchiveError::ZipError(e) => write!(f, "ZIP error: {}", e),
ArchiveError::UnsupportedFormat(fmt) => write!(f, "Unsupported archive format: {}", fmt), ArchiveError::UnsupportedFormat(fmt) => {
write!(f, "Unsupported archive format: {}", fmt)
}
} }
} }
} }

View File

@@ -97,7 +97,8 @@ impl FetcherCache {
return None; return None;
} }
let meta: CacheMetadata = serde_json::from_str(&fs::read_to_string(&meta_path).ok()?).ok()?; let meta: CacheMetadata =
serde_json::from_str(&fs::read_to_string(&meta_path).ok()?).ok()?;
if meta.hash == expected_hash { if meta.hash == expected_hash {
Some(data_path) Some(data_path)
@@ -166,7 +167,8 @@ impl FetcherCache {
return None; return None;
} }
let meta: CacheMetadata = serde_json::from_str(&fs::read_to_string(&meta_path).ok()?).ok()?; let meta: CacheMetadata =
serde_json::from_str(&fs::read_to_string(&meta_path).ok()?).ok()?;
if meta.hash == expected_hash { if meta.hash == expected_hash {
Some(self.make_store_path(&meta.hash, &meta.name)) Some(self.make_store_path(&meta.hash, &meta.name))

View File

@@ -2,8 +2,8 @@ use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command; use std::process::Command;
use super::cache::FetcherCache;
use super::FetchGitResult; use super::FetchGitResult;
use super::cache::FetcherCache;
pub fn fetch_git( pub fn fetch_git(
cache: &FetcherCache, cache: &FetcherCache,
@@ -72,10 +72,7 @@ fn fetch_repo(repo: &PathBuf, all_refs: bool) -> Result<(), GitError> {
args.push("--all"); args.push("--all");
} }
let output = Command::new("git") let output = Command::new("git").args(args).current_dir(repo).output()?;
.args(args)
.current_dir(repo)
.output()?;
if !output.status.success() { if !output.status.success() {
return Err(GitError::CommandFailed { return Err(GitError::CommandFailed {
@@ -87,7 +84,11 @@ fn fetch_repo(repo: &PathBuf, all_refs: bool) -> Result<(), GitError> {
Ok(()) Ok(())
} }
fn resolve_rev(repo: &PathBuf, git_ref: Option<&str>, rev: Option<&str>) -> Result<String, GitError> { fn resolve_rev(
repo: &PathBuf,
git_ref: Option<&str>,
rev: Option<&str>,
) -> Result<String, GitError> {
if let Some(rev) = rev { if let Some(rev) = rev {
return Ok(rev.to_string()); return Ok(rev.to_string());
} }
@@ -263,7 +264,7 @@ fn days_to_ymd(days: u64) -> (u64, u64, u64) {
} }
fn is_leap_year(y: u64) -> bool { fn is_leap_year(y: u64) -> bool {
(y % 4 == 0 && y % 100 != 0) || (y % 400 == 0) (y.is_multiple_of(4) && !y.is_multiple_of(100)) || y.is_multiple_of(400)
} }
trait Pipe: Sized { trait Pipe: Sized {

View File

@@ -2,8 +2,8 @@ use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command; use std::process::Command;
use super::cache::FetcherCache;
use super::FetchHgResult; use super::FetchHgResult;
use super::cache::FetcherCache;
pub fn fetch_hg( pub fn fetch_hg(
cache: &FetcherCache, cache: &FetcherCache,