diff --git a/src/cli.bux b/src/cli.bux index d97b00d..193e69e 100644 --- a/src/cli.bux +++ b/src/cli.bux @@ -620,6 +620,115 @@ func Cli_Init() -> int { // Test command — build and run tests // --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// Add — add a dependency to bux.toml +// --------------------------------------------------------------------------- + +func Cli_Add(pkgName: String, url: String) -> int { + let cwd: String = bux_getcwd(); + let tomlPath: String = bux_path_join(cwd, "bux.toml"); + if !FileExists(tomlPath) { + PrintLine("Error: no bux.toml found"); + return 1; + } + var man: Manifest = Manifest_Load(tomlPath); + if Manifest_HasDep(man, pkgName) { + Print("Error: dependency '"); Print(pkgName); PrintLine("' already exists"); + return 1; + } + let added: bool = Manifest_AddDep(&man, pkgName, url); + if !added { + PrintLine("Error: cannot add dependency (max 8)"); + return 1; + } + let tomlContent: String = Manifest_ToString(man); + if !WriteFile(tomlPath, tomlContent) { + PrintLine("Error: cannot write bux.toml"); + return 1; + } + Print("Added dependency: "); Print(pkgName); Print(" = "); PrintLine(url); + return 0; +} + +// --------------------------------------------------------------------------- +// Remove — remove a dependency from bux.toml +// --------------------------------------------------------------------------- + +func Cli_Remove(pkgName: String) -> int { + let cwd: String = bux_getcwd(); + let tomlPath: String = bux_path_join(cwd, "bux.toml"); + if !FileExists(tomlPath) { + PrintLine("Error: no bux.toml found"); + return 1; + } + var man: Manifest = Manifest_Load(tomlPath); + if !Manifest_HasDep(man, pkgName) { + Print("Error: dependency '"); Print(pkgName); PrintLine("' not found"); + return 1; + } + let removed: bool = Manifest_RemoveDep(&man, pkgName); + if !removed { + PrintLine("Error: cannot remove dependency"); + return 1; + } + let tomlContent: String = Manifest_ToString(man); + if !WriteFile(tomlPath, tomlContent) { + PrintLine("Error: cannot write bux.toml"); + return 1; + } + Print("Removed dependency: "); PrintLine(pkgName); + return 0; +} + +// --------------------------------------------------------------------------- +// Fetch — download dependencies into deps/ +// --------------------------------------------------------------------------- + +func Cli_Fetch() -> int { + let cwd: String = bux_getcwd(); + let tomlPath: String = bux_path_join(cwd, "bux.toml"); + if !FileExists(tomlPath) { + PrintLine("Error: no bux.toml found"); + return 1; + } + let man: Manifest = Manifest_Load(tomlPath); + if man.depCount == 0 { + PrintLine("No dependencies to fetch"); + return 0; + } + let depsDir: String = bux_path_join(cwd, "deps"); + discard bux_mkdir_if_needed(depsDir); + var i: int = 0; + while i < man.depCount { + var depName: String = ""; + var depUrl: String = ""; + if i == 0 { depName = man.depName0; depUrl = man.depUrl0; } + else if i == 1 { depName = man.depName1; depUrl = man.depUrl1; } + else if i == 2 { depName = man.depName2; depUrl = man.depUrl2; } + else if i == 3 { depName = man.depName3; depUrl = man.depUrl3; } + else if i == 4 { depName = man.depName4; depUrl = man.depUrl4; } + else if i == 5 { depName = man.depName5; depUrl = man.depUrl5; } + else if i == 6 { depName = man.depName6; depUrl = man.depUrl6; } + else if i == 7 { depName = man.depName7; depUrl = man.depUrl7; } + let depPath: String = bux_path_join(depsDir, depName); + if DirExists(depPath) { + Print("Updating "); PrintLine(depName); + let cmd: String = String_Concat("cd \"", String_Concat(depPath, "\" && git pull --quiet 2>/dev/null")); + discard bux_system(cmd); + } else { + Print("Fetching "); Print(depName); Print(" from "); PrintLine(depUrl); + let cmd: String = String_Concat("git clone --quiet \"", String_Concat(depUrl, String_Concat("\" \"", String_Concat(depPath, "\"")))); + let rc: int = bux_system(cmd); + if rc != 0 { + Print("Error: failed to fetch "); PrintLine(depName); + } + } + i = i + 1; + } + PrintLine("Fetch complete"); + return 0; +} + // --------------------------------------------------------------------------- // Fmt command — format source files // --------------------------------------------------------------------------- @@ -835,6 +944,43 @@ func Cli_BuildProject(projectDir: String) -> int { PrintLine(""); } + // Merge dependency declarations (deps shadow stdlib, user shadows deps) + if man.depCount > 0 { + let depsDir: String = bux_path_join(projectDir, "deps"); + var di: int = 0; + while di < man.depCount { + var depName: String = ""; + if di == 0 { depName = man.depName0; } + else if di == 1 { depName = man.depName1; } + else if di == 2 { depName = man.depName2; } + else if di == 3 { depName = man.depName3; } + else if di == 4 { depName = man.depName4; } + else if di == 5 { depName = man.depName5; } + else if di == 6 { depName = man.depName6; } + else if di == 7 { depName = man.depName7; } + let depSrcDir: String = bux_path_join(bux_path_join(depsDir, depName), "src"); + if DirExists(depSrcDir) { + var depFileCount: int = 0; + let depFiles: *String = bux_list_dir(depSrcDir, ".bux", &depFileCount); + if depFileCount > 0 { + Print("Merging dependency: "); + PrintLine(depName); + var dfi: int = 0; + while dfi < depFileCount { + Print(" Merging "); + PrintLine(depFiles[dfi]); + let added: int = Cli_MergeFileInto(merged, depFiles[dfi], userNames, userNameCount); + Print(" -> "); + PrintInt(added as int64); + PrintLine(" decls added"); + dfi = dfi + 1; + } + } + } + di = di + 1; + } + } + // Copy user declarations into merged (user shadows stdlib) Cli_CopyModuleDecls(merged, userMerged); @@ -955,7 +1101,7 @@ func Cli_Run(args: *String, argCount: int) -> int { if argCount < 2 { PrintLine("Bux Self-Hosting Compiler v0.2.0"); PrintLine("Usage: buxc [args]"); - PrintLine("Commands: build, check, new, init, fmt, test, run, project, help, version"); + PrintLine("Commands: build, check, new, init, add, remove, fetch, fmt, test, run, project, help, version"); return 0; } @@ -968,7 +1114,7 @@ func Cli_Run(args: *String, argCount: int) -> int { if String_Eq(cmd, "help") || String_Eq(cmd, "--help") || String_Eq(cmd, "-h") { PrintLine("Bux Self-Hosting Compiler v0.2.0"); PrintLine("Usage: buxc [args]"); - PrintLine("Commands: build, check, new, init, fmt, test, run, project, help, version"); + PrintLine("Commands: build, check, new, init, add, remove, fetch, fmt, test, run, project, help, version"); PrintLine("Pipeline modules:"); PrintLine(" Lexer ✅ 695 lines"); PrintLine(" Parser ✅ 1004 lines"); @@ -991,6 +1137,26 @@ func Cli_Run(args: *String, argCount: int) -> int { return Cli_Init(); } + if String_Eq(cmd, "add") { + if argCount < 4 { + PrintLine("Usage: buxc add "); + return 1; + } + return Cli_Add(args[2], args[3]); + } + + if String_Eq(cmd, "remove") { + if argCount < 3 { + PrintLine("Usage: buxc remove "); + return 1; + } + return Cli_Remove(args[2]); + } + + if String_Eq(cmd, "fetch") { + return Cli_Fetch(); + } + if String_Eq(cmd, "fmt") { let dir: String = "."; if argCount >= 3 { dir = args[2]; } diff --git a/src/manifest.bux b/src/manifest.bux index 47df46d..4003b09 100644 --- a/src/manifest.bux +++ b/src/manifest.bux @@ -12,6 +12,23 @@ struct Manifest { version: String; pkgType: String; output: String; + depCount: int; + depName0: String; + depUrl0: String; + depName1: String; + depUrl1: String; + depName2: String; + depUrl2: String; + depName3: String; + depUrl3: String; + depName4: String; + depUrl4: String; + depName5: String; + depUrl5: String; + depName6: String; + depUrl6: String; + depName7: String; + depUrl7: String; } // --------------------------------------------------------------------------- @@ -24,6 +41,7 @@ func Manifest_Parse(content: String) -> Manifest { m.version = "0.1.0"; m.pkgType = "bin"; m.output = "Bin"; + m.depCount = 0; if String_Eq(content, "") { return m; } @@ -43,6 +61,8 @@ func Manifest_Parse(content: String) -> Manifest { currentSection = "Package"; } else if String_StartsWith(line, "[Build]") { currentSection = "Build"; + } else if String_StartsWith(line, "[dependencies]") { + currentSection = "dependencies"; } else { currentSection = ""; } @@ -70,6 +90,18 @@ func Manifest_Parse(content: String) -> Manifest { if String_Eq(key, "Type") { m.pkgType = val; } } else if String_Eq(currentSection, "Build") { if String_Eq(key, "Output") { m.output = val; } + } else if String_Eq(currentSection, "dependencies") { + if m.depCount < 8 { + if m.depCount == 0 { m.depName0 = key; m.depUrl0 = val; } + else if m.depCount == 1 { m.depName1 = key; m.depUrl1 = val; } + else if m.depCount == 2 { m.depName2 = key; m.depUrl2 = val; } + else if m.depCount == 3 { m.depName3 = key; m.depUrl3 = val; } + else if m.depCount == 4 { m.depName4 = key; m.depUrl4 = val; } + else if m.depCount == 5 { m.depName5 = key; m.depUrl5 = val; } + else if m.depCount == 6 { m.depName6 = key; m.depUrl6 = val; } + else if m.depCount == 7 { m.depName7 = key; m.depUrl7 = val; } + m.depCount = m.depCount + 1; + } } } i = i + 1; @@ -78,6 +110,138 @@ func Manifest_Parse(content: String) -> Manifest { return m; } +// --------------------------------------------------------------------------- +// Dependency helpers +// --------------------------------------------------------------------------- + +func Manifest_HasDep(m: Manifest, name: String) -> bool { + var i: int = 0; + while i < m.depCount { + var depName: String = ""; + if i == 0 { depName = m.depName0; } + else if i == 1 { depName = m.depName1; } + else if i == 2 { depName = m.depName2; } + else if i == 3 { depName = m.depName3; } + else if i == 4 { depName = m.depName4; } + else if i == 5 { depName = m.depName5; } + else if i == 6 { depName = m.depName6; } + else if i == 7 { depName = m.depName7; } + if String_Eq(depName, name) { return true; } + i = i + 1; + } + return false; +} + +func Manifest_GetDepUrl(m: Manifest, name: String) -> String { + var i: int = 0; + while i < m.depCount { + var depName: String = ""; + var depUrl: String = ""; + if i == 0 { depName = m.depName0; depUrl = m.depUrl0; } + else if i == 1 { depName = m.depName1; depUrl = m.depUrl1; } + else if i == 2 { depName = m.depName2; depUrl = m.depUrl2; } + else if i == 3 { depName = m.depName3; depUrl = m.depUrl3; } + else if i == 4 { depName = m.depName4; depUrl = m.depUrl4; } + else if i == 5 { depName = m.depName5; depUrl = m.depUrl5; } + else if i == 6 { depName = m.depName6; depUrl = m.depUrl6; } + else if i == 7 { depName = m.depName7; depUrl = m.depUrl7; } + if String_Eq(depName, name) { return depUrl; } + i = i + 1; + } + return ""; +} + +func Manifest_AddDep(m: *Manifest, name: String, url: String) -> bool { + if Manifest_HasDep(*m, name) { return false; } + if m.depCount >= 8 { return false; } + if m.depCount == 0 { m.depName0 = name; m.depUrl0 = url; } + else if m.depCount == 1 { m.depName1 = name; m.depUrl1 = url; } + else if m.depCount == 2 { m.depName2 = name; m.depUrl2 = url; } + else if m.depCount == 3 { m.depName3 = name; m.depUrl3 = url; } + else if m.depCount == 4 { m.depName4 = name; m.depUrl4 = url; } + else if m.depCount == 5 { m.depName5 = name; m.depUrl5 = url; } + else if m.depCount == 6 { m.depName6 = name; m.depUrl6 = url; } + else if m.depCount == 7 { m.depName7 = name; m.depUrl7 = url; } + m.depCount = m.depCount + 1; + return true; +} + +func Manifest_RemoveDep(m: *Manifest, name: String) -> bool { + var found: int = -1; + var i: int = 0; + while i < m.depCount { + var depName: String = ""; + if i == 0 { depName = m.depName0; } + else if i == 1 { depName = m.depName1; } + else if i == 2 { depName = m.depName2; } + else if i == 3 { depName = m.depName3; } + else if i == 4 { depName = m.depName4; } + else if i == 5 { depName = m.depName5; } + else if i == 6 { depName = m.depName6; } + else if i == 7 { depName = m.depName7; } + if String_Eq(depName, name) { found = i; break; } + i = i + 1; + } + if found < 0 { return false; } + i = found; + while i < m.depCount - 1 { + if i == 0 { m.depName0 = m.depName1; m.depUrl0 = m.depUrl1; } + else if i == 1 { m.depName1 = m.depName2; m.depUrl1 = m.depUrl2; } + else if i == 2 { m.depName2 = m.depName3; m.depUrl2 = m.depUrl3; } + else if i == 3 { m.depName3 = m.depName4; m.depUrl3 = m.depUrl4; } + else if i == 4 { m.depName4 = m.depName5; m.depUrl4 = m.depUrl5; } + else if i == 5 { m.depName5 = m.depName6; m.depUrl5 = m.depUrl6; } + else if i == 6 { m.depName6 = m.depName7; m.depUrl6 = m.depUrl7; } + i = i + 1; + } + m.depCount = m.depCount - 1; + return true; +} + +// --------------------------------------------------------------------------- +// Serialize manifest back to TOML +// --------------------------------------------------------------------------- + +func Manifest_ToString(m: Manifest) -> String { + let sb: StringBuilder = StringBuilder_New(); + StringBuilder_Append(&sb, "[Package]\n"); + StringBuilder_Append(&sb, "Name = \""); + StringBuilder_Append(&sb, m.name); + StringBuilder_Append(&sb, "\"\n"); + StringBuilder_Append(&sb, "Version = \""); + StringBuilder_Append(&sb, m.version); + StringBuilder_Append(&sb, "\"\n"); + StringBuilder_Append(&sb, "Type = \""); + StringBuilder_Append(&sb, m.pkgType); + StringBuilder_Append(&sb, "\"\n\n"); + StringBuilder_Append(&sb, "[Build]\n"); + StringBuilder_Append(&sb, "Output = \""); + StringBuilder_Append(&sb, m.output); + StringBuilder_Append(&sb, "\"\n"); + if m.depCount > 0 { + StringBuilder_Append(&sb, "\n[dependencies]\n"); + var i: int = 0; + while i < m.depCount { + var depName: String = ""; + var depUrl: String = ""; + if i == 0 { depName = m.depName0; depUrl = m.depUrl0; } + else if i == 1 { depName = m.depName1; depUrl = m.depUrl1; } + else if i == 2 { depName = m.depName2; depUrl = m.depUrl2; } + else if i == 3 { depName = m.depName3; depUrl = m.depUrl3; } + else if i == 4 { depName = m.depName4; depUrl = m.depUrl4; } + else if i == 5 { depName = m.depName5; depUrl = m.depUrl5; } + else if i == 6 { depName = m.depName6; depUrl = m.depUrl6; } + else if i == 7 { depName = m.depName7; depUrl = m.depUrl7; } + StringBuilder_Append(&sb, depName); + StringBuilder_Append(&sb, " = \""); + StringBuilder_Append(&sb, depUrl); + StringBuilder_Append(&sb, "\"\n"); + i = i + 1; + } + } + return StringBuilder_Build(&sb); +} + // --------------------------------------------------------------------------- // Load manifest from file // ---------------------------------------------------------------------------