Contributing
Setup, targeted tests, documentation, and pull requests.
Contributions are easiest to review when they stay focused on one subsystem, include targeted verification, and update the matching documentation and examples.
1 Recommended workflow
git clone [email protected]:zcaudate-xyz/foundation-base.git\ncd foundation-base\nlein deps\nlein test :only <namespace-test>
Keep the change focused
Avoid mixing unrelated refactors, dependency upgrades, generated output, and feature work.
Run targeted verification
State the exact namespace, runtime, or documentation command used.
Keep examples linked
Include authored source, build definitions, tests, generated output, and reproduction commands.
Update both entry points
When changing project positioning or navigation, update README.md and src-doc/documentation/main_index.clj together.
2 Documentation
lein publish\nlein serve
3 Plan for Generating cljfmt and clj-kondo Rules for Custom Top-Level Forms
Plan for Generating cljfmt and clj-kondo Rules for Custom Top-Level Formsnn## ObjectivenTo systematically identify custom top-level forms within the /src directory of the foundation-base project and propose methods for generating appropriate cljfmt and clj-kondo rules.nn## Phase 1: Identification of Custom Top-Level FormsnnThe goal of this phase is to identify all unique custom top-level forms. This will involve more than just defmacro as other constructs might also define custom forms (e.g., def, defn with specific metadata, deftype, defrecord, defprotocol, defmulti, defmethod when used in a DSL-like manner).nn### Steps:n1. List all .clj files in /src: Obtain a comprehensive list of all Clojure source files.n2. Parse each file for top-level forms: For each .clj file, read its content and identify all top-level forms. This will require a more sophisticated approach than simple regex, possibly involving a basic Clojure parser or by looking for common patterns of top-level definitions.n3. Filter for custom forms:n Exclude standard Clojure forms (e.g., ns, require, def, defn, let, if, do, comment).n Identify forms that are likely custom macros or DSL constructs. This will involve looking for:n defmacro and its variants (defmacro.js, defmacro.lua, etc.).n def forms that define functions or macros (e.g., (def my-macro (macro/macro-fn ...))).n Forms with specific metadata that indicates custom behavior (e.g., ^{:static/lang :bash}).n deftype, defrecord, defprotocol, defmulti, defmethod if they are part of a custom DSL.n4. Categorize custom forms: Group identified custom forms by their type and behavior (e.g., function-like, variable-like, block-like, DSL-specific). This categorization will inform the type of cljfmt and clj-kondo rules needed.n5. Collect metadata: Extract any relevant metadata associated with these forms, especially ^:style/indent for cljfmt and any lint-as hints for clj-kondo.nn## Phase 2: Generation of clj-kondo RulesnnBased on the identified and categorized custom forms, generate clj-kondo lint-as rules.nn### Steps:n1. Default lint-as mappings: For each category of custom forms, propose a default lint-as mapping to a standard Clojure form (e.g., clojure.core/def, clojure.core/defn, clojure.core/do).n2. Automate lint-as generation: Develop a script or a systematic process to generate the lint-as entries for the .clj-kondo/config.edn file. This script would:n Read the identified custom forms and their categories.n Generate the corresponding lint-as entries.n Merge these new entries with the existing .clj-kondo/config.edn without overwriting existing custom rules.n3. Identify potential custom hooks: For complex custom forms that cannot be adequately handled by lint-as, identify them as candidates for custom clj-kondo hooks. This would require manual inspection and development of Clojure code for the hooks.nn## Phase 3: Generation of cljfmt RulesnnAddress cljfmt rules, focusing on indentation and formatting.nn### Steps:n1. Review existing ^:style/indent: For custom forms that already have ^:style/indent metadata, verify if the indentation is correct.n2. Identify missing ^:style/indent: For custom forms lacking ^:style/indent metadata, propose adding appropriate metadata based on their structure (e.g., {:style/indent 1} for forms with a body).n3. Generate .cljfmt.edn entries (if necessary): If ^:style/indent is insufficient or if specific formatting rules are required (e.g., for complex DSLs), generate entries for a .cljfmt.edn file. This would involve:n Identifying the custom forms that need explicit cljfmt rules.n Defining the appropriate indentation rules (e.g., [](#:block 1), [](#:inner 0)).n Creating or updating the .cljfmt.edn file with these rules.nn## Phase 4: Verification and RefinementnnAfter generating the rules, verify their effectiveness and refine as needed.nn### Steps:n1. Run clj-kondo: Execute clj-kondo across the entire codebase with the updated configuration and analyze the output for new warnings or errors related to the custom forms.n2. Run cljfmt: Apply cljfmt to the codebase and visually inspect the formatting of the custom forms.n3. Iterative refinement: Adjust lint-as rules, add custom hooks, or refine cljfmt rules based on the verification results.nn## Tools to be used:n default_api.glob: To list .clj files.n default_api.read_file: To read file contents.n default_api.search_file_content: To find patterns like defmacro.n default_api.write_file: To create the plan file and update config files.n default_api.replace: To modify existing config files.n clj_eval: Potentially for parsing Clojure forms if a simple regex is insufficient.n
4 Function Design Guide for foundation-base
Function Design Guide for foundation-basennThis guide outlines best practices and common patterns for designing functions within the foundation-base project, drawing examples directly from the codebase. The goal is to promote clarity, modularity, extensibility, and maintainability.nn### 1. Clarity and ReadabilitynnFunctions should be easy to understand at a glance. This is achieved through clear naming, comprehensive documentation, and consistent formatting.nn#### 1.1. Docstrings and MetadatannEvery public function must have a comprehensive docstring that explains its purpose, arguments, and return value. The {:added "version"} metadata is mandatory.nn Purpose: Clearly state what the function does.n Arguments: Describe each argument, its type, and its role.n Return Value: Explain what the function returns.n Examples: Provide illustrative examples of how to use the function.n Metadata: Use {:added "version"} to track when the function was introduced. Other custom metadata can provide additional context.nnExample: std.lib.collection/map-keysnn```clojuren(defn map-keysn "changes the keys of a mapn n (map-keys inc {0 :a 1 :b 2 :c})n => {1 :a, 2 :b, 3 :c}"n {:added "3.0"}n ([f m]n (reduce (fn [out [k v]]n (assoc out (f k) v))n {} n m)))n```nnExample: std.lib.network/port:check-availablenn```clojuren(defn port:check-availablen "check that port is availablen n (port:check-available 51311)n => anything"n {:added "4.0"}n ([port]n (tryn (with-open [ServerSocket s (ServerSocket. port)]n (.setReuseAddress s true)n (.getLocalPort s))n (catch Throwable tn false))))n```nn#### 1.2. Naming Conventionsnn Public Functions: Use spinal-case (e.g., create-directory, process-transform).n Namespaced Functions: Use a colon (:) to create "sub-namespaces" for related functions (e.g., atom:get, socket:port).n Predicate Functions: End with a question mark ? (e.g., component?, hash-map?).n Internal/Helper Functions: Often start with a hyphen (-) or are not explicitly exposed (e.g., -write-value, tf-macroexpand).nn#### 1.3. Arity and Parameter OrdernnFunctions should handle multiple arities gracefully, typically with the simpler arities calling the more complex ones. Positional arguments are preferred for mandatory inputs, while optional parameters are often passed in a map.nnExample: std.fs.api/create-directorynn```clojuren(defn create-directoryn "creates a directory on the filesystem"n {:added "3.0"}n ([path]n (create-directory path {}))n ([path attrs]n (Files/createDirectories (path/path path)n (attr/map->attr-array attrs))))n```nn### 2. Modularity and Single ResponsibilitynnFunctions should ideally do one thing and do it well. Complex tasks are broken down into smaller, composable functions.nnExample: std.timeseries.compute/process-transformnnThis function focuses solely on transforming an array based on a given interval and template, delegating sub-tasks like parse-transform-expr and transform-interval to other functions.nn```clojuren(defn process-transformn "processes the transform stage"n {:added "3.0"}n ([arr {:keys [transform template merge-fn]} type time-opts]n (let [len (count arr)n empty (-> template :raw :empty)n interval (range/range-op :to interval arr time-opts)n [tag val] intervaln arr (case tagn :time (let [{:keys [key order]} time-optsn {:keys [op-fn comp-fn]} (common/order-fns order)n start (key (first arr))n steps (quot (math/abs (- start (key (last arr))))n val)n sorted (group-by (fn [m]n (quot (math/abs (- start (key m)))n val))n arr)]n (mapv (fn [i] n (or (get sorted i)n [(case typen :map (assoc empty key (op-fn start ( i val)))n :time (op-fn start ( i val)))]))n (range steps)))n :ratio (let [num (math/ceil ( len val))]n (partition num arr))n :array (partition val arr))]n (mapv merge-fn arr))))n```nn### 3. Parameter Designnn#### 3.1. Positional vs. Map Argumentsnn Positional Arguments: Used for mandatory and frequently used parameters.n Map Arguments: Preferred for optional parameters, configurations, or when there are many parameters. This improves readability and allows for easy extension.nnExample: std.lib.future/submitnn```clojuren(defn submitn "submits a task to an executor"n {:added "3.0"}n ([ExecutorService service ^Callable f]n (submit service f nil))n ([ExecutorService service f {:keys [min max delay default] :as m}]n (let [Callable f (cond-> fn min (wrap-min-time min (or delay 0)))n opts (cond-> {:pool service}n max (assoc :timeout max)n default (assoc :default default)n delay (assoc :delay delay))]n (f/future:run f opts))))n```nnn### 4. Error HandlingnnRobust error handling is crucial. The h/error function is the standard way to throw exceptions with structured data, making debugging easier.nnExample: std.lib.foundation/errornn```clojuren(defmacro errorn "throws an error with messagen n (error "Error")n => (throws)"n {:added "3.0"}n ([message]n (throw (ex-info ~message {})))n ([message data]n (throw (ex-info ~message ~data))))n```nnExample: hara.lang.base.book/assert-modulenn```clojuren(defn assert-modulen "asserts that module exists"n {:added "4.0"}n [book module-id]n (or (has-module? book module-id)n (h/error "No module found." {:available (set (list-entries book :module)) n :module module-id})))n```nn### 5. Immutability vs. MutabilitynnClojure favors immutability, but mutable state is managed explicitly when necessary.nn Persistent Data Structures: Clojure's default data structures are immutable.n Atoms and Volatiles: atom and volatile! are used for managing mutable state with clear semantics.n* defmutable: For defining mutable data structures when performance or specific behavior requires it.nnExample: std.lib.mutable/defmutablenn```clojuren(defmacro defmutablen "allows definition of a mutable datastructure"n {:added "3.0"}n ([tp-name fields & protos] n {:pre [(symbol? tp-name)n (every? symbol? fields)]} (let [fields (mapv (fn [sym] n (with-meta sym n (assoc (meta sym) :volatile-mutable true)))n fields)]n n (deftype ~tp-name ~fields n IMutablen (-set [~'this ~'k ~'v] n (case ~'k n ~@(mapcat n (fn [x] n [~(keyword (name x)) n (~'set! ~x ~'v)]) n fields)) n ~'this)n n (-set-new [~'this ~'k ~'v] n (assert (not (~'k ~'this)) (str ~'k " is already set.")) n (case ~'k n ~@(mapcat n (fn [x] n [~(keyword (name x)) n (~'set! ~x ~'v)]) n fields)) n ~'this)nn (-fields [~'this] n ~(mapv (comp keyword name) fields))nn (-clone [~'this] n ~(let [cstr (symbol (str tp-name "."))] n (~cstr ~@fields)))n n clojure.lang.ILookup n (~'valAt [~'this ~'k ~'default] n (case ~'k n ~@(mapcat n (fn [x] n [~(keyword (name x)) n ~x]) n fields) n ~'default))n (~'valAt [~'this ~'k] n (.valAt ~'this ~'k nil))n ~@protos))))n```nn### 6. Extensibility (Multimethods and Protocols)nnFunctions are often designed to be extensible, allowing new behaviors to be added without modifying existing code.nn* **defmulti and defmethod:** Used for dispatching behavior based on the type or value of arguments.n* **Protocols (defprotocol, extend-type, defimpl):** Define interfaces that can be implemented by different types.nn**Example: net.http.common/-write-value (Multimethod)**nn```clojuren(defmulti -write-valuen "writes the string value of the datastructure according to format"n {:added "0.5"}n (fn [s format] format))nn(defmethod -write-value :ednn ([s _]n (pr-str s)))n```nn**Example: std.lib.component/IComponent (Protocol with defimpl)**nn```clojuren(defimpl LuceneSearch [type instance]n :string common/to-stringn :protocols [std.protocol.component/IComponentn :body {-start impl/start-lucenen -stop impl/stop-lucene}])n```nn### 7. hara.lang DSL IntegrationnnFunctions designed for the hara.lang DSL have specific patterns for cross-platform compatibility and code generation.nn* **defn. and defmacro.:** Functions and macros are defined with a language-specific tag (e.g., defn.js, defmacro.lua).n* **Cross-Platform Utilities (xt.lang.base-lib):** The k/ alias is commonly used for xt.lang.base-lib functions, which provide platform-agnostic operations.n* **Code Generation:** Functions often take grammar and mopts (macro options) as arguments to facilitate code generation.nn**Example: js.blessed.layout/LayoutMain (React Component in JS DSL)**nn```clojuren(defn.js LayoutMainn "constructs the main page"n {:added "4.0"}n ([#{[(:= header {:menu []n :toggle niln :user nil})n (:= footer {:menu []n :toggle nil})n initn routen setRouten indexn setIndexn sectionsn statusn setStatusn busyn setBusyn notifyn setNotifyn menuWidthn menuContentn menuFootern menuHiden consolen consoleHeight]}] ; ... rest of functionn```nn**Example: kmi.queue.common/mq-do-key (Lua DSL Function)**nn```clojuren(defn.lua mq-do-keyn "helper function for multi key ops"n {:added "3.0"}n ([key f acc]n (local k-space (cat key ":_"))n (local k-pattern (cat k-space ":[^\\:]+$"))n (local k-partitions (r/scan-regex k-pattern (cat k-space ":*")))n (k/for:array [[i pfull] k-partitions]n (local p (. pfull (sub (+ (len key) 4))))n (f key p acc))n (return acc)))n```nn### 8. Higher-Order Functions and Function CompositionnnFunctions are often designed to be composed, taking other functions as arguments or returning functions.nn* **comp, partial:** Used for creating new functions from existing ones.n* **Threading Macros (->, ->>):** Used for chaining function calls, improving readability of sequential operations.nn**Example: std.lib.transform.apply/wrap-hash-set**nn```clojuren(defn wrap-hash-setn "allows operations to be performed on sets"n {:added "3.0"}n ([f]n (fn [val datasource]n (cond (set? val)n (set (map #(f % datasource) val))nn :elsen (f val datasource)))))n```nn### 9. Macros for Abstraction and Code GenerationnnMacros are used to reduce boilerplate, create DSLs, and generate code at compile time.nn* **defmacro:** Standard Clojure macro definition.n* **h/template-entries:** A powerful macro for generating multiple definitions from a template, often used for binding external library functions or constants.nn**Example: std.lib.template/deftemplate and h/template-entries**nn```clojuren(deftemplate res-api-tmpln ([](#sym res-sym config)n (let [extra (count (:args config))n args (map :name (:args config))n default (:default (first (:args config)))])n ;; ... generates def formn ))nn(h/template-entries [res-api-tmpl] n [[res:exists? res-access-get {:post boolean}]n [res:set res-access-set {:args [{:name instance}]}]])n```nnThis guide provides a deeper insight into the function design principles and patterns prevalent in the foundation-base` codebase. By understanding and applying these principles, developers can contribute functions that are consistent, robust, and easily integrated into the existing architecture.
and defmacro.:** Functions and macros are defined with a language-specific tag (e.g., defn.js, defmacro.lua).n* **Cross-Platform Utilities (xt.lang.base-lib):** The k/ alias is commonly used for xt.lang.base-lib functions, which provide platform-agnostic operations.n* **Code Generation:** Functions often take grammar and mopts (macro options) as arguments to facilitate code generation.nn**Example: js.blessed.layout/LayoutMain (React Component in JS DSL)**nn```clojuren(defn.js LayoutMainn "constructs the main page"n {:added "4.0"}n ([#{[(:= header {:menu []n :toggle niln :user nil})n (:= footer {:menu []n :toggle nil})n initn routen setRouten indexn setIndexn sectionsn statusn setStatusn busyn setBusyn notifyn setNotifyn menuWidthn menuContentn menuFootern menuHiden consolen consoleHeight]}] ; ... rest of functionn```nn**Example: kmi.queue.common/mq-do-key (Lua DSL Function)**nn```clojuren(defn.lua mq-do-keyn "helper function for multi key ops"n {:added "3.0"}n ([key f acc]n (local k-space (cat key ":_"))n (local k-pattern (cat k-space ":[^\\:]+$"))n (local k-partitions (r/scan-regex k-pattern (cat k-space ":*")))n (k/for:array [[i pfull] k-partitions]n (local p (. pfull (sub (+ (len key) 4))))n (f key p acc))n (return acc)))n```nn### 8. Higher-Order Functions and Function CompositionnnFunctions are often designed to be composed, taking other functions as arguments or returning functions.nn* **comp, partial:** Used for creating new functions from existing ones.n* **Threading Macros (->, ->>):** Used for chaining function calls, improving readability of sequential operations.nn**Example: std.lib.transform.apply/wrap-hash-set**nn```clojuren(defn wrap-hash-setn "allows operations to be performed on sets"n {:added "3.0"}n ([f]n (fn [val datasource]n (cond (set? val)n (set (map #(f % datasource) val))nn :elsen (f val datasource)))))n```nn### 9. Macros for Abstraction and Code GenerationnnMacros are used to reduce boilerplate, create DSLs, and generate code at compile time.nn* **defmacro:** Standard Clojure macro definition.n* **h/template-entries:** A powerful macro for generating multiple definitions from a template, often used for binding external library functions or constants.nn**Example: std.lib.template/deftemplate and h/template-entries**nn```clojuren(deftemplate res-api-tmpln ([](#sym res-sym config)n (let [extra (count (:args config))n args (map :name (:args config))n default (:default (first (:args config)))])n ;; ... generates def formn ))nn(h/template-entries [res-api-tmpl] n [[res:exists? res-access-get {:post boolean}]n [res:set res-access-set {:args [{:name instance}]}]])n```nnThis guide provides a deeper insight into the function design principles and patterns prevalent in the foundation-base` codebase. By understanding and applying these principles, developers can contribute functions that are consistent, robust, and easily integrated into the existing architecture.