Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions .github/workflows/build_deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Build & Deploy

on:
workflow_dispatch

jobs:
build-and-deploy:
name: Build and Deploy
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- name: Prepare java
uses: actions/setup-java@v1.4.3
with:
java-version: 21

- name: Install clojure tools
uses: DeLaGuardo/setup-clojure@master
with:
cli: latest

- name: Cache clojure dependencies
uses: actions/cache@v4.2.4
with:
path: |
~/.m2/repository
~/.gitlibs
~/.deps.clj
.cpcache
key: cljdeps-${{ hashFiles('deps.edn') }}
restore-keys: cljdeps-

- name: Run CLJ tests
run: clojure -M:clj-test

- name: Run CLJS tests
run: clojure -M:cljs-test

- name: Build JAR
run: clojure -M:build jar

- name: Deploy to Clojars
run: clojure -X:deploy
env:
CLOJARS_USERNAME: ${{ secrets.CLOJARS_USERNAME }}
CLOJARS_PASSWORD: ${{ secrets.CLOJARS_PASSWORD }}
9 changes: 6 additions & 3 deletions .github/workflows/unit_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:

jobs:
tests:
name: Clojure
name: Run Tests
runs-on: ubuntu-latest

steps:
Expand All @@ -22,6 +22,7 @@ jobs:
uses: DeLaGuardo/setup-clojure@master
with:
cli: latest

- name: Cache clojure dependencies
uses: actions/cache@v4.2.4
with:
Expand All @@ -33,8 +34,10 @@ jobs:
# List all files containing dependencies:
key: cljdeps-${{ hashFiles('deps.edn') }}
restore-keys: cljdeps-
- name: Start clojure tests

- name: Run CLJ tests
run: clojure -M:clj-test
- name: Start clojurescript tests

- name: Run CLJS tests
run: clojure -M:cljs-test

11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
# 1.0.5
# 1.0.6

ADDED `print-trace` in `commando.impl.utils` — replaces `print-deep-stats` with an improved flamegraph that also shows per-node instruction keys and optional title. Add `:__title` or `"__title"` to any instruction's top level to annotate that node in the output. `print-deep-stats` is kept as a deprecated alias.

ADDED named anchor navigation for `:commando/from` paths. Declare an anchor with `"__anchor"` or `:__anchor` key in any instruction map, then reference it with `"@name"` as a path segment. The resolver walks up the tree and resolves to the nearest ancestor with that anchor name — independent of nesting depth. Anchors can be combined with existing `"../"` relative navigation in a single path.

UPDATED `resolve-relative-path` in `commando.impl.dependency` to accept an optional leading `instruction` argument and handle `"@anchor"` segments.

UPDATED `point-target-path` in `commando.impl.dependency` to pass the instruction into `resolve-relative-path`, enabling anchor resolution.

ADDED new keys to `commando.impl.utils/*execute-config*`. Added hooks keys
- `:hook-execute-start` if not nil, call procedure at the start of `commando.core/execute` function.
- `:hook-execute-end` if not nil, call procedure at the end of `commando.core/execute` function.
Expand Down
52 changes: 36 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

[![Clojars Project](https://img.shields.io/clojars/v/org.clojars.funkcjonariusze/commando.svg)](https://clojars.org/org.clojars.funkcjonariusze/commando)
[![Run tests](https://github.com/funkcjonariusze/commando/actions/workflows/unit_test.yml/badge.svg)](https://github.com/funkcjonariusze/commando/actions/workflows/unit_test.yml)
[![cljdoc badge](https://cljdoc.org/badge/org.clojars.funkcjonariusze/commando)](https://cljdoc.org/d/org.clojars.funkcjonariusze/commando/1.0.5)
[![cljdoc badge](https://cljdoc.org/badge/org.clojars.funkcjonariusze/commando)](https://cljdoc.org/d/org.clojars.funkcjonariusze/commando/1.0.6)

**Commando** is a flexible Clojure library for managing, extracting, and transforming data inside nested map structures aimed to build your own Data DSL.

Expand Down Expand Up @@ -34,10 +34,10 @@

```clojure
;; deps.edn with git
{org.clojars.funkcjonariusze/commando {:mvn/version "1.0.5"}}
{org.clojars.funkcjonariusze/commando {:mvn/version "1.0.6"}}

;; leiningen
[org.clojars.funkcjonariusze/commando "1.0.5"]
[org.clojars.funkcjonariusze/commando "1.0.6"]
```

## Quick Start
Expand Down Expand Up @@ -128,24 +128,44 @@ The basic commands is found in namespace `commando.commands.builtin`. It describ

#### command-from-spec

Allows retrieving data from the instruction by referencing it via the `:commando/from` key. An optional function can be applied via the `:=` key.
Retrieves a value from the instruction by path. An optional `":="` key applies a function to the result.

The `:commando/from` command supports relative paths like `"../"`, `"./"` for accessing data. The example below shows how values "1", "2", and "3" can be incremented and decremented in separate map namespaces:
**Absolute path** — list of keys from the instruction root:

```clojure
(commando/execute
[commands-builtin/command-from-spec]
{"incrementing 1"
{"1" 1
"2" {:commando/from ["../" "1"] := inc}
"3" {:commando/from ["../" "2"] := inc}}
"decrementing 1"
{"1" 1
"2" {:commando/from ["../" "1"] := dec}
"3" {:commando/from ["../" "2"] := dec}}})
;; =>
;; {"incrementing 1" {"1" 1, "2" 2, "3" 3},
;; "decrementing 1" {"1" 1, "2" 0, "3" -1}}
{:catalog {:price 99}
:ref {:commando/from [:catalog :price]}})
;; => {:catalog {:price 99}, :ref 99}
```

**Relative path** — `"../"` goes up one level from the command's position, `"./"` stays at the current level:

```clojure
(commando/execute
[commands-builtin/command-from-spec]
{"section-a" {"price" 10 "ref" {"commando-from" ["../" "price"]}}
"section-b" {"price" 20 "ref" {"commando-from" ["../" "price"]}}})
;; => {"section-a" {"price" 10, "ref" 10}
;; "section-b" {"price" 20, "ref" 20}}
```

**Named anchors** — mark any map with `"__anchor"` or `:__anchor`, then jump to it with `"@name"` regardless of nesting depth. The resolver finds the nearest ancestor with that name, so duplicate anchor names are safe — each command resolves to its own closest one:

```clojure
(commando/execute
[commands-builtin/command-from-spec]
{:items [{:__anchor "item" :price 10 :total {:commando/from ["@item" :price]}}
{:__anchor "item" :price 20 :total {:commando/from ["@item" :price]}}]})
;; => {:items [{:__anchor "item", :price 10, :total 10}
;; {:__anchor "item", :price 20, :total 20}]}
```

Anchors and `"../"` can be combined in one path — after jumping to the anchor, navigation continues from there:

```clojure
{:commando/from ["@section" "../" :base-price]}
```

#### command-fn-spec
Expand Down
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<packaging>jar</packaging>
<groupId>org.clojars.funkcjonariusze</groupId>
<artifactId>commando</artifactId>
<version>1.0.5</version>
<version>1.0.6</version>
<name>commando</name>
<dependencies>
<dependency>
Expand Down Expand Up @@ -42,6 +42,6 @@
<url>https://github.com/funkcjonariusze/commando</url>
<connection>scm:git:git://github.com/funkcjonariusze/commando.git</connection>
<developerConnection>scm:git:ssh://git@github.com:funkcjonariusze/commando.git</developerConnection>
<tag>1.0.5</tag>
<tag>1.0.6</tag>
</scm>
</project>
75 changes: 56 additions & 19 deletions src/commando/impl/dependency.cljc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
(ns commando.impl.dependency
(:require
[clojure.string :as str]
[commando.impl.command-map :as cm]
[commando.impl.utils :as utils]))

Expand Down Expand Up @@ -60,23 +61,59 @@
(remove #(= % command-path-obj))
set)))

(defn- find-anchor-path
"Walks UP from current-path looking for the nearest ancestor map
that has key \"__anchor\" or :__anchor equal to anchor-name.
Returns the path vector to that ancestor, or nil if not found."
[instruction current-path anchor-name]
(loop [path (vec current-path)]
(when (seq path)
(let [node (get-in instruction path)]
(if (and (map? node)
(= anchor-name (or (get node "__anchor")
(get node :__anchor))))
path
(recur (vec (butlast path))))))))

(defn resolve-relative-path
"Resolves path segments with relative navigation (../ and ./) against a base path."
[base-path segments]
(let [{:keys [relative path]} (reduce (fn [acc segment]
(let [{:keys [relative path]} acc]
(cond
(= segment "../") {:relative
(if relative (butlast relative) (butlast base-path))
:path path}
(= segment "./") {:relative (if relative relative base-path)
:path path}
:else {:relative relative
:path (conj path segment)})))
{:relative nil
:path []}
segments)]
(if relative (concat relative path) path)))
"Resolves path segments with relative navigation against a base path.
Returns nil if an @anchor segment cannot be resolved.

Supported segment types:
\"../\" - go up one level from current position
\"./\" - stay at current level (noop for relative base)
\"@anchor\" - jump to nearest ancestor with matching __anchor name
(requires instruction to be passed as first argument)
any other - descend into that key"
[instruction base-path segments]
(let [result
(reduce
(fn [acc segment]
(let [{:keys [relative path]} acc
current-base (or relative base-path)]
(cond
(= segment "../")
{:relative (vec (butlast current-base)) :path path}

(= segment "./")
{:relative (vec current-base) :path path}

(and instruction
(string? segment)
(str/starts-with? segment "@"))
(let [anchor-name (subs segment 1)
anchor-path (find-anchor-path instruction (butlast current-base) anchor-name)]
(if anchor-path
{:relative anchor-path :path path}
(reduced nil)))

:else
{:relative relative :path (conj path segment)})))
{:relative nil :path []}
segments)]
(when result
(let [{:keys [relative path]} result]
(if relative (vec (concat relative path)) (vec path))))))

(defn path-exists-in-instruction?
"Checks if a path exists in the instruction map."
Expand Down Expand Up @@ -113,9 +150,8 @@
(reduced pointed-path)))
nil
point-key-seq)]
(->> pointed-path
(resolve-relative-path command-path)
vec)))
(or (resolve-relative-path instruction command-path pointed-path)
(throw-point-error command-path-obj pointed-path instruction))))

(defmethod find-command-dependencies :point
[command-path-obj instruction path-trie _type]
Expand Down Expand Up @@ -159,3 +195,4 @@
(find-command-dependencies command-path-obj instruction path-trie dependency-mode))))
{}
cm-list)))

Loading
Loading