Author:ptrace Comitter:ptrace Date:2025-11-30 01:45:23 UTC

Introducing a better way to manage multiple and complex templates

diff --git a/.gitignore b/.gitignore index a56aaef..c3ad25d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ # Jai .build jinit add_feat.md diff --git a/LICENSE b/LICENSE index 9c058e4..8117f2c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) Copyright (c) 2025 adam@p-trace.com, key.p-trace.com, Key ID: E766CB298A6D1E64 Copyright (c) 2025  dev@ptrace.dev Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/README.md b/README.md index d0f1f41..65b9622 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,128 @@ # Jai Init Smol program that creates some basic files and folders for Jai development.   This program creates files and directories based on templates for Jai development. Adapt it to your own needs.   > [!WARNING] > Version 2.0.0 introduced a lot of changes. If you want to stick to v1.x.x, > visit [this branch](https://codeberg.org/ptrace/jai-init/src/branch/1.0.0) ## Build ## Build / Install ```plain $ jai -version Version: beta 0.2.018, built on 11 October 2025. Version: beta 0.2.020, built on 27 November 2025. ``` ```plain $ jai jinit.jai $ jai build.jai - silent release ``` - The executable is stored as `bin/jinit`. - Add the binary to your $PATH or symlink it where appropriate ## Usage - add the binary to your $PATH or symlink it where appropriate - `$ jinit -help` for available args Init a minimal project:   `$ jinit -t min`   Init a library project:   `$ jinit -t lib -r my_lib_name`   Don't create a scm repo:   `$ jinit -t min -g`   **Overview** ``` $ init -help     -g: If provided, no SCM repository will be created     -l: List available template types     -r: Replace the replace marker with a given string     -t: Pass a template type     -help, -HELP, -?: Show the list of commands. ``` ### Arg: `-g` The default SCM is Git. But you can change it inside the `build.jai`. ### Arg: `-r <string>` Some templates support "replace markers". If they're present, you can pass a string via `-r <string>` and those markers get replaced with  your string. More informations inside `templates/lib/rules_lib.jai`. ### Arg: `-t <template type>` You can specify the exact template that should be used here.  Use `-l` to list all available templates.   You can omit this argument, then it will use the default template, which  you can configure in `build.jai`. ## Templates You can create multiple `build.jai` templates and invoke them with args.   A template consists of two files: `rules_*.jai` and `template.jai`. The `template.jai` can be named however you want. It contains the build  script, which will be known as `build.jai` if you initialize your project.   The `rules_*.jai` has a strict naming, otherwise this program cannot find it.   In this file we declare which files and directories should be created upfront.   ### Examples For example the `templates/minimal` creates this structure:   ``` . ├── .git ├── .gitignore ├── build.jai ├── README.md └── src     └── main.jai ``` Where the `templates/lib` creates this:   ``` . ├── .git ├── .gitignore ├── build.jai ├── my_lib │   ├── lib.jai │   └── module.jai ├── README.md └── test     └── test.jai ``` ### Create a Template You can create a new template with those steps:   1. create a new template directory in `templates/<your dir>` 1. copy the `empty` (or any) template as starting point `$ cp -r empty <your dir>` 1. start creating your build script in `template.jai` 1. adapt the configuration in `rules_*.jai` 1. recompile `jinit` via `$ jai build.jai` **Note:** Inside the `src/main.jai` you can enable the `DRY_RUN` flag, which  won't create any files and directories. It just logs the actions instead.   ## How does it Work All templates get baked into the binary. So the final executable does not depend  on any template files at runtime.   Basically, the `build.jai` collects all `rules_*.jai` files and creates the  necessary data structures, which get baked into the `src/main.jai` file. The `main.jai` file evaluates at compile time all rules and creates the final  template as static `[N]Rules` array.   For example, there's a template inside `jinit.jai` called `CONTENT_BUILD_VARIANT_MINIMAL`,  it contains a very small `build.jai` script.   You can create it with `jinit -v1`.   You can also add own templates: - create a new multiline string that contains your build template - inside the struct `Arguments`, add a new argument - inside the proc `create_files()`, add it to the `if args` construct - optionally, define a default template by adapting the constant `DEFAULT_CONTENT_BUILD_FILE` ## Credits Procs `build_release` and `build_debug` are adapted from the [Jai Wiki](https://github.com/Ivo-Balbaert/The_Way_to_Jai/blob/main/book/30B_Manipulating_the_build_process.md) diff --git a/build.jai b/build.jai new file mode 100644 index 0000000..79e54bd --- /dev/null +++ b/build.jai @@ -0,0 +1,276 @@ #import "Basic"; #import "String"; #import "Compiler"; #import "File"; #import "File_Utilities"; #import "Autorun"; // ---------------------------------------- // config // ------ // For a template example, visit `templates/minimal/rules_minimal.jai` // Which template should be used by default // (You can list all templates with `jinit -l`) SET_DEFAULT_TEMPLATE :: "arg"; // You can include this "marker" in your templates/rules // and replace them at runtime with `jinit -t <template> -r <your new string>` SET_RENAME_MARKER :: "__RENAME_PLACEHOLDER__"; // If you don't use a SCM, you can turn it off here SET_SCM_IS_TURNED_OFF :: false; // Define the initializing commands for your SCM SET_SCM_COMMANDS :: string.[ "git", "init" ]; // [ ! IMPORTANT ! ]: Some SCMs define a directory where they store all refs //                    and other internals. //                    You have to set the directory name here, otherwise it //                    will overwrite your SCM repository at every invocation! SET_SCM_DOT_DIR :: ".git"; SET_BUILD_FILE_NAME :: "build.jai"; // ---------------------------------------- #placeholder TEMPLATES_AND_RULES; #placeholder TR_EXEC_BUF_MAX; #placeholder TR_EXEC_BODY; #placeholder DEFAULT_TEMPLATE; #placeholder BUILD_FN; #placeholder SCM_IS_TURNED_OFF; #placeholder SCM_COMMANDS; #placeholder SCM_DOT_DIR; #placeholder RENAME_MARKER; TEMPLATE_DIR :: "templates"; build :: () {     args := get_build_options().compile_time_command_line;     // args     args_help               := array_find(args, "help");     args_compiler_silent    := array_find(args, "silent");     args_program_run        := array_find(args, "run");     args_build_release      := array_find(args, "release");     // -----------------------------------------     w := compiler_create_workspace();     if !w {         log("Workspace creation failed.");         return;     }     set_build_options_dc(.{ do_output = false });     if args_help {         args_help_print();         return;     }     print("The workspace w is %\n", w);     make_directory_if_it_does_not_exist("bin");     target_options := get_build_options(w);     target_options.output_executable_name = "jinit";     target_options.output_path = "bin";     if args_compiler_silent  target_options.text_output_flags = 0;     if args_build_release {         build_release(w, *target_options);     } else {         build_debug(w, *target_options);     }     compiler_begin_intercept(w);     add_build_file(tprint("%src/main.jai", #filepath), w);     template_rules := template_rules_find();     defer array_free(template_rules);     template_rules_inject(template_rules, w);     add_build_string(         tprint("RENAME_MARKER :: \"%\";", SET_RENAME_MARKER),         w     );     add_build_string(         tprint("SCM_IS_TURNED_OFF :: %;", SET_SCM_IS_TURNED_OFF),         w     );     add_build_string(         tprint("SCM_COMMANDS :: %.%;", "string", SET_SCM_COMMANDS),         w     );     #assert(SET_SCM_DOT_DIR) "[ERR]: NO SCM REF DIRECTORY IS SET!";     add_build_string(         tprint("SCM_DOT_DIR :: \"%\";", SET_SCM_DOT_DIR),         w     );     add_build_string(         tprint("BUILD_FN :: \"%\";", SET_BUILD_FILE_NAME),         w     );     add_build_string(         tprint("DEFAULT_TEMPLATE :: \"%\";", SET_DEFAULT_TEMPLATE),         w     );     compiler_response := compiler_message();     compiler_end_intercept(w);     if !compiler_response {         log("Compiler response failed.");         return;     }     if args_program_run  run_build_result(w); } build_debug :: (w: Workspace, target_options: *Build_Options) {     log("Choosing debug options...");     target_options.backend =.X64;     set_optimization(target_options, Optimization_Type.DEBUG, true);     set_build_options(target_options.*, w); } build_release :: (w: Workspace, target_options: *Build_Options) {     log("Choosing release options...");     target_options.backend = .LLVM;     set_optimization(target_options, Optimization_Type.VERY_OPTIMIZED);     set_build_options(target_options.*, w); } args_help_print :: () {     help_message := #string _END_ Usage:  jai build.jai - [ARGS] Options:     help        Prints this help menu     silent      Disables compiler/linker statistics     run         Runs your program afterwards     release     Builds with release options. If omitted, it builds a debug build. _END_;     log(help_message); } compiler_message :: () -> success: bool {     while true {         message := compiler_wait_for_message();         if !message break;         if message.kind == {         case .COMPLETE;             message_complete := cast(*Message_Complete) message;             return message_complete.error_code == 0;         }     }     return false; } template_rules_find :: () -> [..]string {     templates: [..]string;     collect_template_files :: (using info: *File_Visit_Info, templates: *[..]string) {         if starts_with(short_name, "rules_") && ends_with(short_name, ".jai") {             content, success := read_entire_file(full_name);             if !success {                 log("Cannot read file: %", full_name);                 exit(1);             }             array_add(templates, content);         }     }     complete := visit_files(         TEMPLATE_DIR,         recursive = true,         *templates, collect_template_files,         visit_files = true     );     assert(complete, "Some error occured while traversing the file tree");     return templates; } template_rules_inject :: (templates: [..]string, w: Workspace) {     count := templates.count;     inject_proc_body :: () #expand {         add_build_string(tprint("TR_EXEC_BUF_MAX :: %;", count), `w);         sb: String_Builder;         init_string_builder(*sb);         template_arr :: "buf[%1] = #insert,scope(FILE_SCOPE) TEMPLATES_AND_RULES[%2]; ";         template_creator :: "buf[%1].template = create_template(buf[%2].template); ";         for i: 0..count-1 {             append(*sb, tprint(template_arr, i, i));             append(*sb, tprint(template_creator, i, i));         }         tr_exec_body := builder_to_string(*sb);         s := trim(tr_exec_body,, temp);         add_build_string(tprint("TR_EXEC_BODY :: \"%\";", s), `w);     }     inject_rules_array :: () #expand {         sb: String_Builder;         init_string_builder(*sb);         append(*sb, "string.");         escaped: [..]string;         escaped.allocator = temp;         for templates {             array_add(*escaped, replace(it, "\"", "\\\""));         }         append(*sb, tprint("%", escaped));         append(*sb, ";");         s := builder_to_string(*sb);         st := trim(s,, temp);         add_build_string(tprint("TEMPLATES_AND_RULES :: %;", st), `w);     }     inject_proc_body();     inject_rules_array(); } main :: () {} #run build(); diff --git a/jinit.jai b/jinit.jai deleted file mode 100644 index 9c2ecde..0000000 --- a/jinit.jai +++ /dev/null @@ -1,297 +0,0 @@ /*     +Version: 1.0.0 */ #import "Basic"; File :: #import "File"; FileUtils :: #import "File_Utilities"; Process :: #import "Process"; String :: #import "String"; Cli :: #import "Command_Line"; // adapt it to your own taste DIRS_IN_WORKPLACE :: string.["src"]; JAI_ENTRYPOINT :: "src/main.jai"; JAI_BUILD :: "build.jai"; GIT_IGNORE :: ".gitignore"; OTHER_EMPTY_FILES :: string.[     "README.md", ]; // You can choose between multiple template via CLI args. // But you can also set a default template here. DEFAULT_CONTENT_BUILD_FILE :: CONTENT_BUILD_VARIANT_MINIMAL; CONTENT_GIT_IGNORE :: #string END # Jai .build bin *.codex END; CONTENT_MAIN_JAI :: #string END #import "Basic"; main :: () {     print("Hello, Sailor!\n"); } END; create_git_project :: (root_fp: string, args: Arguments) {     if args.g return;     if FileUtils.file_exists(".git") return;     command := string.["git", "init"];     process_result, output_str, error_str := Process.run_command(         ..command,         working_directory = root_fp,         capture_and_return_output = true     );     if process_result.exit_code != 0 {         if error_str  log("Error: %", error_str);         if output_str log("Error: %", output_str);         exit(1);     } } create_directories :: (root_fp: string) {     for DIRS_IN_WORKPLACE {         if String.starts_with(it, "/") {             log("Are you sure you want to create it at the root dir? Input: %", it);             exit(1);         }         File.make_directory_if_it_does_not_exist(tprint("%/%", root_fp, it));     } } create_file :: (fp: string, content: string = "") {     if FileUtils.file_exists(fp) {         log("[Info]: The file '%' already exists.", fp);         return;     }     if !File.write_entire_file(fp, content) {         log("Cannot create or write into file: %", fp);         exit(1);     } } create_files :: (root_fp: string, args: Arguments) {     fp_entrypoint := tprint("%/%", root_fp, JAI_ENTRYPOINT);     fp_jai_build := tprint("%/%", root_fp, JAI_BUILD);     fp_gitignore := tprint("%/%", root_fp, GIT_IGNORE);     content_build_file := DEFAULT_CONTENT_BUILD_FILE;     if args.v1 {         content_build_file = CONTENT_BUILD_VARIANT_MINIMAL;     } else if args.v2 {         content_build_file = CONTENT_BUILD_VARIANT_WITH_ARGS;     }     create_file(fp_entrypoint, CONTENT_MAIN_JAI);     create_file(fp_jai_build, content_build_file);     if !args.g {         create_file(fp_gitignore, CONTENT_GIT_IGNORE);     }     for OTHER_EMPTY_FILES {         create_file(tprint("%/%", root_fp, it));     } } Arguments :: struct {     g: bool; @"?If provided, no Git repository and .gitignore file will be created"     v1: bool; @"?Creates a minimal build.jai script"     v2: bool; @"?a build.jai script with more features (Args support, release builds, ...)" } main :: () {     success, args, is_set := Cli.parse_arguments(Arguments);     if !success {         log("[Error]: Cannot parse arguments");         exit(1);     }     current_path := get_working_directory();     if !current_path {         log("Cannot resolve project path");         exit(1);     }     create_directories(current_path);     create_files(current_path, args);     create_git_project(current_path, args);     log("[Jai Init]: Done"); } // -------------------------------------- // Build Templates // --------------- CONTENT_BUILD_VARIANT_MINIMAL :: #string _JINIT_END_ #import "Basic"; #import "Compiler"; #import "File"; build :: () {     w := compiler_create_workspace();     if !w {         print("Workspace creation failed.\n");         return;     }     print("The workspace w is %\n", w);     make_directory_if_it_does_not_exist("bin");     target_options := get_build_options(w);     target_options.output_executable_name = "program";     target_options.output_path = "bin";     //import_path: [..] string;     //array_add(*import_path, ..target_options.import_path);     //array_add(*import_path, "modules");     //target_options.import_path = import_path;     set_build_options_dc(.{do_output = false});     set_build_options(target_options, w);     add_build_file(tprint("%src/main.jai", #filepath), w); } main :: () {} #run build(); _JINIT_END_; CONTENT_BUILD_VARIANT_WITH_ARGS :: #string _JINIT_END_ #import "Basic"; #import "Compiler"; #import "File"; #import "Autorun"; build :: () {     args := get_build_options().compile_time_command_line;     // args     args_help               := array_find(args, "help");     args_compiler_silent    := array_find(args, "silent");     args_program_run        := array_find(args, "run");     args_build_release      := array_find(args, "release");     // -----------------------------------------     w := compiler_create_workspace();     if !w {         log("Workspace creation failed.");         return;     }     set_build_options_dc(.{ do_output = false });     if args_help {         args_help_print();         return;     }     print("The workspace w is %\n", w);     make_directory_if_it_does_not_exist("bin");     target_options := get_build_options(w);     target_options.output_executable_name = "program";     target_options.output_path = "bin";     if args_compiler_silent  target_options.text_output_flags = 0;     if args_build_release {         build_release(w, *target_options);     } else {         build_debug(w, *target_options);     }     compiler_begin_intercept(w);     add_build_file(tprint("%src/main.jai", #filepath), w);     jinit_compiler_response := jinit_compiler_intercept();     compiler_end_intercept(w);     if !jinit_compiler_response {         log("Compiler response failed.");         return;     }     if args_program_run  run_build_result(w); } // adapted from: https://github.com/Ivo-Balbaert/The_Way_to_Jai/blob/main/book/30B_Manipulating_the_build_process.md build_debug :: (w: Workspace, target_options: *Build_Options) {     log("Choosing debug options...");     target_options.backend =.X64;     set_optimization(target_options, Optimization_Type.DEBUG, true);     set_build_options(target_options.*, w); } build_release :: (w: Workspace, target_options: *Build_Options) {     log("Choosing release options...");     target_options.backend = .LLVM;     set_optimization(target_options, Optimization_Type.VERY_OPTIMIZED);     set_build_options(target_options.*, w); } args_help_print :: () {     help_message := #string _END_ Usage:  jai build.jai - [ARGS] Options:     help        Prints this help menu     silent      Disables compiler/linker statistics     run         Runs your program afterwards     release     Builds with release options. If omitted, it builds a debug build. _END_;     log(help_message); } jinit_compiler_intercept :: () -> success: bool {     while true {         message := compiler_wait_for_message();         if !message break;         if message.kind == {         case .COMPLETE;             message_complete := cast(*Message_Complete) message;             return message_complete.error_code == 0;         }     }     return false; } main :: () {} #run build(); _JINIT_END_; diff --git a/src/main.jai b/src/main.jai new file mode 100644 index 0000000..0294baa --- /dev/null +++ b/src/main.jai @@ -0,0 +1,278 @@ /*  * MIT License  *  * Copyright (c) 2025 dev@ptrace.dev  *  * Permission is hereby granted, free of charge, to any person obtaining a copy  * of this software and associated documentation files (the "Software"), to deal  * in the Software without restriction, including without limitation the rights  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell  * copies of the Software, and to permit persons to whom the Software is  * furnished to do so, subject to the following conditions:  *  * The above copyright notice and this permission notice shall be included in all  * copies or substantial portions of the Software.  *  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE  * SOFTWARE.  *  */ /*     +Version: 2.0.0 */ #import "Basic"; #import "String"; File :: #import "File"; FileUtils :: #import "File_Utilities"; Process :: #import "Process"; Cli :: #import "Command_Line"; // if enabled, it does not create any directories, files or SCM repositories DRY_RUN :: false; // if enabled, the dry run won't emit any logs DONT_BE_CHATTY :: false; Rules :: struct {     arg_name: string;     description: string;     template: string;     dirs_in_workplace: []string;     files: []File_Entity; } File_Entity :: struct {     file_name: string;     content: string; } Arguments :: struct {     g: bool; @"?If provided, no SCM repository will be created"     l: bool; @"?List available template types"     t: string; @"?Pass a template type"     r: string; @"?Replace the replace marker with a given string" } log_dry :: (msg: string) {     #if DONT_BE_CHATTY return;     log("[Dry Run]: %", msg); } create_scm_project :: (root_fp: string, args: Arguments) {     if SCM_IS_TURNED_OFF || args.g return;     #if DRY_RUN {         log_dry("Creating SCM Project");         return;     }     if FileUtils.file_exists(SCM_DOT_DIR) return;     command := SCM_COMMANDS;     process_result, output_str, error_str := Process.run_command(         ..command,         working_directory = root_fp,         capture_and_return_output = true     );     if process_result.exit_code != 0 {         if error_str  log("Error: %", error_str);         if output_str log("Error: %", output_str);         exit(1);     } } create_directories :: (root_fp: string, rules: Rules) {     if !rules.dirs_in_workplace  return;     for rules.dirs_in_workplace {         #if DRY_RUN {             log_dry(tprint("mkdir: %/%", root_fp, it));             return;         }         File.make_directory_if_it_does_not_exist(tprint("%/%", root_fp, it));     } } create_file :: (fp: string, content: string = "") {     #if DRY_RUN {         log_dry(tprint("file_write: % -> content: %", fp, content));         return;     }     if FileUtils.file_exists(fp) {         log("Info: The file '%' already exists.", fp);         return;     }     if !File.write_entire_file(fp, content) {         log("Cannot create or write into file: %", fp);         exit(1);     } } create_files :: (root_fp: string, rules: Rules) {     for rules.files {         fp := tprint("%/%", root_fp, it.file_name);         create_file(fp, it.content);     }     build_template_fp := tprint("%/%", root_fp, BUILD_FN);     create_file(build_template_fp, rules.template); } create_template :: (template_fp: string) -> string {     if !FileUtils.file_exists(template_fp) {         log_error("Could not find template: %", template_fp);     }     content, success := File.read_entire_file(template_fp);     if !success {         log_error("Cannot read file: %", template_fp);         exit(1);     }     return content; } template_rules_exec :: () -> []Rules {     buf: [TR_EXEC_BUF_MAX]Rules;     #insert TR_EXEC_BODY;     return buf; } rules_execute :: (template_rules: []Rules, root_fp: string, args: Arguments) {     create_project :: (template_name: string) #expand {         success := false;         match: Rules;         for `template_rules {             if it.arg_name == template_name {                 success = true;                 match = it;             }         }         if !success {             log("Cannot find template: %", template_name);             exit(1);         }         if `args.r {             template_placeholder_rename(*match, `args.r);         }         create_directories(root_fp, match);         create_files(root_fp, match);     }     if args.t {         create_project(args.t);         return;     }     log("Default Template: %", DEFAULT_TEMPLATE);     create_project(DEFAULT_TEMPLATE); } template_placeholder_rename :: (rules: *Rules, new_name: string) {     replace_new :: (arr: []string) -> []string #expand {         buf: [..]string;         buf.allocator = temp;         for * arr {             if it.* == RENAME_MARKER {                 array_add(*buf, `new_name);             } else {                 array_add(*buf, it.*);             }         }         return buf;     }     replace_new :: (s: string, new: *string) #expand {         if contains(s, RENAME_MARKER) {             `had_match = true;             new.* = replace(s, RENAME_MARKER, `new_name);         }     }     rules.template = replace(rules.template, RENAME_MARKER, new_name,, temp);     success := array_find(rules.dirs_in_workplace, RENAME_MARKER);     if success {         rules.dirs_in_workplace = replace_new(rules.dirs_in_workplace);     }     had_match := false;     buf: [..]File_Entity;     buf.allocator = temp;     for * rules.files {         file_name := it.file_name;         content := it.content;         replace_new(it.file_name, *file_name);         replace_new(it.content, *content);         array_add(*buf, .{ file_name, content });     }     if had_match {         rules.files = buf;     } } list_all_templates :: (template_rules: []Rules) {     log("Available Templates:");     for template_rules {         print("  %\t%\n", it.arg_name, it.description);     }     print("\n"); } FILE_SCOPE :: #code {}; main :: () {     success, args, is_set := Cli.parse_arguments(Arguments);     if !success {         log("[Error]: Cannot parse arguments");         exit(1);     }     template_rules := #run template_rules_exec();     if args.l {         list_all_templates(template_rules);         return;     }     current_path := get_working_directory();     if !current_path {         log("Cannot resolve project path");         exit(1);     }     rules_execute(template_rules, current_path, args);     create_scm_project(current_path, args);     log("[Jai Init]: Done"); } diff --git a/templates/empty/rules_empty.jai b/templates/empty/rules_empty.jai new file mode 100644 index 0000000..271f027 --- /dev/null +++ b/templates/empty/rules_empty.jai @@ -0,0 +1,9 @@ () -> Rules {     return .{         arg_name = "emp",         description = "Empty template",         template = "templates/empty/template.jai",         dirs_in_workplace = .[],         files = .[],     }; }(); diff --git a/templates/empty/template.jai b/templates/empty/template.jai new file mode 100644 index 0000000..afdd2ec --- /dev/null +++ b/templates/empty/template.jai @@ -0,0 +1,34 @@ #import "Basic"; #import "Compiler"; #import "File"; build :: () {     w := compiler_create_workspace();     if !w {         print("Workspace creation failed.\n");         return;     }     print("The workspace w is %\n", w);     make_directory_if_it_does_not_exist("bin");     target_options := get_build_options(w);     target_options.output_executable_name = "program";     target_options.output_path = "bin";     //import_path: [..] string;     //array_add(*import_path, ..target_options.import_path);     //array_add(*import_path, "custom_module_path");     //target_options.import_path = import_path;     set_build_options_dc(.{do_output = false});     set_build_options(target_options, w);     add_build_file(tprint("%src/main.jai", #filepath), w); } main :: () {} #run build(); diff --git a/templates/lib/rules_lib.jai b/templates/lib/rules_lib.jai new file mode 100644 index 0000000..32b3e8d --- /dev/null +++ b/templates/lib/rules_lib.jai @@ -0,0 +1,76 @@ /* If you're looking the first time into those files, I recommend to view the documentation at `templates/minimal/rules_minimal.jai` first. This template is a special one, because it contains a placeholder: `__RENAME_PLACEHOLDER__` Which you can replace via `jinit -t lib -r foo_bar`. You can place the placeholder inside those places: - Your `template.jai` - rules.dirs_in_workplace - rules.files You cannot place it in `rules.template`, because this pathstring gets  replaced with the template at compile time. */ () -> Rules {     gitignore :: #string STR_END # Jai .build /bin     STR_END;     main_jai :: #string STR_END #import "Basic"()(     MEMORY_DEBUGGER = MEMORY_DEBUGGER ); #import "__RENAME_PLACEHOLDER__"; MEMORY_DEBUGGER :: false; main :: () {     #if MEMORY_DEBUGGER defer report_memory_leaks();     log("Hello from test.jai");     f(); }     STR_END;     lib_jai :: #string STR_END #import "Basic"; f :: () {     log("Hello from lib"); }     STR_END;     module_jai :: #string STR_END #load "lib.jai";     STR_END;     return .{         arg_name = "lib",         description = "Lib starter pack. Pass `-r <lib name>` to Jinit, to give it a name",         template = "templates/lib/template.jai",         dirs_in_workplace = .[ "test", "__RENAME_PLACEHOLDER__" ],         files = .[             .{ ".gitignore", gitignore },             .{ "test/test.jai", main_jai },             .{ "__RENAME_PLACEHOLDER__/lib.jai", lib_jai },             .{ "__RENAME_PLACEHOLDER__/module.jai", module_jai },             .{ "README.md", "" },         ],     }; }(); diff --git a/templates/lib/template.jai b/templates/lib/template.jai new file mode 100644 index 0000000..8b55bc2 --- /dev/null +++ b/templates/lib/template.jai @@ -0,0 +1,114 @@ #import "Basic"; #import "Compiler"; #import "File"; #import "Autorun"; build :: () {     args := get_build_options().compile_time_command_line;     // args     args_help               := array_find(args, "help");     args_compiler_silent    := array_find(args, "silent");     args_program_run        := array_find(args, "run");     args_build_release      := array_find(args, "release");     // -----------------------------------------     w := compiler_create_workspace();     if !w {         log("Workspace creation failed.");         return;     }     set_build_options_dc(.{ do_output = false });     if args_help {         args_help_print();         return;     }     print("The workspace w is %\n", w);     make_directory_if_it_does_not_exist("bin");     target_options := get_build_options(w);     target_options.output_executable_name = "program";     target_options.output_path = "bin";     import_path: [..] string;     array_add(*import_path, ..target_options.import_path);     array_add(*import_path, ".");     target_options.import_path = import_path;     if args_compiler_silent  target_options.text_output_flags = 0;     if args_build_release {         build_release(w, *target_options);     } else {         build_debug(w, *target_options);     }     compiler_begin_intercept(w);     add_build_file(tprint("%test/test.jai", #filepath), w);     compiler_response := message_loop();     compiler_end_intercept(w);     if !compiler_response {         log("Compiler response failed.");         return;     }     if args_program_run  run_build_result(w); } build_debug :: (w: Workspace, target_options: *Build_Options) {     log("Choosing debug options...");     target_options.backend =.X64;     set_optimization(target_options, Optimization_Type.DEBUG, true);     set_build_options(target_options.*, w); } build_release :: (w: Workspace, target_options: *Build_Options) {     log("Choosing release options...");     target_options.backend = .LLVM;     set_optimization(target_options, Optimization_Type.VERY_OPTIMIZED);     set_build_options(target_options.*, w); } args_help_print :: () {     help_message := #string _END_ Usage:  jai build.jai - [ARGS] Options:     help        Prints this help menu     silent      Disables compiler/linker statistics     run         Runs your program afterwards     release     Builds with release options. If omitted, it builds a debug build. _END_;     log(help_message); } message_loop :: () -> success: bool {     while true {         message := compiler_wait_for_message();         if !message break;         if message.kind == {         case .COMPLETE;             message_complete := cast(*Message_Complete) message;             return message_complete.error_code == 0;         }     }     return false; } main :: () {} #run build(); diff --git a/templates/minimal/rules_minimal.jai b/templates/minimal/rules_minimal.jai new file mode 100644 index 0000000..04825b7 --- /dev/null +++ b/templates/minimal/rules_minimal.jai @@ -0,0 +1,64 @@ /* This file acts as configuration for your templates. Sometimes it might be useful to create all necessary files upfront, instead of bloating the build script with one-time code. Here you can declare what files and directories should be created  at project initialization. This file gets consumed at compile time, hence the anonymous procedure here. It also allows you to shift some verbosity to distinct constants or variables. You could even do more sophisticated things, as long it returns the  `Rules` struct, defined in `src/main.jai`. */ () -> Rules {     // You can even delcare the exact file contents.     // For example the `.gitignore` file:     gitignore :: #string STR_END # Jai .build /bin     STR_END;     main_jai :: #string STR_END #import "Basic"; main :: () {     log("Hello, Sailor!"); }     STR_END;     return .{         // This is how you would call the template via arguments.         // In this case: `jinit -t min`         arg_name = "min",         // Some description, could be also a empty string.         description = "Minimal template",         // The relative or absolute filepath to your template.         // This allows you to keep your templates where you want.         template = "templates/minimal/template.jai",         // Which directories should be created. It won't create parents         // automatically. They must be created first like this:         // `.[ "test", "test/foo", "test/foo/bar" ]`         dirs_in_workplace = .[ "src" ],         // Which files should be created. And you can declare the content, too.         files = .[             .{ ".gitignore", gitignore },             .{ "src/main.jai", main_jai },             .{ "README.md", "" },         ],     }; }(); diff --git a/templates/minimal/template.jai b/templates/minimal/template.jai new file mode 100644 index 0000000..afdd2ec --- /dev/null +++ b/templates/minimal/template.jai @@ -0,0 +1,34 @@ #import "Basic"; #import "Compiler"; #import "File"; build :: () {     w := compiler_create_workspace();     if !w {         print("Workspace creation failed.\n");         return;     }     print("The workspace w is %\n", w);     make_directory_if_it_does_not_exist("bin");     target_options := get_build_options(w);     target_options.output_executable_name = "program";     target_options.output_path = "bin";     //import_path: [..] string;     //array_add(*import_path, ..target_options.import_path);     //array_add(*import_path, "custom_module_path");     //target_options.import_path = import_path;     set_build_options_dc(.{do_output = false});     set_build_options(target_options, w);     add_build_file(tprint("%src/main.jai", #filepath), w); } main :: () {} #run build(); diff --git a/templates/with_args/rules_with_args.jai b/templates/with_args/rules_with_args.jai new file mode 100644 index 0000000..42a52a4 --- /dev/null +++ b/templates/with_args/rules_with_args.jai @@ -0,0 +1,29 @@ () -> Rules {     gitignore :: #string STR_END # Jai .build /bin     STR_END;     main_jai :: #string STR_END #import "Basic"; main :: () {     log("Hello, Sailor!"); }     STR_END;     return .{         arg_name = "arg",         description = "Supports build arguments",         template = "templates/with_args/template.jai",         dirs_in_workplace = .[ "src", ],         files = .[             .{ ".gitignore", gitignore },             .{ "src/main.jai", main_jai },             .{ "README.md", "" },         ],     }; }(); diff --git a/templates/with_args/template.jai b/templates/with_args/template.jai new file mode 100644 index 0000000..962c8b3 --- /dev/null +++ b/templates/with_args/template.jai @@ -0,0 +1,109 @@ #import "Basic"; #import "Compiler"; #import "File"; #import "Autorun"; build :: () {     args := get_build_options().compile_time_command_line;     // args     args_help               := array_find(args, "help");     args_compiler_silent    := array_find(args, "silent");     args_program_run        := array_find(args, "run");     args_build_release      := array_find(args, "release");     // -----------------------------------------     w := compiler_create_workspace();     if !w {         log("Workspace creation failed.");         return;     }     set_build_options_dc(.{ do_output = false });     if args_help {         args_help_print();         return;     }     print("The workspace w is %\n", w);     make_directory_if_it_does_not_exist("bin");     target_options := get_build_options(w);     target_options.output_executable_name = "program";     target_options.output_path = "bin";     if args_compiler_silent  target_options.text_output_flags = 0;     if args_build_release {         build_release(w, *target_options);     } else {         build_debug(w, *target_options);     }     compiler_begin_intercept(w);     add_build_file(tprint("%src/main.jai", #filepath), w);     compiler_response := message_loop();     compiler_end_intercept(w);     if !compiler_response {         log("Compiler response failed.");         return;     }     if args_program_run  run_build_result(w); } build_debug :: (w: Workspace, target_options: *Build_Options) {     log("Choosing debug options...");     target_options.backend =.X64;     set_optimization(target_options, Optimization_Type.DEBUG, true);     set_build_options(target_options.*, w); } build_release :: (w: Workspace, target_options: *Build_Options) {     log("Choosing release options...");     target_options.backend = .LLVM;     set_optimization(target_options, Optimization_Type.VERY_OPTIMIZED);     set_build_options(target_options.*, w); } args_help_print :: () {     help_message := #string _END_ Usage:  jai build.jai - [ARGS] Options:     help        Prints this help menu     silent      Disables compiler/linker statistics     run         Runs your program afterwards     release     Builds with release options. If omitted, it builds a debug build. _END_;     log(help_message); } message_loop :: () -> success: bool {     while true {         message := compiler_wait_for_message();         if !message break;         if message.kind == {         case .COMPLETE;             message_complete := cast(*Message_Complete) message;             return message_complete.error_code == 0;         }     }     return false; } main :: () {} #run build();