Compare commits
6 Commits
48a43bed55
...
cf4dd6c379
| Author | SHA1 | Date | |
|---|---|---|---|
| cf4dd6c379 | |||
| 31c7a62311 | |||
| ad5d047c01 | |||
| 795742e3d8 | |||
| 60cd61d771 | |||
| d95a6e509c |
1213
Cargo.lock
generated
1213
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
8
Justfile
8
Justfile
@@ -1,15 +1,15 @@
|
|||||||
[no-exit-message]
|
[no-exit-message]
|
||||||
@repl:
|
@repl:
|
||||||
RUST_LOG=none cargo run --bin repl
|
cargo run --bin repl
|
||||||
|
|
||||||
[no-exit-message]
|
[no-exit-message]
|
||||||
@eval expr:
|
@eval expr:
|
||||||
RUST_LOG=none cargo run --bin eval -- '{{expr}}'
|
cargo run --bin eval -- '{{expr}}'
|
||||||
|
|
||||||
[no-exit-message]
|
[no-exit-message]
|
||||||
@replr:
|
@replr:
|
||||||
RUST_LOG=none cargo run --bin repl --release
|
cargo run --bin repl --release
|
||||||
|
|
||||||
[no-exit-message]
|
[no-exit-message]
|
||||||
@evalr expr:
|
@evalr expr:
|
||||||
RUST_LOG=none cargo run --bin eval --release -- '{{expr}}'
|
cargo run --bin eval --release -- '{{expr}}'
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ let
|
|||||||
flake = (
|
flake = (
|
||||||
import flake-compat {
|
import flake-compat {
|
||||||
src = ./.;
|
src = ./.;
|
||||||
|
copySourceTreeToStore = false;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
in
|
in
|
||||||
|
|||||||
@@ -7,6 +7,6 @@ edition = "2024"
|
|||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
convert_case = "0.8"
|
convert_case = "0.11"
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
syn = { version = "2.0", features = ["full"] }
|
syn = { version = "2.0", features = ["full"] }
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ nix-compat = { git = "https://git.snix.dev/snix/snix.git", version = "0.1.0", fe
|
|||||||
|
|
||||||
# REPL
|
# REPL
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
rustyline = "14.0"
|
rustyline = "17.0"
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
@@ -31,7 +31,7 @@ itertools = "0.14"
|
|||||||
|
|
||||||
regex = "1.11"
|
regex = "1.11"
|
||||||
|
|
||||||
deno_core = "0.376"
|
deno_core = "0.385"
|
||||||
deno_error = "0.7"
|
deno_error = "0.7"
|
||||||
|
|
||||||
nix-nar = "0.3"
|
nix-nar = "0.3"
|
||||||
@@ -40,28 +40,29 @@ hex = "0.4"
|
|||||||
|
|
||||||
base64 = "0.22"
|
base64 = "0.22"
|
||||||
|
|
||||||
# Fetcher dependencies
|
reqwest = { version = "0.13", features = ["blocking", "rustls"], default-features = false }
|
||||||
reqwest = { version = "0.12", features = ["blocking", "rustls-tls"], default-features = false }
|
|
||||||
tar = "0.4"
|
tar = "0.4"
|
||||||
flate2 = "1.0"
|
flate2 = "1.0"
|
||||||
xz2 = "0.1"
|
xz2 = "0.1"
|
||||||
bzip2 = "0.5"
|
bzip2 = "0.6"
|
||||||
zip = "2.2"
|
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
dirs = "5.0"
|
# spec 1.0.0
|
||||||
|
toml = "0.9.9"
|
||||||
|
dirs = "6.0"
|
||||||
tempfile = "3.24"
|
tempfile = "3.24"
|
||||||
rusqlite = { version = "0.33", features = ["bundled"] }
|
rusqlite = { version = "0.38", features = ["bundled"] }
|
||||||
|
|
||||||
rnix = "0.12"
|
rnix = "0.13"
|
||||||
rowan = "0.15"
|
rowan = "0.16"
|
||||||
|
|
||||||
nix-js-macros = { path = "../nix-js-macros" }
|
nix-js-macros = { path = "../nix-js-macros" }
|
||||||
ere = "0.2.4"
|
ere = "0.2.4"
|
||||||
num_enum = "0.7.5"
|
num_enum = "0.7.5"
|
||||||
|
tap = "1.0.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = { version = "0.5", features = ["html_reports"] }
|
criterion = { version = "0.8", features = ["html_reports"] }
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "basic_ops"
|
name = "basic_ops"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use criterion::{Criterion, black_box, criterion_group, criterion_main};
|
use criterion::{Criterion, criterion_group, criterion_main};
|
||||||
|
use std::hint::black_box;
|
||||||
use utils::eval;
|
use utils::eval;
|
||||||
|
|
||||||
fn bench_arithmetic(c: &mut Criterion) {
|
fn bench_arithmetic(c: &mut Criterion) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use criterion::{Criterion, black_box, criterion_group, criterion_main};
|
use criterion::{Criterion, criterion_group, criterion_main};
|
||||||
|
use std::hint::black_box;
|
||||||
use utils::eval;
|
use utils::eval;
|
||||||
|
|
||||||
fn bench_builtin_math(c: &mut Criterion) {
|
fn bench_builtin_math(c: &mut Criterion) {
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use criterion::{Criterion, black_box, criterion_group, criterion_main};
|
use criterion::{Criterion, criterion_group, criterion_main};
|
||||||
use nix_js::context::Context;
|
use nix_js::context::Context;
|
||||||
|
use std::hint::black_box;
|
||||||
use utils::compile;
|
use utils::compile;
|
||||||
|
|
||||||
fn bench_parse_and_downgrade(c: &mut Criterion) {
|
fn bench_parse_and_downgrade(c: &mut Criterion) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use criterion::{Criterion, black_box, criterion_group, criterion_main};
|
use criterion::{Criterion, criterion_group, criterion_main};
|
||||||
|
use std::hint::black_box;
|
||||||
use utils::eval;
|
use utils::eval;
|
||||||
|
|
||||||
fn bench_non_recursive(c: &mut Criterion) {
|
fn bench_non_recursive(c: &mut Criterion) {
|
||||||
|
|||||||
@@ -6,55 +6,22 @@ import type { NixString, NixValue } from "../types";
|
|||||||
import { isStringWithContext, isNixPath } from "../types";
|
import { isStringWithContext, isNixPath } from "../types";
|
||||||
import { force } from "../thunk";
|
import { force } from "../thunk";
|
||||||
import { type NixStringContext, mkStringWithContext, addBuiltContext } from "../string-context";
|
import { type NixStringContext, mkStringWithContext, addBuiltContext } from "../string-context";
|
||||||
import { forceFunction } from "../type-assert";
|
import { forceFunction, forceStringNoCtx } from "../type-assert";
|
||||||
import { nixValueToJson } from "../conversion";
|
import { nixValueToJson } from "../conversion";
|
||||||
import { isAttrs, isPath, typeOf } from "./type-check";
|
import { isAttrs, isPath, typeOf } from "./type-check";
|
||||||
|
|
||||||
const convertJsonToNix = (json: unknown): NixValue => {
|
|
||||||
if (json === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (typeof json === "boolean") {
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
if (typeof json === "number") {
|
|
||||||
if (Number.isInteger(json)) {
|
|
||||||
return BigInt(json);
|
|
||||||
}
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
if (typeof json === "string") {
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
if (Array.isArray(json)) {
|
|
||||||
return json.map(convertJsonToNix);
|
|
||||||
}
|
|
||||||
if (typeof json === "object") {
|
|
||||||
const result: Record<string, NixValue> = {};
|
|
||||||
for (const [key, value] of Object.entries(json)) {
|
|
||||||
result[key] = convertJsonToNix(value);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
throw new TypeError(`unsupported JSON value type: ${typeof json}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const fromJSON = (e: NixValue): NixValue => {
|
export const fromJSON = (e: NixValue): NixValue => {
|
||||||
const str = force(e);
|
const str = force(e);
|
||||||
if (typeof str !== "string" && !isStringWithContext(str)) {
|
if (typeof str !== "string" && !isStringWithContext(str)) {
|
||||||
throw new TypeError(`builtins.fromJSON: expected a string, got ${typeOf(str)}`);
|
throw new TypeError(`builtins.fromJSON: expected a string, got ${typeOf(str)}`);
|
||||||
}
|
}
|
||||||
const jsonStr = isStringWithContext(str) ? str.value : str;
|
const jsonStr = isStringWithContext(str) ? str.value : str;
|
||||||
try {
|
return Deno.core.ops.op_from_json(jsonStr);
|
||||||
const parsed = JSON.parse(jsonStr);
|
|
||||||
return convertJsonToNix(parsed);
|
|
||||||
} catch (err) {
|
|
||||||
throw new SyntaxError(`builtins.fromJSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fromTOML = (e: NixValue): never => {
|
export const fromTOML = (e: NixValue): NixValue => {
|
||||||
throw new Error("Not implemented: fromTOML");
|
const toml = forceStringNoCtx(e);
|
||||||
|
return Deno.core.ops.op_from_toml(toml);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const toJSON = (e: NixValue): NixString => {
|
export const toJSON = (e: NixValue): NixString => {
|
||||||
|
|||||||
@@ -107,6 +107,13 @@ const extractArgs = (attrs: NixAttrs, outContext: NixStringContext): string[] =>
|
|||||||
return argsList.map((a) => coerceToString(a, StringCoercionMode.ToString, true, outContext));
|
return argsList.map((a) => coerceToString(a, StringCoercionMode.ToString, true, outContext));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const outputPathName = (drvName: string, output: string) => {
|
||||||
|
if (output === "out") {
|
||||||
|
return drvName
|
||||||
|
}
|
||||||
|
return `${drvName}-${output}`
|
||||||
|
}
|
||||||
|
|
||||||
const structuredAttrsExcludedKeys = new Set([
|
const structuredAttrsExcludedKeys = new Set([
|
||||||
"__structuredAttrs",
|
"__structuredAttrs",
|
||||||
"__ignoreNulls",
|
"__ignoreNulls",
|
||||||
@@ -296,7 +303,7 @@ export const derivationStrict = (args: NixValue): NixAttrs => {
|
|||||||
let drvPath: string;
|
let drvPath: string;
|
||||||
|
|
||||||
if (fixedOutputInfo) {
|
if (fixedOutputInfo) {
|
||||||
const pathName = Deno.core.ops.op_output_path_name(drvName, "out");
|
const pathName = outputPathName(drvName, "out");
|
||||||
const outPath = Deno.core.ops.op_make_fixed_output_path(
|
const outPath = Deno.core.ops.op_make_fixed_output_path(
|
||||||
fixedOutputInfo.hashAlgo,
|
fixedOutputInfo.hashAlgo,
|
||||||
fixedOutputInfo.hash,
|
fixedOutputInfo.hash,
|
||||||
@@ -374,7 +381,7 @@ export const derivationStrict = (args: NixValue): NixAttrs => {
|
|||||||
|
|
||||||
outputInfos = new Map<string, OutputInfo>();
|
outputInfos = new Map<string, OutputInfo>();
|
||||||
for (const outputName of outputs) {
|
for (const outputName of outputs) {
|
||||||
const pathName = Deno.core.ops.op_output_path_name(drvName, outputName);
|
const pathName = outputPathName(drvName, outputName);
|
||||||
const outPath = Deno.core.ops.op_make_store_path(`output:${outputName}`, drvModuloHash, pathName);
|
const outPath = Deno.core.ops.op_make_store_path(`output:${outputName}`, drvModuloHash, pathName);
|
||||||
outputInfos.set(outputName, {
|
outputInfos.set(outputName, {
|
||||||
path: outPath,
|
path: outPath,
|
||||||
|
|||||||
@@ -11,14 +11,15 @@ import {
|
|||||||
forceStringNoCtx,
|
forceStringNoCtx,
|
||||||
forceStringValue,
|
forceStringValue,
|
||||||
} from "../type-assert";
|
} from "../type-assert";
|
||||||
import type { NixValue, NixAttrs, NixPath } from "../types";
|
import type { NixValue, NixAttrs, NixPath, NixString } from "../types";
|
||||||
import { isNixPath, IS_PATH, CatchableError } from "../types";
|
import { isNixPath, IS_PATH, CatchableError } from "../types";
|
||||||
import { force } from "../thunk";
|
import { force } from "../thunk";
|
||||||
import { coerceToPath, coerceToString, StringCoercionMode } from "./conversion";
|
import { coerceToPath, coerceToString, StringCoercionMode } from "./conversion";
|
||||||
import { getPathValue } from "../path";
|
import { getPathValue } from "../path";
|
||||||
import type { NixStringContext, StringWithContext } from "../string-context";
|
import type { NixStringContext, StringWithContext } from "../string-context";
|
||||||
import { mkStringWithContext } from "../string-context";
|
import { mkStringWithContext, addOpaqueContext } from "../string-context";
|
||||||
import { isAttrs, isPath } from "./type-check";
|
import { isAttrs, isPath } from "./type-check";
|
||||||
|
import { baseNameOf } from "./path";
|
||||||
|
|
||||||
const importCache = new Map<string, NixValue>();
|
const importCache = new Map<string, NixValue>();
|
||||||
|
|
||||||
@@ -108,14 +109,6 @@ export interface FetchGitResult {
|
|||||||
nar_hash: string | null;
|
nar_hash: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FetchHgResult {
|
|
||||||
out_path: string;
|
|
||||||
branch: string;
|
|
||||||
rev: string;
|
|
||||||
short_rev: string;
|
|
||||||
rev_count: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const normalizeUrlInput = (
|
const normalizeUrlInput = (
|
||||||
args: NixValue,
|
args: NixValue,
|
||||||
): { url: string; hash?: string; name?: string; executable?: boolean } => {
|
): { url: string; hash?: string; name?: string; executable?: boolean } => {
|
||||||
@@ -139,16 +132,26 @@ const normalizeUrlInput = (
|
|||||||
const normalizeTarballInput = (args: NixValue): { url: string; sha256?: string; name?: string } => {
|
const normalizeTarballInput = (args: NixValue): { url: string; sha256?: string; name?: string } => {
|
||||||
const forced = force(args);
|
const forced = force(args);
|
||||||
if (isAttrs(forced)) {
|
if (isAttrs(forced)) {
|
||||||
const url = forceStringNoCtx(forced.url);
|
const url = resolvePseudoUrl(forceStringNoCtx(forced.url));
|
||||||
const sha256 = "sha256" in forced ? forceStringNoCtx(forced.sha256) : undefined;
|
const sha256 = "sha256" in forced ? forceStringNoCtx(forced.sha256) : undefined;
|
||||||
const name = "name" in forced ? forceStringNoCtx(forced.name) : undefined;
|
const nameRaw = "name" in forced ? forceStringNoCtx(forced.name) : undefined;
|
||||||
|
// FIXME: extract baseNameOfRaw
|
||||||
|
const name = nameRaw === "" ? baseNameOf(nameRaw) as string : nameRaw;
|
||||||
return { url, sha256, name };
|
return { url, sha256, name };
|
||||||
} else {
|
} else {
|
||||||
return { url: forceStringNoCtx(forced) };
|
return { url: forceStringNoCtx(forced) };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchurl = (args: NixValue): string => {
|
const resolvePseudoUrl = (url: string) => {
|
||||||
|
if (url.startsWith("channel:")) {
|
||||||
|
return `https://channels.nixos.org/${url.substring(8)}/nixexprs.tar.xz`
|
||||||
|
} else {
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetchurl = (args: NixValue): NixString => {
|
||||||
const { url, hash, name, executable } = normalizeUrlInput(args);
|
const { url, hash, name, executable } = normalizeUrlInput(args);
|
||||||
const result: FetchUrlResult = Deno.core.ops.op_fetch_url(
|
const result: FetchUrlResult = Deno.core.ops.op_fetch_url(
|
||||||
url,
|
url,
|
||||||
@@ -156,13 +159,17 @@ export const fetchurl = (args: NixValue): string => {
|
|||||||
name ?? null,
|
name ?? null,
|
||||||
executable ?? false,
|
executable ?? false,
|
||||||
);
|
);
|
||||||
return result.store_path;
|
const context: NixStringContext = new Set();
|
||||||
|
addOpaqueContext(context, result.store_path);
|
||||||
|
return mkStringWithContext(result.store_path, context);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchTarball = (args: NixValue): string => {
|
export const fetchTarball = (args: NixValue): NixString => {
|
||||||
const { url, name, sha256 } = normalizeTarballInput(args);
|
const { url, name, sha256 } = normalizeTarballInput(args);
|
||||||
const result: FetchTarballResult = Deno.core.ops.op_fetch_tarball(url, name ?? null, sha256 ?? null);
|
const result: FetchTarballResult = Deno.core.ops.op_fetch_tarball(url, name ?? null, sha256 ?? null);
|
||||||
return result.store_path;
|
const context: NixStringContext = new Set();
|
||||||
|
addOpaqueContext(context, result.store_path);
|
||||||
|
return mkStringWithContext(result.store_path, context);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchGit = (args: NixValue): NixAttrs => {
|
export const fetchGit = (args: NixValue): NixAttrs => {
|
||||||
@@ -170,8 +177,10 @@ export const fetchGit = (args: NixValue): NixAttrs => {
|
|||||||
if (typeof forced === "string" || isPath(forced)) {
|
if (typeof forced === "string" || isPath(forced)) {
|
||||||
const path = coerceToPath(forced);
|
const path = coerceToPath(forced);
|
||||||
const result: FetchGitResult = Deno.core.ops.op_fetch_git(path, null, null, false, false, false, null);
|
const result: FetchGitResult = Deno.core.ops.op_fetch_git(path, null, null, false, false, false, null);
|
||||||
|
const outContext: NixStringContext = new Set();
|
||||||
|
addOpaqueContext(outContext, result.out_path);
|
||||||
return {
|
return {
|
||||||
outPath: result.out_path,
|
outPath: mkStringWithContext(result.out_path, outContext),
|
||||||
rev: result.rev,
|
rev: result.rev,
|
||||||
shortRev: result.short_rev,
|
shortRev: result.short_rev,
|
||||||
revCount: BigInt(result.rev_count),
|
revCount: BigInt(result.rev_count),
|
||||||
@@ -200,8 +209,10 @@ export const fetchGit = (args: NixValue): NixAttrs => {
|
|||||||
name,
|
name,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const outContext: NixStringContext = new Set();
|
||||||
|
addOpaqueContext(outContext, result.out_path);
|
||||||
return {
|
return {
|
||||||
outPath: result.out_path,
|
outPath: mkStringWithContext(result.out_path, outContext),
|
||||||
rev: result.rev,
|
rev: result.rev,
|
||||||
shortRev: result.short_rev,
|
shortRev: result.short_rev,
|
||||||
revCount: BigInt(result.rev_count),
|
revCount: BigInt(result.rev_count),
|
||||||
@@ -212,21 +223,8 @@ export const fetchGit = (args: NixValue): NixAttrs => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchMercurial = (args: NixValue): NixAttrs => {
|
export const fetchMercurial = (_args: NixValue): NixAttrs => {
|
||||||
const attrs = forceAttrs(args);
|
throw new Error("Not implemented: fetchMercurial")
|
||||||
const url = forceStringValue(attrs.url);
|
|
||||||
const rev = "rev" in attrs ? forceStringValue(attrs.rev) : null;
|
|
||||||
const name = "name" in attrs ? forceStringValue(attrs.name) : null;
|
|
||||||
|
|
||||||
const result: FetchHgResult = Deno.core.ops.op_fetch_hg(url, rev, name);
|
|
||||||
|
|
||||||
return {
|
|
||||||
outPath: result.out_path,
|
|
||||||
branch: result.branch,
|
|
||||||
rev: result.rev,
|
|
||||||
shortRev: result.short_rev,
|
|
||||||
revCount: BigInt(result.rev_count),
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchTree = (args: NixValue): NixAttrs => {
|
export const fetchTree = (args: NixValue): NixAttrs => {
|
||||||
@@ -348,10 +346,9 @@ export const pathExists = (path: NixValue): boolean => {
|
|||||||
*
|
*
|
||||||
* Returns: Store path string
|
* Returns: Store path string
|
||||||
*/
|
*/
|
||||||
export const path = (args: NixValue): string => {
|
export const path = (args: NixValue): NixString => {
|
||||||
const attrs = forceAttrs(args);
|
const attrs = forceAttrs(args);
|
||||||
|
|
||||||
// Required: path parameter
|
|
||||||
if (!("path" in attrs)) {
|
if (!("path" in attrs)) {
|
||||||
throw new TypeError("builtins.path: 'path' attribute is required");
|
throw new TypeError("builtins.path: 'path' attribute is required");
|
||||||
}
|
}
|
||||||
@@ -359,23 +356,18 @@ export const path = (args: NixValue): string => {
|
|||||||
const pathValue = force(attrs.path);
|
const pathValue = force(attrs.path);
|
||||||
let pathStr: string;
|
let pathStr: string;
|
||||||
|
|
||||||
// Accept both Path values and strings
|
|
||||||
if (isNixPath(pathValue)) {
|
if (isNixPath(pathValue)) {
|
||||||
pathStr = getPathValue(pathValue);
|
pathStr = getPathValue(pathValue);
|
||||||
} else {
|
} else {
|
||||||
pathStr = forceStringValue(pathValue);
|
pathStr = forceStringValue(pathValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optional: name parameter (defaults to basename in Rust)
|
|
||||||
const name = "name" in attrs ? forceStringValue(attrs.name) : null;
|
const name = "name" in attrs ? forceStringValue(attrs.name) : null;
|
||||||
|
|
||||||
// Optional: recursive parameter (default: true)
|
|
||||||
const recursive = "recursive" in attrs ? forceBool(attrs.recursive) : true;
|
const recursive = "recursive" in attrs ? forceBool(attrs.recursive) : true;
|
||||||
|
|
||||||
// Optional: sha256 parameter
|
|
||||||
const sha256 = "sha256" in attrs ? forceStringValue(attrs.sha256) : null;
|
const sha256 = "sha256" in attrs ? forceStringValue(attrs.sha256) : null;
|
||||||
|
|
||||||
// Handle filter parameter
|
let storePath: string;
|
||||||
|
|
||||||
if ("filter" in attrs) {
|
if ("filter" in attrs) {
|
||||||
const filterFn = forceFunction(attrs.filter);
|
const filterFn = forceFunction(attrs.filter);
|
||||||
|
|
||||||
@@ -391,20 +383,20 @@ export const path = (args: NixValue): string => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const storePath: string = Deno.core.ops.op_add_filtered_path(
|
storePath = Deno.core.ops.op_add_filtered_path(
|
||||||
pathStr,
|
pathStr,
|
||||||
name,
|
name,
|
||||||
recursive,
|
recursive,
|
||||||
sha256,
|
sha256,
|
||||||
includePaths,
|
includePaths,
|
||||||
);
|
);
|
||||||
return storePath;
|
} else {
|
||||||
|
storePath = Deno.core.ops.op_add_path(pathStr, name, recursive, sha256);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call Rust op to add path to store
|
const context: NixStringContext = new Set();
|
||||||
const storePath: string = Deno.core.ops.op_add_path(pathStr, name, recursive, sha256);
|
addOpaqueContext(context, storePath);
|
||||||
|
return mkStringWithContext(storePath, context);
|
||||||
return storePath;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const toFile =
|
export const toFile =
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ export const hasContext = context.hasContext;
|
|||||||
export const hashFile =
|
export const hashFile =
|
||||||
(type: NixValue) =>
|
(type: NixValue) =>
|
||||||
(p: NixValue): never => {
|
(p: NixValue): never => {
|
||||||
|
const ty = forceStringNoCtx(type);
|
||||||
throw new Error("Not implemented: hashFile");
|
throw new Error("Not implemented: hashFile");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -25,36 +25,6 @@ import { mkStringWithContext, type NixStringContext } from "../string-context";
|
|||||||
* - baseNameOf "foo" → "foo"
|
* - baseNameOf "foo" → "foo"
|
||||||
*/
|
*/
|
||||||
export const baseNameOf = (s: NixValue): NixString => {
|
export const baseNameOf = (s: NixValue): NixString => {
|
||||||
const forced = force(s);
|
|
||||||
|
|
||||||
// Path input → string output (no context)
|
|
||||||
if (isNixPath(forced)) {
|
|
||||||
const pathStr = forced.value;
|
|
||||||
|
|
||||||
if (pathStr.length === 0) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
let last = pathStr.length - 1;
|
|
||||||
if (pathStr[last] === "/" && last > 0) {
|
|
||||||
last -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let pos = last;
|
|
||||||
while (pos >= 0 && pathStr[pos] !== "/") {
|
|
||||||
pos -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pos === -1) {
|
|
||||||
pos = 0;
|
|
||||||
} else {
|
|
||||||
pos += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return pathStr.substring(pos, last + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// String input → string output (preserve context)
|
|
||||||
const context: NixStringContext = new Set();
|
const context: NixStringContext = new Set();
|
||||||
const pathStr = coerceToString(s, StringCoercionMode.Base, false, context);
|
const pathStr = coerceToString(s, StringCoercionMode.Base, false, context);
|
||||||
|
|
||||||
|
|||||||
@@ -41,11 +41,12 @@ export const nixValueToJson = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (seen.has(v)) {
|
// FIXME: is this check necessary?
|
||||||
throw new Error("cycle detected in toJSON");
|
// if (seen.has(v)) {
|
||||||
} else {
|
// throw new Error("cycle detected in toJSON");
|
||||||
seen.add(v)
|
// } else {
|
||||||
}
|
// seen.add(v)
|
||||||
|
// }
|
||||||
|
|
||||||
if (Array.isArray(v)) {
|
if (Array.isArray(v)) {
|
||||||
return v.map((item) => nixValueToJson(item, strict, outContext, copyToStore, seen));
|
return v.map((item) => nixValueToJson(item, strict, outContext, copyToStore, seen));
|
||||||
|
|||||||
4
nix-js/runtime-ts/src/types/global.d.ts
vendored
4
nix-js/runtime-ts/src/types/global.d.ts
vendored
@@ -21,7 +21,6 @@ declare global {
|
|||||||
column: number | null;
|
column: number | null;
|
||||||
};
|
};
|
||||||
function op_make_store_path(ty: string, hash_hex: string, name: string): string;
|
function op_make_store_path(ty: string, hash_hex: string, name: string): string;
|
||||||
function op_output_path_name(drv_name: string, output_name: string): string;
|
|
||||||
function op_parse_hash(hash_str: string, algo: string | null): { hex: string; algo: string };
|
function op_parse_hash(hash_str: string, algo: string | null): { hex: string; algo: string };
|
||||||
function op_make_fixed_output_path(
|
function op_make_fixed_output_path(
|
||||||
hash_algo: string,
|
hash_algo: string,
|
||||||
@@ -49,7 +48,6 @@ declare global {
|
|||||||
all_refs: boolean,
|
all_refs: boolean,
|
||||||
name: string | null,
|
name: string | null,
|
||||||
): FetchGitResult;
|
): FetchGitResult;
|
||||||
function op_fetch_hg(url: string, rev: string | null, name: string | null): FetchHgResult;
|
|
||||||
function op_add_path(
|
function op_add_path(
|
||||||
path: string,
|
path: string,
|
||||||
name: string | null,
|
name: string | null,
|
||||||
@@ -76,6 +74,8 @@ declare global {
|
|||||||
): string;
|
): string;
|
||||||
function op_match(regex: string, text: string): (string | null)[] | null;
|
function op_match(regex: string, text: string): (string | null)[] | null;
|
||||||
function op_split(regex: string, text: string): (string | (string | null)[])[];
|
function op_split(regex: string, text: string): (string | (string | null)[])[];
|
||||||
|
function op_from_json(json: string): any;
|
||||||
|
function op_from_toml(toml: string): any;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,10 @@ use std::path::Path;
|
|||||||
use std::ptr::NonNull;
|
use std::ptr::NonNull;
|
||||||
|
|
||||||
use hashbrown::{HashMap, HashSet};
|
use hashbrown::{HashMap, HashSet};
|
||||||
use itertools::Itertools as _;
|
|
||||||
use rnix::TextRange;
|
use rnix::TextRange;
|
||||||
use string_interner::DefaultStringInterner;
|
use string_interner::DefaultStringInterner;
|
||||||
|
|
||||||
use crate::codegen::{CodegenContext, compile};
|
use crate::codegen::{CodegenContext, compile, compile_scoped};
|
||||||
use crate::downgrade::*;
|
use crate::downgrade::*;
|
||||||
use crate::error::{Error, Result, Source};
|
use crate::error::{Error, Result, Source};
|
||||||
use crate::ir::{
|
use crate::ir::{
|
||||||
@@ -17,6 +16,34 @@ use crate::runtime::{Runtime, RuntimeContext};
|
|||||||
use crate::store::{DaemonStore, Store, StoreConfig};
|
use crate::store::{DaemonStore, Store, StoreConfig};
|
||||||
use crate::value::{Symbol, Value};
|
use crate::value::{Symbol, Value};
|
||||||
|
|
||||||
|
fn parse_error_span(error: &rnix::ParseError) -> Option<rnix::TextRange> {
|
||||||
|
use rnix::ParseError::*;
|
||||||
|
match error {
|
||||||
|
Unexpected(range)
|
||||||
|
| UnexpectedExtra(range)
|
||||||
|
| UnexpectedWanted(_, range, _)
|
||||||
|
| UnexpectedDoubleBind(range)
|
||||||
|
| DuplicatedArgs(range, _) => Some(*range),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_parse_error<'a>(
|
||||||
|
errors: impl IntoIterator<Item = &'a rnix::ParseError>,
|
||||||
|
source: Source,
|
||||||
|
) -> Option<Box<Error>> {
|
||||||
|
for err in errors {
|
||||||
|
if let Some(span) = parse_error_span(err) {
|
||||||
|
return Some(
|
||||||
|
Error::parse_error(err.to_string())
|
||||||
|
.with_source(source)
|
||||||
|
.with_span(span),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
ctx: Ctx,
|
ctx: Ctx,
|
||||||
runtime: Runtime<Ctx>,
|
runtime: Runtime<Ctx>,
|
||||||
@@ -232,23 +259,24 @@ impl Ctx {
|
|||||||
self.sources.get(id).expect("source not found").clone()
|
self.sources.get(id).expect("source not found").clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile<'a>(&'a mut self, source: Source, extra_scope: Option<Scope<'a>>) -> Result<String> {
|
fn downgrade<'a>(&mut self, source: Source, extra_scope: Option<Scope<'a>>) -> Result<ExprId> {
|
||||||
tracing::debug!("Parsing Nix expression");
|
tracing::debug!("Parsing Nix expression");
|
||||||
|
|
||||||
self.sources.push(source.clone());
|
self.sources.push(source.clone());
|
||||||
|
|
||||||
let root = rnix::Root::parse(&source.src);
|
let root = rnix::Root::parse(&source.src);
|
||||||
if !root.errors().is_empty() {
|
handle_parse_error(root.errors(), source).map_or(Ok(()), Err)?;
|
||||||
let error_msg = root.errors().iter().join("; ");
|
|
||||||
let err = Error::parse_error(error_msg).with_source(source);
|
tracing::debug!("Downgrading Nix expression");
|
||||||
return Err(err);
|
let expr = root
|
||||||
|
.tree()
|
||||||
|
.expr()
|
||||||
|
.ok_or_else(|| Error::parse_error("unexpected EOF".into()))?;
|
||||||
|
self.downgrade_ctx(extra_scope).downgrade(expr)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::unwrap_used)]
|
fn compile<'a>(&'a mut self, source: Source, extra_scope: Option<Scope<'a>>) -> Result<String> {
|
||||||
let root = self
|
let root = self.downgrade(source, extra_scope)?;
|
||||||
.downgrade_ctx(extra_scope)
|
|
||||||
.downgrade(root.tree().expr().unwrap())?;
|
|
||||||
|
|
||||||
tracing::debug!("Generating JavaScript code");
|
tracing::debug!("Generating JavaScript code");
|
||||||
let code = compile(self.get_ir(root), self);
|
let code = compile(self.get_ir(root), self);
|
||||||
tracing::debug!("Generated code: {}", &code);
|
tracing::debug!("Generated code: {}", &code);
|
||||||
@@ -256,31 +284,13 @@ impl Ctx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn compile_scoped(&mut self, source: Source, scope: Vec<String>) -> Result<String> {
|
pub(crate) fn compile_scoped(&mut self, source: Source, scope: Vec<String>) -> Result<String> {
|
||||||
use crate::codegen::compile_scoped;
|
|
||||||
|
|
||||||
tracing::debug!("Parsing Nix expression for scoped import");
|
|
||||||
|
|
||||||
self.sources.push(source.clone());
|
|
||||||
|
|
||||||
let root = rnix::Root::parse(&source.src);
|
|
||||||
if !root.errors().is_empty() {
|
|
||||||
let error_msg = root.errors().iter().join("; ");
|
|
||||||
let err = Error::parse_error(error_msg).with_source(source);
|
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let scope = Scope::ScopedImport(
|
let scope = Scope::ScopedImport(
|
||||||
scope
|
scope
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|k| self.symbols.get_or_intern(k))
|
.map(|k| self.symbols.get_or_intern(k))
|
||||||
.collect(),
|
.collect(),
|
||||||
);
|
);
|
||||||
|
let root = self.downgrade(source, Some(scope))?;
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let root = self
|
|
||||||
.downgrade_ctx(Some(scope))
|
|
||||||
.downgrade(root.tree().expr().unwrap())?;
|
|
||||||
|
|
||||||
tracing::debug!("Generating JavaScript code for scoped import");
|
tracing::debug!("Generating JavaScript code for scoped import");
|
||||||
let code = compile_scoped(self.get_ir(root), self);
|
let code = compile_scoped(self.get_ir(root), self);
|
||||||
tracing::debug!("Generated scoped code: {}", &code);
|
tracing::debug!("Generated scoped code: {}", &code);
|
||||||
|
|||||||
@@ -1,17 +1,41 @@
|
|||||||
// Assume no parse error
|
|
||||||
#![allow(clippy::unwrap_used)]
|
|
||||||
|
|
||||||
use hashbrown::hash_map::Entry;
|
use hashbrown::hash_map::Entry;
|
||||||
use hashbrown::{HashMap, HashSet};
|
use hashbrown::{HashMap, HashSet};
|
||||||
use itertools::Itertools as _;
|
use itertools::Itertools as _;
|
||||||
use rnix::TextRange;
|
use rnix::TextRange;
|
||||||
use rnix::ast::{self, AstToken, Expr, HasEntry};
|
use rnix::ast::{self, AstToken, Expr, HasEntry};
|
||||||
use rowan::ast::AstNode;
|
use rowan::ast::AstNode;
|
||||||
|
use tap::TryConv;
|
||||||
|
|
||||||
use crate::error::{Error, Result, Source};
|
use crate::error::{Error, Result, Source};
|
||||||
use crate::ir::*;
|
use crate::ir::*;
|
||||||
use crate::value::Symbol;
|
use crate::value::Symbol;
|
||||||
|
|
||||||
|
trait Require<T> {
|
||||||
|
fn require(self, ctx: &impl DowngradeContext, span: TextRange) -> Result<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Require<T> for Option<T> {
|
||||||
|
#[inline]
|
||||||
|
fn require(self, ctx: &impl DowngradeContext, span: TextRange) -> Result<T> {
|
||||||
|
self.ok_or_else(|| {
|
||||||
|
Error::parse_error("invalid syntax".into())
|
||||||
|
.with_source(ctx.get_current_source())
|
||||||
|
.with_span(span)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, E: std::fmt::Display> Require<T> for std::result::Result<T, E> {
|
||||||
|
#[inline]
|
||||||
|
fn require(self, ctx: &impl DowngradeContext, span: TextRange) -> Result<T> {
|
||||||
|
self.map_err(|e| {
|
||||||
|
Error::parse_error(format!("invalid syntax: {e}"))
|
||||||
|
.with_source(ctx.get_current_source())
|
||||||
|
.with_span(span)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait DowngradeContext {
|
pub trait DowngradeContext {
|
||||||
fn downgrade(self, expr: rnix::ast::Expr) -> Result<ExprId>;
|
fn downgrade(self, expr: rnix::ast::Expr) -> Result<ExprId>;
|
||||||
|
|
||||||
@@ -64,7 +88,10 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for Expr {
|
|||||||
IfElse(ifelse) => ifelse.downgrade(ctx),
|
IfElse(ifelse) => ifelse.downgrade(ctx),
|
||||||
Select(select) => select.downgrade(ctx),
|
Select(select) => select.downgrade(ctx),
|
||||||
Str(str) => str.downgrade(ctx),
|
Str(str) => str.downgrade(ctx),
|
||||||
Path(path) => path.downgrade(ctx),
|
PathAbs(path) => path.downgrade(ctx),
|
||||||
|
PathRel(path) => path.downgrade(ctx),
|
||||||
|
PathHome(path) => path.downgrade(ctx),
|
||||||
|
PathSearch(path) => path.downgrade(ctx),
|
||||||
Literal(lit) => lit.downgrade(ctx),
|
Literal(lit) => lit.downgrade(ctx),
|
||||||
Lambda(lambda) => lambda.downgrade(ctx),
|
Lambda(lambda) => lambda.downgrade(ctx),
|
||||||
LegacyLet(let_) => let_.downgrade(ctx),
|
LegacyLet(let_) => let_.downgrade(ctx),
|
||||||
@@ -76,19 +103,25 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for Expr {
|
|||||||
Ident(ident) => ident.downgrade(ctx),
|
Ident(ident) => ident.downgrade(ctx),
|
||||||
With(with) => with.downgrade(ctx),
|
With(with) => with.downgrade(ctx),
|
||||||
HasAttr(has) => has.downgrade(ctx),
|
HasAttr(has) => has.downgrade(ctx),
|
||||||
Paren(paren) => paren.expr().unwrap().downgrade(ctx),
|
Paren(paren) => paren
|
||||||
Root(root) => root.expr().unwrap().downgrade(ctx),
|
.expr()
|
||||||
|
.require(ctx, paren.syntax().text_range())?
|
||||||
|
.downgrade(ctx),
|
||||||
|
Root(root) => root
|
||||||
|
.expr()
|
||||||
|
.require(ctx, root.syntax().text_range())?
|
||||||
|
.downgrade(ctx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Assert {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Assert {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let assertion = self.condition().unwrap();
|
let span = self.syntax().text_range();
|
||||||
|
let assertion = self.condition().require(ctx, span)?;
|
||||||
let assertion_raw = assertion.to_string();
|
let assertion_raw = assertion.to_string();
|
||||||
let assertion = assertion.downgrade(ctx)?;
|
let assertion = assertion.downgrade(ctx)?;
|
||||||
let expr = self.body().unwrap().downgrade(ctx)?;
|
let expr = self.body().require(ctx, span)?.downgrade(ctx)?;
|
||||||
let span = self.syntax().text_range();
|
|
||||||
Ok(ctx.new_expr(
|
Ok(ctx.new_expr(
|
||||||
Assert {
|
Assert {
|
||||||
assertion,
|
assertion,
|
||||||
@@ -103,10 +136,10 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Assert {
|
|||||||
|
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::IfElse {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::IfElse {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let cond = self.condition().unwrap().downgrade(ctx)?;
|
|
||||||
let consq = self.body().unwrap().downgrade(ctx)?;
|
|
||||||
let alter = self.else_body().unwrap().downgrade(ctx)?;
|
|
||||||
let span = self.syntax().text_range();
|
let span = self.syntax().text_range();
|
||||||
|
let cond = self.condition().require(ctx, span)?.downgrade(ctx)?;
|
||||||
|
let consq = self.body().require(ctx, span)?.downgrade(ctx)?;
|
||||||
|
let alter = self.else_body().require(ctx, span)?.downgrade(ctx)?;
|
||||||
Ok(ctx.new_expr(
|
Ok(ctx.new_expr(
|
||||||
If {
|
If {
|
||||||
cond,
|
cond,
|
||||||
@@ -119,38 +152,33 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::IfElse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Path {
|
macro_rules! path {
|
||||||
|
($ty:ident) => {
|
||||||
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::$ty {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let span = self.syntax().text_range();
|
let span = self.syntax().text_range();
|
||||||
let parts = self
|
downgrade_path(self.parts(), span, ctx)
|
||||||
.parts()
|
|
||||||
.map(|part| match part {
|
|
||||||
ast::InterpolPart::Literal(lit) => Ok(ctx.new_expr(
|
|
||||||
Str {
|
|
||||||
val: lit.to_string(),
|
|
||||||
span: lit.syntax().text_range(),
|
|
||||||
}
|
}
|
||||||
.to_ir(),
|
|
||||||
)),
|
|
||||||
ast::InterpolPart::Interpolation(interpol) => {
|
|
||||||
interpol.expr().unwrap().downgrade(ctx)
|
|
||||||
}
|
}
|
||||||
})
|
};
|
||||||
.collect::<Result<Vec<_>>>()?;
|
}
|
||||||
|
path!(PathAbs);
|
||||||
let expr = if parts.len() == 1 {
|
path!(PathRel);
|
||||||
let part = parts.into_iter().next().unwrap();
|
path!(PathHome);
|
||||||
if let &Ir::Str(Str { ref val, span }) = ctx.get_ir(part)
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::PathSearch {
|
||||||
&& let Some(path) = val.strip_prefix("<").map(|path| &path[..path.len() - 1])
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
{
|
let span = self.syntax().text_range();
|
||||||
ctx.replace_ir(
|
let path = {
|
||||||
part,
|
let temp = self.content().require(ctx, span)?;
|
||||||
|
let text = temp.text();
|
||||||
|
ctx.new_expr(
|
||||||
Str {
|
Str {
|
||||||
val: path.to_string(),
|
val: text[1..text.len() - 1].to_string(),
|
||||||
span,
|
span,
|
||||||
}
|
}
|
||||||
.to_ir(),
|
.to_ir(),
|
||||||
);
|
)
|
||||||
|
};
|
||||||
let sym = ctx.new_sym("findFile".into());
|
let sym = ctx.new_sym("findFile".into());
|
||||||
let find_file = ctx.new_expr(Builtin { inner: sym, span }.to_ir());
|
let find_file = ctx.new_expr(Builtin { inner: sym, span }.to_ir());
|
||||||
let sym = ctx.new_sym("nixPath".into());
|
let sym = ctx.new_sym("nixPath".into());
|
||||||
@@ -163,28 +191,14 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Path {
|
|||||||
}
|
}
|
||||||
.to_ir(),
|
.to_ir(),
|
||||||
);
|
);
|
||||||
return Ok(ctx.new_expr(
|
Ok(ctx.new_expr(
|
||||||
Call {
|
Call {
|
||||||
func: call,
|
func: call,
|
||||||
arg: part,
|
arg: path,
|
||||||
span,
|
span,
|
||||||
}
|
}
|
||||||
.to_ir(),
|
.to_ir(),
|
||||||
));
|
))
|
||||||
} else {
|
|
||||||
part
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ctx.new_expr(
|
|
||||||
ConcatStrings {
|
|
||||||
parts,
|
|
||||||
span,
|
|
||||||
force_string: false,
|
|
||||||
}
|
|
||||||
.to_ir(),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
Ok(ctx.new_expr(Path { expr, span }.to_ir()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,14 +214,17 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Str {
|
|||||||
.map(|part| match part {
|
.map(|part| match part {
|
||||||
ast::InterpolPart::Literal(lit) => Ok(ctx.new_expr(Str { val: lit, span }.to_ir())),
|
ast::InterpolPart::Literal(lit) => Ok(ctx.new_expr(Str { val: lit, span }.to_ir())),
|
||||||
ast::InterpolPart::Interpolation(interpol) => {
|
ast::InterpolPart::Interpolation(interpol) => {
|
||||||
let inner = interpol.expr().unwrap().downgrade(ctx)?;
|
let inner = interpol
|
||||||
|
.expr()
|
||||||
|
.require(ctx, interpol.syntax().text_range())?
|
||||||
|
.downgrade(ctx)?;
|
||||||
Ok(ctx.maybe_thunk(inner))
|
Ok(ctx.maybe_thunk(inner))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>>>()?;
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
Ok(if is_single_literal {
|
Ok(if is_single_literal {
|
||||||
parts.into_iter().next().unwrap()
|
parts.into_iter().next().expect("is_single_literal checked")
|
||||||
} else {
|
} else {
|
||||||
ctx.new_expr(
|
ctx.new_expr(
|
||||||
ConcatStrings {
|
ConcatStrings {
|
||||||
@@ -226,12 +243,12 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Literal {
|
|||||||
let span = self.syntax().text_range();
|
let span = self.syntax().text_range();
|
||||||
Ok(ctx.new_expr(match self.kind() {
|
Ok(ctx.new_expr(match self.kind() {
|
||||||
ast::LiteralKind::Integer(int) => Int {
|
ast::LiteralKind::Integer(int) => Int {
|
||||||
inner: int.value().unwrap(),
|
inner: int.value().require(ctx, span)?,
|
||||||
span,
|
span,
|
||||||
}
|
}
|
||||||
.to_ir(),
|
.to_ir(),
|
||||||
ast::LiteralKind::Float(float) => Float {
|
ast::LiteralKind::Float(float) => Float {
|
||||||
inner: float.value().unwrap(),
|
inner: float.value().require(ctx, span)?,
|
||||||
span,
|
span,
|
||||||
}
|
}
|
||||||
.to_ir(),
|
.to_ir(),
|
||||||
@@ -246,8 +263,8 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Literal {
|
|||||||
|
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Ident {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Ident {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let text = self.ident_token().unwrap().to_string();
|
|
||||||
let span = self.syntax().text_range();
|
let span = self.syntax().text_range();
|
||||||
|
let text = self.ident_token().require(ctx, span)?.to_string();
|
||||||
|
|
||||||
if text == "__curPos" {
|
if text == "__curPos" {
|
||||||
return Ok(ctx.new_expr(CurPos { span }.to_ir()));
|
return Ok(ctx.new_expr(CurPos { span }.to_ir()));
|
||||||
@@ -292,10 +309,10 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::List {
|
|||||||
/// Downgrades a binary operation.
|
/// Downgrades a binary operation.
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::BinOp {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::BinOp {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let lhs = self.lhs().unwrap().downgrade(ctx)?;
|
|
||||||
let rhs = self.rhs().unwrap().downgrade(ctx)?;
|
|
||||||
let kind = self.operator().unwrap().into();
|
|
||||||
let span = self.syntax().text_range();
|
let span = self.syntax().text_range();
|
||||||
|
let lhs = self.lhs().require(ctx, span)?.downgrade(ctx)?;
|
||||||
|
let rhs = self.rhs().require(ctx, span)?.downgrade(ctx)?;
|
||||||
|
let kind = self.operator().require(ctx, span)?.into();
|
||||||
Ok(ctx.new_expr(
|
Ok(ctx.new_expr(
|
||||||
BinOp {
|
BinOp {
|
||||||
lhs,
|
lhs,
|
||||||
@@ -311,9 +328,9 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::BinOp {
|
|||||||
/// Downgrades a "has attribute" (`?`) expression.
|
/// Downgrades a "has attribute" (`?`) expression.
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::HasAttr {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::HasAttr {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let lhs = self.expr().unwrap().downgrade(ctx)?;
|
|
||||||
let rhs = downgrade_attrpath(self.attrpath().unwrap(), ctx)?;
|
|
||||||
let span = self.syntax().text_range();
|
let span = self.syntax().text_range();
|
||||||
|
let lhs = self.expr().require(ctx, span)?.downgrade(ctx)?;
|
||||||
|
let rhs = downgrade_attrpath(self.attrpath().require(ctx, span)?, ctx)?;
|
||||||
Ok(ctx.new_expr(HasAttr { lhs, rhs, span }.to_ir()))
|
Ok(ctx.new_expr(HasAttr { lhs, rhs, span }.to_ir()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -321,9 +338,9 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::HasAttr {
|
|||||||
/// Downgrades a unary operation.
|
/// Downgrades a unary operation.
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::UnaryOp {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::UnaryOp {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let rhs = self.expr().unwrap().downgrade(ctx)?;
|
|
||||||
let kind = self.operator().unwrap().into();
|
|
||||||
let span = self.syntax().text_range();
|
let span = self.syntax().text_range();
|
||||||
|
let rhs = self.expr().require(ctx, span)?.downgrade(ctx)?;
|
||||||
|
let kind = self.operator().require(ctx, span)?.into();
|
||||||
Ok(ctx.new_expr(UnOp { rhs, kind, span }.to_ir()))
|
Ok(ctx.new_expr(UnOp { rhs, kind, span }.to_ir()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -331,8 +348,9 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::UnaryOp {
|
|||||||
/// Downgrades an attribute selection (`.`).
|
/// Downgrades an attribute selection (`.`).
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Select {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Select {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let expr = self.expr().unwrap().downgrade(ctx)?;
|
let span = self.syntax().text_range();
|
||||||
let attrpath = downgrade_attrpath(self.attrpath().unwrap(), ctx)?;
|
let expr = self.expr().require(ctx, span)?.downgrade(ctx)?;
|
||||||
|
let attrpath = downgrade_attrpath(self.attrpath().require(ctx, span)?, ctx)?;
|
||||||
let default = if let Some(default) = self.default_expr() {
|
let default = if let Some(default) = self.default_expr() {
|
||||||
let default_expr = default.downgrade(ctx)?;
|
let default_expr = default.downgrade(ctx)?;
|
||||||
Some(ctx.maybe_thunk(default_expr))
|
Some(ctx.maybe_thunk(default_expr))
|
||||||
@@ -390,8 +408,8 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::LegacyLet {
|
|||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::LetIn {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::LetIn {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let entries: Vec<_> = self.entries().collect();
|
let entries: Vec<_> = self.entries().collect();
|
||||||
let body_expr = self.body().unwrap();
|
|
||||||
let span = self.syntax().text_range();
|
let span = self.syntax().text_range();
|
||||||
|
let body_expr = self.body().require(ctx, span)?;
|
||||||
|
|
||||||
downgrade_let_bindings(entries, ctx, span, |ctx, _binding_keys| {
|
downgrade_let_bindings(entries, ctx, span, |ctx, _binding_keys| {
|
||||||
body_expr.downgrade(ctx)
|
body_expr.downgrade(ctx)
|
||||||
@@ -403,11 +421,12 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::LetIn {
|
|||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::With {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::With {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let span = self.syntax().text_range();
|
let span = self.syntax().text_range();
|
||||||
let namespace = self.namespace().unwrap().downgrade(ctx)?;
|
let namespace = self.namespace().require(ctx, span)?.downgrade(ctx)?;
|
||||||
let namespace = ctx.maybe_thunk(namespace);
|
let namespace = ctx.maybe_thunk(namespace);
|
||||||
|
|
||||||
let (body, thunks) = ctx
|
let body_expr = self.body().require(ctx, span)?;
|
||||||
.with_thunk_scope(|ctx| ctx.with_with_scope(|ctx| self.body().unwrap().downgrade(ctx)));
|
let (body, thunks) =
|
||||||
|
ctx.with_thunk_scope(|ctx| ctx.with_with_scope(|ctx| body_expr.downgrade(ctx)));
|
||||||
let body = body?;
|
let body = body?;
|
||||||
|
|
||||||
Ok(ctx.new_expr(
|
Ok(ctx.new_expr(
|
||||||
@@ -426,7 +445,9 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::With {
|
|||||||
/// This involves desugaring pattern-matching arguments into `let` bindings.
|
/// This involves desugaring pattern-matching arguments into `let` bindings.
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let raw_param = self.param().unwrap();
|
let span = self.syntax().text_range();
|
||||||
|
let raw_param = self.param().require(ctx, span)?;
|
||||||
|
let body_ast = self.body().require(ctx, span)?;
|
||||||
let arg = ctx.new_arg(raw_param.syntax().text_range());
|
let arg = ctx.new_arg(raw_param.syntax().text_range());
|
||||||
|
|
||||||
struct Ret {
|
struct Ret {
|
||||||
@@ -440,19 +461,20 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
|
|||||||
|
|
||||||
match raw_param {
|
match raw_param {
|
||||||
ast::Param::IdentParam(id) => {
|
ast::Param::IdentParam(id) => {
|
||||||
// Simple case: `x: body`
|
|
||||||
let param_sym = ctx.new_sym(id.to_string());
|
let param_sym = ctx.new_sym(id.to_string());
|
||||||
param = None;
|
param = None;
|
||||||
|
|
||||||
// Downgrade body in Param scope
|
body = ctx
|
||||||
body = ctx.with_param_scope(param_sym, arg, |ctx| {
|
.with_param_scope(param_sym, arg, |ctx| body_ast.clone().downgrade(ctx))?;
|
||||||
self.body().unwrap().downgrade(ctx)
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
ast::Param::Pattern(pattern) => {
|
ast::Param::Pattern(pattern) => {
|
||||||
let alias = pattern
|
let alias = pattern
|
||||||
.pat_bind()
|
.pat_bind()
|
||||||
.map(|alias| ctx.new_sym(alias.ident().unwrap().to_string()));
|
.map(|alias| {
|
||||||
|
let ident = alias.ident().require(ctx, alias.syntax().text_range())?;
|
||||||
|
Ok::<_, Box<Error>>(ctx.new_sym(ident.to_string()))
|
||||||
|
})
|
||||||
|
.transpose()?;
|
||||||
|
|
||||||
let ellipsis = pattern.ellipsis_token().is_some();
|
let ellipsis = pattern.ellipsis_token().is_some();
|
||||||
let pat_entries = pattern.pat_entries();
|
let pat_entries = pattern.pat_entries();
|
||||||
@@ -462,7 +484,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
|
|||||||
required,
|
required,
|
||||||
optional,
|
optional,
|
||||||
} = downgrade_pattern_bindings(pat_entries, alias, arg, ctx, |ctx, _| {
|
} = downgrade_pattern_bindings(pat_entries, alias, arg, ctx, |ctx, _| {
|
||||||
self.body().unwrap().downgrade(ctx)
|
body_ast.clone().downgrade(ctx)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
param = Some(Param {
|
param = Some(Param {
|
||||||
@@ -479,7 +501,6 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
|
|||||||
});
|
});
|
||||||
let Ret { param, body } = ret?;
|
let Ret { param, body } = ret?;
|
||||||
|
|
||||||
let span = self.syntax().text_range();
|
|
||||||
Ok(ctx.new_expr(
|
Ok(ctx.new_expr(
|
||||||
Func {
|
Func {
|
||||||
body,
|
body,
|
||||||
@@ -498,8 +519,9 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
|
|||||||
/// Each Apply node represents a single function call with one argument.
|
/// Each Apply node represents a single function call with one argument.
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Apply {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Apply {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let func = self.lambda().unwrap().downgrade(ctx)?;
|
let span = self.syntax().text_range();
|
||||||
let arg = self.argument().unwrap().downgrade(ctx)?;
|
let func = self.lambda().require(ctx, span)?.downgrade(ctx)?;
|
||||||
|
let arg = self.argument().require(ctx, span)?.downgrade(ctx)?;
|
||||||
let arg = ctx.maybe_thunk(arg);
|
let arg = ctx.maybe_thunk(arg);
|
||||||
let span = self.syntax().text_range();
|
let span = self.syntax().text_range();
|
||||||
Ok(ctx.new_expr(Call { func, arg, span }.to_ir()))
|
Ok(ctx.new_expr(Call { func, arg, span }.to_ir()))
|
||||||
@@ -551,7 +573,8 @@ impl PendingAttrSet {
|
|||||||
ast::Attr::Str(string) => {
|
ast::Attr::Str(string) => {
|
||||||
let parts = string.normalized_parts();
|
let parts = string.normalized_parts();
|
||||||
if parts.len() == 1
|
if parts.len() == 1
|
||||||
&& let ast::InterpolPart::Literal(lit) = parts.into_iter().next().unwrap()
|
&& let ast::InterpolPart::Literal(lit) =
|
||||||
|
parts.into_iter().next().expect("len checked")
|
||||||
{
|
{
|
||||||
let sym = ctx.new_sym(lit);
|
let sym = ctx.new_sym(lit);
|
||||||
return self.insert_static(sym, span, rest, value, ctx);
|
return self.insert_static(sym, span, rest, value, ctx);
|
||||||
@@ -768,9 +791,10 @@ impl PendingAttrSet {
|
|||||||
self.collect_inherit(inherit, ctx)?;
|
self.collect_inherit(inherit, ctx)?;
|
||||||
}
|
}
|
||||||
ast::Entry::AttrpathValue(value) => {
|
ast::Entry::AttrpathValue(value) => {
|
||||||
let attrpath = value.attrpath().unwrap();
|
let span = value.syntax().text_range();
|
||||||
|
let attrpath = value.attrpath().require(ctx, span)?;
|
||||||
let path: Vec<_> = attrpath.attrs().collect();
|
let path: Vec<_> = attrpath.attrs().collect();
|
||||||
let expr = value.value().unwrap();
|
let expr = value.value().require(ctx, span)?;
|
||||||
self.insert(&path, expr, ctx)?;
|
self.insert(&path, expr, ctx)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -783,7 +807,13 @@ impl PendingAttrSet {
|
|||||||
inherit: ast::Inherit,
|
inherit: ast::Inherit,
|
||||||
ctx: &mut impl DowngradeContext,
|
ctx: &mut impl DowngradeContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let from = inherit.from().map(|f| f.expr().unwrap());
|
let from = inherit
|
||||||
|
.from()
|
||||||
|
.map(|f| {
|
||||||
|
let span = f.syntax().text_range();
|
||||||
|
f.expr().require(ctx, span)
|
||||||
|
})
|
||||||
|
.transpose()?;
|
||||||
for attr in inherit.attrs() {
|
for attr in inherit.attrs() {
|
||||||
let span = attr.syntax().text_range();
|
let span = attr.syntax().text_range();
|
||||||
let sym = match &attr {
|
let sym = match &attr {
|
||||||
@@ -791,7 +821,8 @@ impl PendingAttrSet {
|
|||||||
ast::Attr::Str(s) => {
|
ast::Attr::Str(s) => {
|
||||||
let parts = s.normalized_parts();
|
let parts = s.normalized_parts();
|
||||||
if parts.len() == 1
|
if parts.len() == 1
|
||||||
&& let ast::InterpolPart::Literal(lit) = parts.into_iter().next().unwrap()
|
&& let ast::InterpolPart::Literal(lit) =
|
||||||
|
parts.into_iter().next().expect("len checked")
|
||||||
{
|
{
|
||||||
ctx.new_sym(lit)
|
ctx.new_sym(lit)
|
||||||
} else {
|
} else {
|
||||||
@@ -886,7 +917,7 @@ fn make_attrpath_value_entry(path: Vec<ast::Attr>, value: ast::Expr) -> ast::Ent
|
|||||||
|
|
||||||
let green = builder.finish();
|
let green = builder.finish();
|
||||||
let node = rowan::SyntaxNode::<NixLanguage>::new_root(green);
|
let node = rowan::SyntaxNode::<NixLanguage>::new_root(green);
|
||||||
ast::Entry::cast(node).unwrap()
|
ast::Entry::cast(node).expect("constructed valid Entry node")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Downgrades the entries of a non-recursive attribute set.
|
/// Downgrades the entries of a non-recursive attribute set.
|
||||||
@@ -915,10 +946,13 @@ fn downgrade_attr(attr: ast::Attr, ctx: &mut impl DowngradeContext) -> Result<At
|
|||||||
if parts.is_empty() {
|
if parts.is_empty() {
|
||||||
Ok(Attr::Str(ctx.new_sym("".to_string()), span))
|
Ok(Attr::Str(ctx.new_sym("".to_string()), span))
|
||||||
} else if parts.len() == 1 {
|
} else if parts.len() == 1 {
|
||||||
match parts.into_iter().next().unwrap() {
|
match parts.into_iter().next().expect("len checked") {
|
||||||
Literal(ident) => Ok(Attr::Str(ctx.new_sym(ident), span)),
|
Literal(ident) => Ok(Attr::Str(ctx.new_sym(ident), span)),
|
||||||
Interpolation(interpol) => Ok(Attr::Dynamic(
|
Interpolation(interpol) => Ok(Attr::Dynamic(
|
||||||
interpol.expr().unwrap().downgrade(ctx)?,
|
interpol
|
||||||
|
.expr()
|
||||||
|
.require(ctx, interpol.syntax().text_range())?
|
||||||
|
.downgrade(ctx)?,
|
||||||
span,
|
span,
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
@@ -927,7 +961,10 @@ fn downgrade_attr(attr: ast::Attr, ctx: &mut impl DowngradeContext) -> Result<At
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|part| match part {
|
.map(|part| match part {
|
||||||
Literal(lit) => Ok(ctx.new_expr(self::Str { val: lit, span }.to_ir())),
|
Literal(lit) => Ok(ctx.new_expr(self::Str { val: lit, span }.to_ir())),
|
||||||
Interpolation(interpol) => interpol.expr().unwrap().downgrade(ctx),
|
Interpolation(interpol) => interpol
|
||||||
|
.expr()
|
||||||
|
.require(ctx, interpol.syntax().text_range())?
|
||||||
|
.downgrade(ctx),
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>>>()?;
|
.collect::<Result<Vec<_>>>()?;
|
||||||
Ok(Attr::Dynamic(
|
Ok(Attr::Dynamic(
|
||||||
@@ -943,10 +980,13 @@ fn downgrade_attr(attr: ast::Attr, ctx: &mut impl DowngradeContext) -> Result<At
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Dynamic(dynamic) => Ok(Attr::Dynamic(
|
Dynamic(dynamic) => {
|
||||||
dynamic.expr().unwrap().downgrade(ctx)?,
|
let span = dynamic.syntax().text_range();
|
||||||
dynamic.syntax().text_range(),
|
Ok(Attr::Dynamic(
|
||||||
)),
|
dynamic.expr().require(ctx, span)?.downgrade(ctx)?,
|
||||||
|
span,
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -988,7 +1028,7 @@ where
|
|||||||
let (params, mut binding_keys) = pat_entries
|
let (params, mut binding_keys) = pat_entries
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|entry| {
|
.map(|entry| {
|
||||||
let ident = entry.ident().unwrap();
|
let ident = entry.ident().require(ctx, entry.syntax().text_range())?;
|
||||||
let sym_span = ident.syntax().text_range();
|
let sym_span = ident.syntax().text_range();
|
||||||
let sym = ctx.new_sym(ident.syntax().text().to_string());
|
let sym = ctx.new_sym(ident.syntax().text().to_string());
|
||||||
let default = entry.default();
|
let default = entry.default();
|
||||||
@@ -1054,7 +1094,7 @@ where
|
|||||||
span,
|
span,
|
||||||
} in params
|
} in params
|
||||||
{
|
{
|
||||||
let slot = *let_bindings.get(&sym).unwrap();
|
let slot = *let_bindings.get(&sym).expect("binding registered");
|
||||||
|
|
||||||
let default = if let Some(default) = default {
|
let default = if let Some(default) = default {
|
||||||
let default = default.clone().downgrade(ctx)?;
|
let default = default.clone().downgrade(ctx)?;
|
||||||
@@ -1076,7 +1116,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(alias_sym) = alias {
|
if let Some(alias_sym) = alias {
|
||||||
let slot = *let_bindings.get(&alias_sym).unwrap();
|
let slot = *let_bindings.get(&alias_sym).expect("binding registered");
|
||||||
ctx.register_thunk(slot, arg);
|
ctx.register_thunk(slot, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1318,3 +1358,38 @@ fn finalize_pending_value<Ctx: DowngradeContext, const ALLOW_DYN: bool>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn downgrade_path(
|
||||||
|
parts: impl IntoIterator<Item = ast::InterpolPart<ast::PathContent>>,
|
||||||
|
span: rnix::TextRange,
|
||||||
|
ctx: &mut impl DowngradeContext,
|
||||||
|
) -> Result<ExprId> {
|
||||||
|
let parts = parts
|
||||||
|
.into_iter()
|
||||||
|
.map(|part| match part {
|
||||||
|
ast::InterpolPart::Literal(lit) => Ok(ctx.new_expr(
|
||||||
|
Str {
|
||||||
|
val: lit.text().to_string(),
|
||||||
|
span: lit.syntax().text_range(),
|
||||||
|
}
|
||||||
|
.to_ir(),
|
||||||
|
)),
|
||||||
|
ast::InterpolPart::Interpolation(interpol) => interpol
|
||||||
|
.expr()
|
||||||
|
.require(ctx, interpol.syntax().text_range())?
|
||||||
|
.downgrade(ctx),
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
let expr = match parts.try_conv::<[_; 1]>() {
|
||||||
|
Ok([part]) => part,
|
||||||
|
Err(parts) => ctx.new_expr(
|
||||||
|
ConcatStrings {
|
||||||
|
parts,
|
||||||
|
span,
|
||||||
|
force_string: false,
|
||||||
|
}
|
||||||
|
.to_ir(),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
Ok(ctx.new_expr(Path { expr, span }.to_ir()))
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#![allow(unused_assignments)]
|
|
||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ mod archive;
|
|||||||
pub(crate) mod cache;
|
pub(crate) mod cache;
|
||||||
mod download;
|
mod download;
|
||||||
mod git;
|
mod git;
|
||||||
mod hg;
|
|
||||||
mod metadata_cache;
|
mod metadata_cache;
|
||||||
|
|
||||||
pub use cache::FetcherCache;
|
pub use cache::FetcherCache;
|
||||||
@@ -47,15 +46,6 @@ pub struct FetchGitResult {
|
|||||||
pub nar_hash: Option<String>,
|
pub nar_hash: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
pub struct FetchHgResult {
|
|
||||||
pub out_path: String,
|
|
||||||
pub branch: String,
|
|
||||||
pub rev: String,
|
|
||||||
pub short_rev: String,
|
|
||||||
pub rev_count: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[op2]
|
#[op2]
|
||||||
#[serde]
|
#[serde]
|
||||||
pub fn op_fetch_url<Ctx: RuntimeContext>(
|
pub fn op_fetch_url<Ctx: RuntimeContext>(
|
||||||
@@ -119,7 +109,7 @@ pub fn op_fetch_url<Ctx: RuntimeContext>(
|
|||||||
|
|
||||||
info!(bytes = data.len(), "Download complete");
|
info!(bytes = data.len(), "Download complete");
|
||||||
|
|
||||||
let hash = crate::nix_utils::sha256_hex(&String::from_utf8_lossy(&data));
|
let hash = crate::nix_utils::sha256_hex(&data);
|
||||||
|
|
||||||
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);
|
||||||
@@ -228,9 +218,7 @@ pub fn op_fetch_tarball<Ctx: RuntimeContext>(
|
|||||||
info!(bytes = data.len(), "Download complete");
|
info!(bytes = data.len(), "Download complete");
|
||||||
|
|
||||||
info!("Extracting tarball");
|
info!("Extracting tarball");
|
||||||
let cache = FetcherCache::new().map_err(|e| NixRuntimeError::from(e.to_string()))?;
|
let (extracted_path, _temp_dir) = archive::extract_tarball_to_temp(&data)
|
||||||
let (extracted_path, _temp_dir) = cache
|
|
||||||
.extract_tarball_to_temp(&data)
|
|
||||||
.map_err(|e| NixRuntimeError::from(e.to_string()))?;
|
.map_err(|e| NixRuntimeError::from(e.to_string()))?;
|
||||||
|
|
||||||
info!("Computing NAR hash");
|
info!("Computing NAR hash");
|
||||||
@@ -311,20 +299,6 @@ pub fn op_fetch_git<Ctx: RuntimeContext>(
|
|||||||
.map_err(|e| NixRuntimeError::from(e.to_string()))
|
.map_err(|e| NixRuntimeError::from(e.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[op2]
|
|
||||||
#[serde]
|
|
||||||
pub fn op_fetch_hg(
|
|
||||||
#[string] url: String,
|
|
||||||
#[string] rev: Option<String>,
|
|
||||||
#[string] name: Option<String>,
|
|
||||||
) -> Result<FetchHgResult, NixRuntimeError> {
|
|
||||||
let cache = FetcherCache::new().map_err(|e| NixRuntimeError::from(e.to_string()))?;
|
|
||||||
let dir_name = name.unwrap_or_else(|| "source".to_string());
|
|
||||||
|
|
||||||
hg::fetch_hg(&cache, &url, rev.as_deref(), &dir_name)
|
|
||||||
.map_err(|e| NixRuntimeError::from(e.to_string()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn normalize_hash(hash: &str) -> String {
|
fn normalize_hash(hash: &str) -> String {
|
||||||
use base64::prelude::*;
|
use base64::prelude::*;
|
||||||
if hash.starts_with("sha256-")
|
if hash.starts_with("sha256-")
|
||||||
@@ -341,6 +315,5 @@ pub fn register_ops<Ctx: RuntimeContext>() -> Vec<deno_core::OpDecl> {
|
|||||||
op_fetch_url::<Ctx>(),
|
op_fetch_url::<Ctx>(),
|
||||||
op_fetch_tarball::<Ctx>(),
|
op_fetch_tarball::<Ctx>(),
|
||||||
op_fetch_git::<Ctx>(),
|
op_fetch_git::<Ctx>(),
|
||||||
op_fetch_hg(),
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use std::fs::{self, File};
|
use std::fs;
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
|
use std::os::unix::ffi::OsStrExt;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use flate2::read::GzDecoder;
|
use flate2::read::GzDecoder;
|
||||||
@@ -10,7 +11,6 @@ pub enum ArchiveFormat {
|
|||||||
TarXz,
|
TarXz,
|
||||||
TarBz2,
|
TarBz2,
|
||||||
Tar,
|
Tar,
|
||||||
Zip,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ArchiveFormat {
|
impl ArchiveFormat {
|
||||||
@@ -27,9 +27,6 @@ impl ArchiveFormat {
|
|||||||
if url.ends_with(".tar") {
|
if url.ends_with(".tar") {
|
||||||
return ArchiveFormat::Tar;
|
return ArchiveFormat::Tar;
|
||||||
}
|
}
|
||||||
if url.ends_with(".zip") {
|
|
||||||
return ArchiveFormat::Zip;
|
|
||||||
}
|
|
||||||
|
|
||||||
if data.len() >= 2 && data[0] == 0x1f && data[1] == 0x8b {
|
if data.len() >= 2 && data[0] == 0x1f && data[1] == 0x8b {
|
||||||
return ArchiveFormat::TarGz;
|
return ArchiveFormat::TarGz;
|
||||||
@@ -40,15 +37,12 @@ impl ArchiveFormat {
|
|||||||
if data.len() >= 3 && &data[0..3] == b"BZh" {
|
if data.len() >= 3 && &data[0..3] == b"BZh" {
|
||||||
return ArchiveFormat::TarBz2;
|
return ArchiveFormat::TarBz2;
|
||||||
}
|
}
|
||||||
if data.len() >= 4 && &data[0..4] == b"PK\x03\x04" {
|
|
||||||
return ArchiveFormat::Zip;
|
|
||||||
}
|
|
||||||
|
|
||||||
ArchiveFormat::TarGz
|
ArchiveFormat::TarGz
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_archive(data: &[u8], dest: &Path) -> Result<PathBuf, ArchiveError> {
|
pub fn extract_tarball(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");
|
||||||
@@ -59,7 +53,6 @@ pub fn extract_archive(data: &[u8], dest: &Path) -> Result<PathBuf, ArchiveError
|
|||||||
ArchiveFormat::TarXz => extract_tar_xz(data, &temp_dir)?,
|
ArchiveFormat::TarXz => extract_tar_xz(data, &temp_dir)?,
|
||||||
ArchiveFormat::TarBz2 => extract_tar_bz2(data, &temp_dir)?,
|
ArchiveFormat::TarBz2 => extract_tar_bz2(data, &temp_dir)?,
|
||||||
ArchiveFormat::Tar => extract_tar(data, &temp_dir)?,
|
ArchiveFormat::Tar => extract_tar(data, &temp_dir)?,
|
||||||
ArchiveFormat::Zip => extract_zip(data, &temp_dir)?,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
strip_single_toplevel(&temp_dir, dest)
|
strip_single_toplevel(&temp_dir, dest)
|
||||||
@@ -92,40 +85,10 @@ fn extract_tar(data: &[u8], dest: &Path) -> Result<(), ArchiveError> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_zip(data: &[u8], dest: &Path) -> Result<(), ArchiveError> {
|
|
||||||
let cursor = Cursor::new(data);
|
|
||||||
let mut archive = zip::ZipArchive::new(cursor)?;
|
|
||||||
|
|
||||||
for i in 0..archive.len() {
|
|
||||||
let mut file = archive.by_index(i)?;
|
|
||||||
let outpath = dest.join(file.mangled_name());
|
|
||||||
|
|
||||||
if file.is_dir() {
|
|
||||||
fs::create_dir_all(&outpath)?;
|
|
||||||
} else {
|
|
||||||
if let Some(parent) = outpath.parent() {
|
|
||||||
fs::create_dir_all(parent)?;
|
|
||||||
}
|
|
||||||
let mut outfile = File::create(&outpath)?;
|
|
||||||
std::io::copy(&mut file, &mut outfile)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
{
|
|
||||||
use std::os::unix::fs::PermissionsExt;
|
|
||||||
if let Some(mode) = file.unix_mode() {
|
|
||||||
fs::set_permissions(&outpath, fs::Permissions::from_mode(mode))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn strip_single_toplevel(temp_dir: &Path, dest: &Path) -> 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().as_os_str().as_bytes()[0] != b'.')
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
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() {
|
||||||
@@ -182,17 +145,21 @@ fn copy_dir_recursive(src: &Path, dst: &Path) -> Result<(), std::io::Error> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn extract_tarball_to_temp(data: &[u8]) -> Result<(PathBuf, tempfile::TempDir), ArchiveError> {
|
||||||
|
let temp_dir = tempfile::tempdir()?;
|
||||||
|
let extracted_path = extract_tarball(data, temp_dir.path())?;
|
||||||
|
Ok((extracted_path, temp_dir))
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ArchiveError {
|
pub enum ArchiveError {
|
||||||
IoError(std::io::Error),
|
IoError(std::io::Error),
|
||||||
ZipError(zip::result::ZipError),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for ArchiveError {
|
impl std::fmt::Display for ArchiveError {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
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),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -204,9 +171,3 @@ impl From<std::io::Error> for ArchiveError {
|
|||||||
ArchiveError::IoError(e)
|
ArchiveError::IoError(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<zip::result::ZipError> for ArchiveError {
|
|
||||||
fn from(e: zip::result::ZipError) -> Self {
|
|
||||||
ArchiveError::ZipError(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,37 +1,6 @@
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use super::archive::ArchiveError;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum CacheError {
|
|
||||||
Io(std::io::Error),
|
|
||||||
Archive(ArchiveError),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for CacheError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
CacheError::Io(e) => write!(f, "I/O error: {}", e),
|
|
||||||
CacheError::Archive(e) => write!(f, "Archive error: {}", e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for CacheError {}
|
|
||||||
|
|
||||||
impl From<std::io::Error> for CacheError {
|
|
||||||
fn from(e: std::io::Error) -> Self {
|
|
||||||
CacheError::Io(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ArchiveError> for CacheError {
|
|
||||||
fn from(e: ArchiveError) -> Self {
|
|
||||||
CacheError::Archive(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct FetcherCache {
|
pub struct FetcherCache {
|
||||||
base_dir: PathBuf,
|
base_dir: PathBuf,
|
||||||
@@ -49,41 +18,12 @@ impl FetcherCache {
|
|||||||
Ok(Self { base_dir })
|
Ok(Self { base_dir })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn make_store_path(&self, hash: &str, name: &str) -> PathBuf {
|
|
||||||
let short_hash = &hash[..32.min(hash.len())];
|
|
||||||
self.base_dir
|
|
||||||
.join("store")
|
|
||||||
.join(format!("{}-{}", short_hash, name))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn git_cache_dir(&self) -> PathBuf {
|
fn git_cache_dir(&self) -> PathBuf {
|
||||||
self.base_dir.join("gitv3")
|
self.base_dir.join("git")
|
||||||
}
|
|
||||||
|
|
||||||
fn hg_cache_dir(&self) -> PathBuf {
|
|
||||||
self.base_dir.join("hg")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hash_key(url: &str) -> String {
|
|
||||||
crate::nix_utils::sha256_hex(url)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_git_bare(&self, url: &str) -> PathBuf {
|
pub fn get_git_bare(&self, url: &str) -> PathBuf {
|
||||||
let key = Self::hash_key(url);
|
let key = crate::nix_utils::sha256_hex(url.as_bytes());
|
||||||
self.git_cache_dir().join(key)
|
self.git_cache_dir().join(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_hg_bare(&self, url: &str) -> PathBuf {
|
|
||||||
let key = Self::hash_key(url);
|
|
||||||
self.hg_cache_dir().join(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn extract_tarball_to_temp(
|
|
||||||
&self,
|
|
||||||
data: &[u8],
|
|
||||||
) -> Result<(PathBuf, tempfile::TempDir), CacheError> {
|
|
||||||
let temp_dir = tempfile::tempdir()?;
|
|
||||||
let extracted_path = super::archive::extract_archive(data, temp_dir.path())?;
|
|
||||||
Ok((extracted_path, temp_dir))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,196 +0,0 @@
|
|||||||
use std::fs;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
use super::FetchHgResult;
|
|
||||||
use super::cache::FetcherCache;
|
|
||||||
|
|
||||||
pub fn fetch_hg(
|
|
||||||
cache: &FetcherCache,
|
|
||||||
url: &str,
|
|
||||||
rev: Option<&str>,
|
|
||||||
name: &str,
|
|
||||||
) -> Result<FetchHgResult, HgError> {
|
|
||||||
let bare_repo = cache.get_hg_bare(url);
|
|
||||||
|
|
||||||
if !bare_repo.exists() {
|
|
||||||
clone_repo(url, &bare_repo)?;
|
|
||||||
} else {
|
|
||||||
pull_repo(&bare_repo)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let target_rev = rev.unwrap_or("tip").to_string();
|
|
||||||
let resolved_rev = resolve_rev(&bare_repo, &target_rev)?;
|
|
||||||
let branch = get_branch(&bare_repo, &resolved_rev)?;
|
|
||||||
|
|
||||||
let checkout_dir = checkout_rev(&bare_repo, &resolved_rev, name, cache)?;
|
|
||||||
|
|
||||||
let rev_count = get_rev_count(&bare_repo, &resolved_rev)?;
|
|
||||||
|
|
||||||
let short_rev = if resolved_rev.len() >= 12 {
|
|
||||||
resolved_rev[..12].to_string()
|
|
||||||
} else {
|
|
||||||
resolved_rev.clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(FetchHgResult {
|
|
||||||
out_path: checkout_dir.to_string_lossy().to_string(),
|
|
||||||
branch,
|
|
||||||
rev: resolved_rev,
|
|
||||||
short_rev,
|
|
||||||
rev_count,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clone_repo(url: &str, dest: &PathBuf) -> Result<(), HgError> {
|
|
||||||
fs::create_dir_all(dest.parent().unwrap_or(dest))?;
|
|
||||||
|
|
||||||
let output = Command::new("hg")
|
|
||||||
.args(["clone", "-U", url])
|
|
||||||
.arg(dest)
|
|
||||||
.env("HGPLAIN", "")
|
|
||||||
.output()?;
|
|
||||||
|
|
||||||
if !output.status.success() {
|
|
||||||
return Err(HgError::CommandFailed {
|
|
||||||
operation: "clone".to_string(),
|
|
||||||
message: String::from_utf8_lossy(&output.stderr).to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pull_repo(repo: &PathBuf) -> Result<(), HgError> {
|
|
||||||
let output = Command::new("hg")
|
|
||||||
.args(["pull"])
|
|
||||||
.current_dir(repo)
|
|
||||||
.env("HGPLAIN", "")
|
|
||||||
.output()?;
|
|
||||||
|
|
||||||
if !output.status.success() {
|
|
||||||
return Err(HgError::CommandFailed {
|
|
||||||
operation: "pull".to_string(),
|
|
||||||
message: String::from_utf8_lossy(&output.stderr).to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resolve_rev(repo: &PathBuf, rev: &str) -> Result<String, HgError> {
|
|
||||||
let output = Command::new("hg")
|
|
||||||
.args(["log", "-r", rev, "--template", "{node}"])
|
|
||||||
.current_dir(repo)
|
|
||||||
.env("HGPLAIN", "")
|
|
||||||
.output()?;
|
|
||||||
|
|
||||||
if !output.status.success() {
|
|
||||||
return Err(HgError::CommandFailed {
|
|
||||||
operation: "log".to_string(),
|
|
||||||
message: format!(
|
|
||||||
"Could not resolve rev '{}': {}",
|
|
||||||
rev,
|
|
||||||
String::from_utf8_lossy(&output.stderr)
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_branch(repo: &PathBuf, rev: &str) -> Result<String, HgError> {
|
|
||||||
let output = Command::new("hg")
|
|
||||||
.args(["log", "-r", rev, "--template", "{branch}"])
|
|
||||||
.current_dir(repo)
|
|
||||||
.env("HGPLAIN", "")
|
|
||||||
.output()?;
|
|
||||||
|
|
||||||
if !output.status.success() {
|
|
||||||
return Ok("default".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
let branch = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
|
||||||
if branch.is_empty() {
|
|
||||||
Ok("default".to_string())
|
|
||||||
} else {
|
|
||||||
Ok(branch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn checkout_rev(
|
|
||||||
bare_repo: &PathBuf,
|
|
||||||
rev: &str,
|
|
||||||
name: &str,
|
|
||||||
cache: &FetcherCache,
|
|
||||||
) -> Result<PathBuf, HgError> {
|
|
||||||
let hash = crate::nix_utils::sha256_hex(&format!("{}:{}", bare_repo.display(), rev));
|
|
||||||
let checkout_dir = cache.make_store_path(&hash, name);
|
|
||||||
|
|
||||||
if checkout_dir.exists() {
|
|
||||||
return Ok(checkout_dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
fs::create_dir_all(&checkout_dir)?;
|
|
||||||
|
|
||||||
let output = Command::new("hg")
|
|
||||||
.args(["archive", "-r", rev])
|
|
||||||
.arg(&checkout_dir)
|
|
||||||
.current_dir(bare_repo)
|
|
||||||
.env("HGPLAIN", "")
|
|
||||||
.output()?;
|
|
||||||
|
|
||||||
if !output.status.success() {
|
|
||||||
fs::remove_dir_all(&checkout_dir)?;
|
|
||||||
return Err(HgError::CommandFailed {
|
|
||||||
operation: "archive".to_string(),
|
|
||||||
message: String::from_utf8_lossy(&output.stderr).to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let hg_archival = checkout_dir.join(".hg_archival.txt");
|
|
||||||
if hg_archival.exists() {
|
|
||||||
fs::remove_file(&hg_archival)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(checkout_dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_rev_count(repo: &PathBuf, rev: &str) -> Result<u64, HgError> {
|
|
||||||
let output = Command::new("hg")
|
|
||||||
.args(["log", "-r", &format!("0::{}", rev), "--template", "x"])
|
|
||||||
.current_dir(repo)
|
|
||||||
.env("HGPLAIN", "")
|
|
||||||
.output()?;
|
|
||||||
|
|
||||||
if !output.status.success() {
|
|
||||||
return Ok(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(output.stdout.len() as u64)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum HgError {
|
|
||||||
IoError(std::io::Error),
|
|
||||||
CommandFailed { operation: String, message: String },
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for HgError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
HgError::IoError(e) => write!(f, "I/O error: {}", e),
|
|
||||||
HgError::CommandFailed { operation, message } => {
|
|
||||||
write!(f, "Mercurial {} failed: {}", operation, message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for HgError {}
|
|
||||||
|
|
||||||
impl From<std::io::Error> for HgError {
|
|
||||||
fn from(e: std::io::Error) -> Self {
|
|
||||||
HgError::IoError(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
use nix_compat::store_path::compress_hash;
|
use nix_compat::store_path::compress_hash;
|
||||||
use sha2::{Digest as _, Sha256};
|
use sha2::{Digest as _, Sha256};
|
||||||
|
|
||||||
pub fn sha256_hex(data: &str) -> String {
|
pub fn sha256_hex(data: &[u8]) -> String {
|
||||||
let mut hasher = Sha256::new();
|
let mut hasher = Sha256::new();
|
||||||
hasher.update(data.as_bytes());
|
hasher.update(data);
|
||||||
hex::encode(hasher.finalize())
|
hex::encode(hasher.finalize())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,11 +19,3 @@ pub fn make_store_path(store_dir: &str, ty: &str, hash_hex: &str, name: &str) ->
|
|||||||
|
|
||||||
format!("{}/{}-{}", store_dir, encoded, name)
|
format!("{}/{}-{}", store_dir, encoded, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn output_path_name(drv_name: &str, output_name: &str) -> String {
|
|
||||||
if output_name == "out" {
|
|
||||||
drv_name.to_string()
|
|
||||||
} else {
|
|
||||||
format!("{}-{}", drv_name, output_name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ fn runtime_extension<Ctx: RuntimeContext>() -> Extension {
|
|||||||
op_make_placeholder(),
|
op_make_placeholder(),
|
||||||
op_decode_span::<Ctx>(),
|
op_decode_span::<Ctx>(),
|
||||||
op_make_store_path::<Ctx>(),
|
op_make_store_path::<Ctx>(),
|
||||||
op_output_path_name(),
|
|
||||||
op_parse_hash(),
|
op_parse_hash(),
|
||||||
op_make_fixed_output_path::<Ctx>(),
|
op_make_fixed_output_path::<Ctx>(),
|
||||||
op_add_path::<Ctx>(),
|
op_add_path::<Ctx>(),
|
||||||
@@ -70,6 +69,8 @@ fn runtime_extension<Ctx: RuntimeContext>() -> Extension {
|
|||||||
op_add_filtered_path::<Ctx>(),
|
op_add_filtered_path::<Ctx>(),
|
||||||
op_match(),
|
op_match(),
|
||||||
op_split(),
|
op_split(),
|
||||||
|
op_from_json(),
|
||||||
|
op_from_toml(),
|
||||||
];
|
];
|
||||||
ops.extend(crate::fetcher::register_ops::<Ctx>());
|
ops.extend(crate::fetcher::register_ops::<Ctx>());
|
||||||
|
|
||||||
@@ -132,10 +133,7 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
|
|||||||
deno_core::v8_set_flags(vec!["".into(), format!("--stack-size={}", 8 * 1024)]),
|
deno_core::v8_set_flags(vec!["".into(), format!("--stack-size={}", 8 * 1024)]),
|
||||||
[""]
|
[""]
|
||||||
);
|
);
|
||||||
JsRuntime::init_platform(
|
JsRuntime::init_platform(Some(v8::new_default_platform(0, false).make_shared()));
|
||||||
Some(v8::new_default_platform(0, false).make_shared()),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut js_runtime = JsRuntime::new(RuntimeOptions {
|
let mut js_runtime = JsRuntime::new(RuntimeOptions {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use std::str::FromStr;
|
|||||||
use hashbrown::hash_map::{Entry, HashMap};
|
use hashbrown::hash_map::{Entry, HashMap};
|
||||||
|
|
||||||
use deno_core::OpState;
|
use deno_core::OpState;
|
||||||
|
use deno_core::v8;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use rust_embed::Embed;
|
use rust_embed::Embed;
|
||||||
|
|
||||||
@@ -246,7 +247,7 @@ pub(super) fn op_resolve_path(
|
|||||||
#[deno_core::op2]
|
#[deno_core::op2]
|
||||||
#[string]
|
#[string]
|
||||||
pub(super) fn op_sha256_hex(#[string] data: String) -> String {
|
pub(super) fn op_sha256_hex(#[string] data: String) -> String {
|
||||||
crate::nix_utils::sha256_hex(&data)
|
crate::nix_utils::sha256_hex(data.as_bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deno_core::op2]
|
#[deno_core::op2]
|
||||||
@@ -325,15 +326,6 @@ pub(super) fn op_make_store_path<Ctx: RuntimeContext>(
|
|||||||
crate::nix_utils::make_store_path(store_dir, &ty, &hash_hex, &name)
|
crate::nix_utils::make_store_path(store_dir, &ty, &hash_hex, &name)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deno_core::op2]
|
|
||||||
#[string]
|
|
||||||
pub(super) fn op_output_path_name(
|
|
||||||
#[string] drv_name: String,
|
|
||||||
#[string] output_name: String,
|
|
||||||
) -> String {
|
|
||||||
crate::nix_utils::output_path_name(&drv_name, &output_name)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
#[derive(serde::Serialize)]
|
||||||
pub(super) struct ParsedHash {
|
pub(super) struct ParsedHash {
|
||||||
hex: String,
|
hex: String,
|
||||||
@@ -1048,3 +1040,116 @@ pub(super) enum SplitResult {
|
|||||||
Text(String),
|
Text(String),
|
||||||
Captures(Vec<Option<String>>),
|
Captures(Vec<Option<String>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) enum NixJsonValue {
|
||||||
|
Null,
|
||||||
|
Bool(bool),
|
||||||
|
Int(i64),
|
||||||
|
Float(f64),
|
||||||
|
Str(String),
|
||||||
|
Arr(Vec<NixJsonValue>),
|
||||||
|
Obj(Vec<(String, NixJsonValue)>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> deno_core::convert::ToV8<'a> for NixJsonValue {
|
||||||
|
type Error = deno_error::JsErrorBox;
|
||||||
|
|
||||||
|
fn to_v8<'i>(
|
||||||
|
self,
|
||||||
|
scope: &mut v8::PinScope<'a, 'i>,
|
||||||
|
) -> std::result::Result<v8::Local<'a, v8::Value>, Self::Error> {
|
||||||
|
match self {
|
||||||
|
Self::Null => Ok(v8::null(scope).into()),
|
||||||
|
Self::Bool(b) => Ok(v8::Boolean::new(scope, b).into()),
|
||||||
|
Self::Int(i) => Ok(v8::BigInt::new_from_i64(scope, i).into()),
|
||||||
|
Self::Float(f) => Ok(v8::Number::new(scope, f).into()),
|
||||||
|
Self::Str(s) => v8::String::new(scope, &s)
|
||||||
|
.map(|s| s.into())
|
||||||
|
.ok_or_else(|| deno_error::JsErrorBox::type_error("failed to create v8 string")),
|
||||||
|
Self::Arr(arr) => {
|
||||||
|
let elements = arr
|
||||||
|
.into_iter()
|
||||||
|
.map(|v| v.to_v8(scope))
|
||||||
|
.collect::<std::result::Result<Vec<_>, _>>()?;
|
||||||
|
Ok(v8::Array::new_with_elements(scope, &elements).into())
|
||||||
|
}
|
||||||
|
Self::Obj(entries) => {
|
||||||
|
let obj = v8::Object::new(scope);
|
||||||
|
for (k, v) in entries {
|
||||||
|
let key: v8::Local<v8::Value> = v8::String::new(scope, &k)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
deno_error::JsErrorBox::type_error("failed to create v8 string")
|
||||||
|
})?
|
||||||
|
.into();
|
||||||
|
let val = v.to_v8(scope)?;
|
||||||
|
obj.set(scope, key, val);
|
||||||
|
}
|
||||||
|
Ok(obj.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn json_to_nix(value: serde_json::Value) -> NixJsonValue {
|
||||||
|
match value {
|
||||||
|
serde_json::Value::Null => NixJsonValue::Null,
|
||||||
|
serde_json::Value::Bool(b) => NixJsonValue::Bool(b),
|
||||||
|
serde_json::Value::Number(n) => {
|
||||||
|
if let Some(i) = n.as_i64() {
|
||||||
|
NixJsonValue::Int(i)
|
||||||
|
} else if let Some(f) = n.as_f64() {
|
||||||
|
NixJsonValue::Float(f)
|
||||||
|
} else {
|
||||||
|
NixJsonValue::Float(n.as_u64().unwrap_or(0) as f64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
serde_json::Value::String(s) => NixJsonValue::Str(s),
|
||||||
|
serde_json::Value::Array(arr) => {
|
||||||
|
NixJsonValue::Arr(arr.into_iter().map(json_to_nix).collect())
|
||||||
|
}
|
||||||
|
serde_json::Value::Object(map) => {
|
||||||
|
NixJsonValue::Obj(map.into_iter().map(|(k, v)| (k, json_to_nix(v))).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toml_to_nix(value: toml::Value) -> std::result::Result<NixJsonValue, NixRuntimeError> {
|
||||||
|
match value {
|
||||||
|
toml::Value::String(s) => Ok(NixJsonValue::Str(s)),
|
||||||
|
toml::Value::Integer(i) => Ok(NixJsonValue::Int(i)),
|
||||||
|
toml::Value::Float(f) => Ok(NixJsonValue::Float(f)),
|
||||||
|
toml::Value::Boolean(b) => Ok(NixJsonValue::Bool(b)),
|
||||||
|
toml::Value::Datetime(_) => Err(NixRuntimeError::from(
|
||||||
|
"while parsing TOML: Dates and times are not supported",
|
||||||
|
)),
|
||||||
|
toml::Value::Array(arr) => {
|
||||||
|
let items: std::result::Result<Vec<_>, _> = arr.into_iter().map(toml_to_nix).collect();
|
||||||
|
Ok(NixJsonValue::Arr(items?))
|
||||||
|
}
|
||||||
|
toml::Value::Table(table) => {
|
||||||
|
let entries: std::result::Result<Vec<_>, _> = table
|
||||||
|
.into_iter()
|
||||||
|
.map(|(k, v)| toml_to_nix(v).map(|v| (k, v)))
|
||||||
|
.collect();
|
||||||
|
Ok(NixJsonValue::Obj(entries?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deno_core::op2]
|
||||||
|
pub(super) fn op_from_json(
|
||||||
|
#[string] json_str: String,
|
||||||
|
) -> std::result::Result<NixJsonValue, NixRuntimeError> {
|
||||||
|
let parsed: serde_json::Value = serde_json::from_str(&json_str)
|
||||||
|
.map_err(|e| NixRuntimeError::from(format!("builtins.fromJSON: {e}")))?;
|
||||||
|
Ok(json_to_nix(parsed))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deno_core::op2]
|
||||||
|
pub(super) fn op_from_toml(
|
||||||
|
#[string] toml_str: String,
|
||||||
|
) -> std::result::Result<NixJsonValue, NixRuntimeError> {
|
||||||
|
let parsed: toml::Value = toml::from_str(&toml_str)
|
||||||
|
.map_err(|e| NixRuntimeError::from(format!("while parsing TOML: {e}")))?;
|
||||||
|
toml_to_nix(parsed)
|
||||||
|
}
|
||||||
|
|||||||
@@ -552,9 +552,13 @@ impl NixDaemonClient {
|
|||||||
assert_eq!(type_marker, "Error");
|
assert_eq!(type_marker, "Error");
|
||||||
|
|
||||||
let level = NixDaemonErrorLevel::try_from_primitive(
|
let level = NixDaemonErrorLevel::try_from_primitive(
|
||||||
self.reader.read_number().await?.try_into().unwrap(),
|
self.reader
|
||||||
|
.read_number()
|
||||||
|
.await?
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| IoError::other("invalid nix-daemon error level"))?,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.map_err(|_| IoError::other("invalid nix-daemon error level"))?;
|
||||||
// removed
|
// removed
|
||||||
let _name: String = self.reader.read_value().await?;
|
let _name: String = self.reader.read_value().await?;
|
||||||
let msg: String = self.reader.read_value().await?;
|
let msg: String = self.reader.read_value().await?;
|
||||||
|
|||||||
@@ -268,12 +268,66 @@ fn escape_quote_string(s: &str) -> String {
|
|||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Format a float matching C's `printf("%g", x)` with default precision 6.
|
||||||
|
fn fmt_nix_float(f: &mut Formatter<'_>, x: f64) -> FmtResult {
|
||||||
|
if !x.is_finite() {
|
||||||
|
return write!(f, "{x}");
|
||||||
|
}
|
||||||
|
if x == 0.0 {
|
||||||
|
return if x.is_sign_negative() {
|
||||||
|
write!(f, "-0")
|
||||||
|
} else {
|
||||||
|
write!(f, "0")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let precision: i32 = 6;
|
||||||
|
let exp = x.abs().log10().floor() as i32;
|
||||||
|
|
||||||
|
let formatted = if exp >= -4 && exp < precision {
|
||||||
|
let decimal_places = (precision - 1 - exp) as usize;
|
||||||
|
format!("{x:.decimal_places$}")
|
||||||
|
} else {
|
||||||
|
let sig_digits = (precision - 1) as usize;
|
||||||
|
let s = format!("{x:.sig_digits$e}");
|
||||||
|
let (mantissa, exp_part) = s
|
||||||
|
.split_once('e')
|
||||||
|
.expect("scientific notation must contain 'e'");
|
||||||
|
let (sign, digits) = if let Some(d) = exp_part.strip_prefix('-') {
|
||||||
|
("-", d)
|
||||||
|
} else if let Some(d) = exp_part.strip_prefix('+') {
|
||||||
|
("+", d)
|
||||||
|
} else {
|
||||||
|
("+", exp_part)
|
||||||
|
};
|
||||||
|
if digits.len() < 2 {
|
||||||
|
format!("{mantissa}e{sign}0{digits}")
|
||||||
|
} else {
|
||||||
|
format!("{mantissa}e{sign}{digits}")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if formatted.contains('.') {
|
||||||
|
if let Some(e_pos) = formatted.find('e') {
|
||||||
|
let trimmed = formatted[..e_pos]
|
||||||
|
.trim_end_matches('0')
|
||||||
|
.trim_end_matches('.');
|
||||||
|
write!(f, "{}{}", trimmed, &formatted[e_pos..])
|
||||||
|
} else {
|
||||||
|
let trimmed = formatted.trim_end_matches('0').trim_end_matches('.');
|
||||||
|
write!(f, "{trimmed}")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
write!(f, "{formatted}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for Value {
|
impl Display for Value {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||||
use Value::*;
|
use Value::*;
|
||||||
match self {
|
match self {
|
||||||
&Int(x) => write!(f, "{x}"),
|
&Int(x) => write!(f, "{x}"),
|
||||||
&Float(x) => write!(f, "{x}"),
|
&Float(x) => fmt_nix_float(f, x),
|
||||||
&Bool(x) => write!(f, "{x}"),
|
&Bool(x) => write!(f, "{x}"),
|
||||||
Null => write!(f, "null"),
|
Null => write!(f, "null"),
|
||||||
String(x) => write!(f, "{}", escape_quote_string(x)),
|
String(x) => write!(f, "{}", escape_quote_string(x)),
|
||||||
@@ -302,7 +356,7 @@ impl Display for ValueCompatDisplay<'_> {
|
|||||||
use Value::*;
|
use Value::*;
|
||||||
match self.0 {
|
match self.0 {
|
||||||
&Int(x) => write!(f, "{x}"),
|
&Int(x) => write!(f, "{x}"),
|
||||||
&Float(x) => write!(f, "{x}"),
|
&Float(x) => fmt_nix_float(f, x),
|
||||||
&Bool(x) => write!(f, "{x}"),
|
&Bool(x) => write!(f, "{x}"),
|
||||||
Null => write!(f, "null"),
|
Null => write!(f, "null"),
|
||||||
String(x) => write!(f, "{}", escape_quote_string(x)),
|
String(x) => write!(f, "{}", escape_quote_string(x)),
|
||||||
|
|||||||
@@ -152,12 +152,9 @@ eval_okay_test!(foldlStrict_lazy_elements);
|
|||||||
eval_okay_test!(foldlStrict_lazy_initial_accumulator);
|
eval_okay_test!(foldlStrict_lazy_initial_accumulator);
|
||||||
eval_okay_test!(fromjson);
|
eval_okay_test!(fromjson);
|
||||||
eval_okay_test!(fromjson_escapes);
|
eval_okay_test!(fromjson_escapes);
|
||||||
|
eval_okay_test!(fromTOML);
|
||||||
eval_okay_test!(
|
eval_okay_test!(
|
||||||
#[ignore = "not implemented: fromTOML"]
|
#[ignore = "timestamps are not supported"]
|
||||||
fromTOML
|
|
||||||
);
|
|
||||||
eval_okay_test!(
|
|
||||||
#[ignore = "not implemented: fromTOML"]
|
|
||||||
fromTOML_timestamps
|
fromTOML_timestamps
|
||||||
);
|
);
|
||||||
eval_okay_test!(functionargs);
|
eval_okay_test!(functionargs);
|
||||||
@@ -209,11 +206,15 @@ eval_okay_test!(
|
|||||||
eval_okay_test!(partition);
|
eval_okay_test!(partition);
|
||||||
eval_okay_test!(path);
|
eval_okay_test!(path);
|
||||||
eval_okay_test!(pathexists);
|
eval_okay_test!(pathexists);
|
||||||
eval_okay_test!(path_string_interpolation, || {
|
eval_okay_test!(
|
||||||
|
#[ignore = "rnix 0.13 regression: /${foo}-/*...*/ fails to parse"]
|
||||||
|
path_string_interpolation,
|
||||||
|
|| {
|
||||||
unsafe {
|
unsafe {
|
||||||
std::env::set_var("HOME", "/fake-home");
|
std::env::set_var("HOME", "/fake-home");
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
);
|
||||||
eval_okay_test!(patterns);
|
eval_okay_test!(patterns);
|
||||||
eval_okay_test!(print);
|
eval_okay_test!(print);
|
||||||
eval_okay_test!(readDir);
|
eval_okay_test!(readDir);
|
||||||
|
|||||||
@@ -489,3 +489,76 @@ fn split_no_match_preserves_context() {
|
|||||||
);
|
);
|
||||||
assert_eq!(result, Value::Bool(true));
|
assert_eq!(result, Value::Bool(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn builtins_path_has_context() {
|
||||||
|
let temp_dir = tempfile::tempdir().unwrap();
|
||||||
|
let test_file = temp_dir.path().join("test.txt");
|
||||||
|
std::fs::write(&test_file, "hello").unwrap();
|
||||||
|
|
||||||
|
let expr = format!(
|
||||||
|
r#"builtins.hasContext (builtins.path {{ path = {}; name = "test-ctx"; }})"#,
|
||||||
|
test_file.display()
|
||||||
|
);
|
||||||
|
let result = eval(&expr);
|
||||||
|
assert_eq!(result, Value::Bool(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn builtins_path_context_tracked_in_structured_attrs_derivation() {
|
||||||
|
let temp_dir = tempfile::tempdir().unwrap();
|
||||||
|
let test_file = temp_dir.path().join("test-patch.txt");
|
||||||
|
std::fs::write(&test_file, "patch content").unwrap();
|
||||||
|
|
||||||
|
let expr = format!(
|
||||||
|
r#"
|
||||||
|
let
|
||||||
|
patch = builtins.path {{ path = {}; name = "test-patch"; }};
|
||||||
|
in
|
||||||
|
(derivation {{
|
||||||
|
__structuredAttrs = true;
|
||||||
|
name = "test-input-srcs";
|
||||||
|
system = "x86_64-linux";
|
||||||
|
builder = "/bin/sh";
|
||||||
|
patches = [ patch ];
|
||||||
|
}}).drvPath
|
||||||
|
"#,
|
||||||
|
test_file.display()
|
||||||
|
);
|
||||||
|
let result = eval(&expr);
|
||||||
|
|
||||||
|
if let Value::String(s) = &result {
|
||||||
|
assert!(s.contains("/nix/store/"), "drvPath should be a store path");
|
||||||
|
} else {
|
||||||
|
panic!("Expected string, got {:?}", result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn builtins_path_context_tracked_in_non_structured_derivation() {
|
||||||
|
let temp_dir = tempfile::tempdir().unwrap();
|
||||||
|
let test_file = temp_dir.path().join("dep.txt");
|
||||||
|
std::fs::write(&test_file, "dependency content").unwrap();
|
||||||
|
|
||||||
|
let expr = format!(
|
||||||
|
r#"
|
||||||
|
let
|
||||||
|
dep = builtins.path {{ path = {}; name = "dep-file"; }};
|
||||||
|
in
|
||||||
|
(derivation {{
|
||||||
|
name = "test-non-structured";
|
||||||
|
system = "x86_64-linux";
|
||||||
|
builder = "/bin/sh";
|
||||||
|
myDep = dep;
|
||||||
|
}}).drvPath
|
||||||
|
"#,
|
||||||
|
test_file.display()
|
||||||
|
);
|
||||||
|
let result = eval(&expr);
|
||||||
|
|
||||||
|
if let Value::String(s) = &result {
|
||||||
|
assert!(s.contains("/nix/store/"), "drvPath should be a store path");
|
||||||
|
} else {
|
||||||
|
panic!("Expected string, got {:?}", result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ let
|
|||||||
flake = (
|
flake = (
|
||||||
import flake-compat {
|
import flake-compat {
|
||||||
src = ./.;
|
src = ./.;
|
||||||
|
copySourceTreeToStore = false;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
in
|
in
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
[files]
|
[files]
|
||||||
extend-exclude = [
|
extend-exclude = [
|
||||||
"nix-js/tests/regex.rs"
|
"nix-js/tests/regex.rs",
|
||||||
|
"nix-js/tests/lang",
|
||||||
]
|
]
|
||||||
|
|
||||||
[default.extend-words]
|
[default.extend-words]
|
||||||
|
|||||||
Reference in New Issue
Block a user