diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..508eff8 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,20 @@ +name: ci +on: + push: + branches: + - main + pull_request: + branches: + - main + + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: denoland/setup-deno@v2 + with: + deno-version: v2.x + - name: Run tests + run: deno test -A \ No newline at end of file diff --git a/.gitignore b/.gitignore index 663f3ef..6d4e12c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ linux-stable vmlinux-builder -.config \ No newline at end of file +.config +.config.toml \ No newline at end of file diff --git a/config.ts b/config.ts index 16ba447..35db7c3 100644 --- a/config.ts +++ b/config.ts @@ -22,10 +22,7 @@ const ConfigValueSchema = z.union([ // Individual config entry const ConfigEntrySchema = z.object({ key: z.string(), - value: z.union([ - ConfigValueSchema, - z.null(), // For commented out options (# CONFIG_* is not set) - ]), + value: ConfigValueSchema.optional(), comment: z.string().optional(), }); @@ -55,7 +52,7 @@ export const KernelConfigSchema = z.object({ }) .optional(), sections: z.array(ConfigSectionSchema), - flatConfig: z.record(z.string(), ConfigValueSchema.or(z.null())), + flatConfig: z.record(z.string(), ConfigValueSchema.optional()), }); // Specific schemas for common config categories @@ -114,9 +111,9 @@ export class KernelConfigParser { */ static parse(content: string): KernelConfig { const lines = content.split("\n"); - const flatConfig: Record = {}; + const flatConfig: Record = {}; const sections: ConfigSection[] = []; - let currentSection: ConfigSection | null = null; + let currentSection: ConfigSection | undefined = undefined; const sectionStack: ConfigSection[] = []; for (const line of lines) { @@ -132,7 +129,7 @@ export class KernelConfigParser { // Check for section end if (sectionName.startsWith("end of")) { if (sectionStack.length > 0) { - currentSection = sectionStack.pop() || null; + currentSection = sectionStack.pop(); } continue; } @@ -164,11 +161,11 @@ export class KernelConfigParser { const disabledMatch = trimmed.match(/^#\s*(CONFIG_\w+)\s+is not set/); if (disabledMatch) { const key = disabledMatch[1]; - flatConfig[key] = null; + flatConfig[key] = undefined; const entry: ConfigEntry = { key, - value: null, + value: undefined, comment: "is not set", }; @@ -286,8 +283,8 @@ export class KernelConfigParser { /** * Get a specific config value */ - static getValue(config: KernelConfig, key: string): ConfigValue | null { - return config.flatConfig[key] ?? null; + static getValue(config: KernelConfig, key: string): ConfigValue | undefined { + return config.flatConfig[key]; } /** @@ -373,7 +370,7 @@ export class KernelConfigParser { private static serializeFlatConfig( lines: string[], - flatConfig: Record, + flatConfig: Record, opts: Required ): void { const keys = opts.sortKeys @@ -396,7 +393,7 @@ export class KernelConfigParser { ): void { const { key, value, comment } = entry; - if (value === null) { + if (!value) { lines.push(`# ${key} is not set`); } else if (value === "y" || value === "m" || value === "n") { lines.push(`${key}=${value}`); @@ -522,7 +519,7 @@ export class KernelConfigDeserializer { try { const data = toml.parse(tomlString); return KernelConfigDeserializer.fromObject( - data as Record + data as Record ); } catch (error) { throw new Error(`Failed to deserialize TOML: ${error}`); @@ -532,15 +529,17 @@ export class KernelConfigDeserializer { /** * Deserialize from YAML-like format */ - static fromObject(obj: Record): KernelConfig { - const flatConfig: Record = {}; + static fromObject( + obj: Record + ): KernelConfig { + const flatConfig: Record = {}; for (const [key, value] of Object.entries(obj)) { if (key.startsWith("CONFIG_")) { if (value === true) { flatConfig[key] = "y"; - } else if (value === false || value === null) { - flatConfig[key] = null; + } else if (value === false || value === undefined) { + flatConfig[key] = undefined; } else if (value === "y" || value === "m" || value === "n") { flatConfig[key] = value; } else if (typeof value === "number" || typeof value === "string") { @@ -594,15 +593,15 @@ export class KernelConfigSerializer { static toObject( config: KernelConfig, booleanStyle: boolean = false - ): Record { - const result: Record = {}; + ): Record { + const result: Record = {}; for (const [key, value] of Object.entries(config.flatConfig)) { if (booleanStyle) { // Convert y/n to true/false if (value === "y" || value === "m") { result[key] = true; - } else if (value === "n" || value === null) { + } else if (value === "n" || value === undefined) { result[key] = false; } else { result[key] = value; @@ -704,7 +703,7 @@ export class KernelConfigSerializer { lines.push(""); for (const [key, value] of Object.entries(config.flatConfig)) { - if (value === null) { + if (!value) { lines.push(`# ${key} is not set`); } else { const shellValue = diff --git a/config_test.ts b/config_test.ts index a712cbb..2097e62 100644 --- a/config_test.ts +++ b/config_test.ts @@ -76,7 +76,7 @@ Deno.test("parse string values", () => { Deno.test("parse disabled options", () => { const config = KernelConfigDeserializer.deserialize(simpleConfig); - assertEquals(config.flatConfig.CONFIG_DEBUG, null); + assertEquals(config.flatConfig.CONFIG_DEBUG, undefined); }); Deno.test("parse hex values", () => { @@ -95,7 +95,7 @@ Deno.test("deserialize from object", () => { const obj = { CONFIG_SMP: true, CONFIG_DEBUG: false }; const config = KernelConfigDeserializer.fromObject(obj); assertEquals(config.flatConfig.CONFIG_SMP, "y"); - assertEquals(config.flatConfig.CONFIG_DEBUG, null); + assertEquals(config.flatConfig.CONFIG_DEBUG, undefined); }); Deno.test("handle empty config", () => { @@ -330,7 +330,7 @@ Deno.test("getValue returns correct value", () => { Deno.test("getValue returns null for non-existent key", () => { const config = KernelConfigDeserializer.deserialize(simpleConfig); const value = KernelConfigParser.getValue(config, "CONFIG_NONEXISTENT"); - assertEquals(value, null); + assertEquals(value, undefined); }); Deno.test("isEnabled detects enabled options", () => { diff --git a/example-toml.ts b/example-toml.ts new file mode 100644 index 0000000..23f298a --- /dev/null +++ b/example-toml.ts @@ -0,0 +1,8 @@ + +import cfg from "./.default-config" with { type: "text" }; +import { KernelConfigSerializer, parseKernelConfigFile } from './config.ts'; + +const config = parseKernelConfigFile(cfg); + +console.log("# Parsed Kernel Configuration:"); +console.log(KernelConfigSerializer.toTOML(config)); diff --git a/example.ts b/example.ts new file mode 100644 index 0000000..b75321a --- /dev/null +++ b/example.ts @@ -0,0 +1,8 @@ + +import cfg from "./.default-config" with { type: "text" }; +import { parseKernelConfigFile } from './config.ts'; + +const config = parseKernelConfigFile(cfg); + +console.log("Parsed Kernel Configuration:"); +console.log(config);