Skip to content
Merged
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
141 changes: 53 additions & 88 deletions manpage
Original file line number Diff line number Diff line change
Expand Up @@ -2302,9 +2302,9 @@ contains the git branch name of the build. May be empty if not available. For ex
.p
dev-1.0.0
.IP "build-time" 3
contains the UTC timestamp of when the build was created. Format is
contains the timestamp of when the build was created. Format is
.B yyyy-MM-ddTHH:mm:ss.
For example:
It has local time for local builds, and UTC for GitHub builds. For example:
.p
2026-06-08T08:22:52
.IP "build-source" 3
Expand All @@ -2318,113 +2318,78 @@ contains the build number from GitHub Actions (github.run_number). Empty for loc
.IP "build-info" 3
contains a composite string with all build information. For example:
.p
Built locally from commit 8bd11da on branch dev-1.0.0 at 2026-06-08T13:01:18
Built locally from commit 8bd11da on branch dev-1.0.0 at 2026-06-08T13:01:18 local
.p
or
.p
Built on github (build 217) from commit 8bd11da of version 1.0.0 at 2026-06-08T12:40:01
Built on github (build 217) from commit 8bd11da of version 1.0.0 at 2026-06-08T10:01:18 UTC
.p

.SH EXAMPLES
`ls -l` yields this:

total 352
-rw-r--r--@ 1 ynir admin 574 Aug 25 2009 Makefile
-rw-r--r--@ 1 ynir admin 3542 Nov 23 00:21 README
-rw-r--r--@ 1 ynir admin 362 Nov 19 08:31 conversion.h
-rw-r--r-- 1 ynir admin 984 Nov 11 17:45 ls.txt
-rw-r--r--@ 1 ynir admin 2233 Nov 23 00:03 main.cc
-rw-r--r-- 1 ynir admin 9412 Nov 23 00:11 main.o
-rw-r--r--@ 1 ynir admin 6567 Nov 23 00:09 spec_build.cc
-rw-r--r-- 1 ynir admin 16776 Nov 23 00:11 spec_build.o
-rw-r--r--@ 1 ynir admin 5494 Nov 19 08:30 spec_convert.cc
-rw-r--r-- 1 ynir admin 17004 Nov 23 00:11 spec_convert.o
-rw-r--r--@ 1 ynir admin 11419 Nov 23 00:10 spec_params.cc
-rw-r--r-- 1 ynir admin 21080 Nov 23 00:11 spec_params.o
-rw-r--r--@ 1 ynir admin 375 Nov 11 09:29 spec_vars.cc
-rw-r--r-- 1 ynir admin 4800 Nov 23 00:11 spec_vars.o
-rwxr-xr-x 1 ynir admin 36740 Nov 23 00:11 specs
-rw-r--r--@ 1 ynir admin 1547 Nov 23 00:10 specs.h
total 96
-rw-rw-r-- 1 sio sio 16432 Jun 1 12:49 dataField.cc
-rw-rw-r-- 1 sio sio 6187 Jun 4 12:16 InputPart.cc
-rw-rw-r-- 1 sio sio 11717 Jun 4 12:16 item.h
-rw-rw-r-- 1 sio sio 35974 Jun 4 12:16 specItems.cc
-rw-rw-r-- 1 sio sio 1144 Jun 4 12:16 specItems.h
-rw-rw-r-- 1 sio sio 13953 Jun 1 12:49 splitItem.cc

Let's run it though a spec:

ls -l | specs 12-* 1 redo w2 1 w4 d2x 8.8 r w8 17
ls -l | specs 12-* 1 REDO IF "wordcount()>=8" THEN w2 1 w4 d2x 8.8 RIGHT w8 17

The first spec unit converts it to this:

1 ynir admin 574 Aug 25 2009 Makefile
1 ynir admin 3542 Nov 23 00:21 README
1 ynir admin 362 Nov 19 08:31 conversion.h
1 ynir admin 984 Nov 11 17:45 ls.txt
1 ynir admin 2233 Nov 23 00:03 main.cc
1 ynir admin 9412 Nov 23 00:11 main.o
1 ynir admin 6567 Nov 23 00:09 spec_build.cc
1 ynir admin 16776 Nov 23 00:11 spec_build.o
1 ynir admin 5494 Nov 19 08:30 spec_convert.cc
1 ynir admin 17004 Nov 23 00:11 spec_convert.o
1 ynir admin 11419 Nov 23 00:10 spec_params.cc
1 ynir admin 21080 Nov 23 00:11 spec_params.o
1 ynir admin 375 Nov 11 09:29 spec_vars.cc
1 ynir admin 4800 Nov 23 00:11 spec_vars.o
1 ynir admin 36740 Nov 23 00:11 specs
1 ynir admin 1547 Nov 23 00:10 specs.h

Then after the redo, we get this:

ynir 23e Makefile
ynir dd6 README
ynir 16a conversion.h
ynir 3d8 ls.txt
ynir 8b9 main.cc
ynir 24c4 main.o
ynir 19a7 spec_build.cc
ynir 4188 spec_build.o
ynir 1576 spec_convert.cc
ynir 426c spec_convert.o
ynir 2c9b spec_params.cc
ynir 5258 spec_params.o
ynir eae spec_vars.cc
ynir 12c0 spec_vars.o
ynir 8f84 specs
ynir 60b specs.h

1 sio sio 16432 Jun 1 12:49 dataField.cc
1 sio sio 6187 Jun 4 12:16 InputPart.cc
1 sio sio 11717 Jun 4 12:16 item.h
1 sio sio 35974 Jun 4 12:16 specItems.cc
1 sio sio 1144 Jun 4 12:16 specItems.h
1 sio sio 13953 Jun 1 12:49 splitItem.cc

Then after the REDO arm, we get this:

sio 4030 dataField.cc
sio 182b InputPart.cc
sio 2dc5 item.h
sio 8c86 specItems.cc
sio 478 specItems.h
sio 3681 splitItem.cc

Alternatively, let's arrange this on multiple lines:

ls -l | specs w9 1 write "Owner:" 3 w3 10 write "Size:" 3 w5 10-20 r

Makefile
Owner: ynir
Size: 574
README
Owner: ynir
Size: 5834
conversion.h
Owner: ynir
Size: 362
list.txt
Owner: ynir
Size: 978
ls.txt
Owner: ynir
Size: 984
main.cc
Owner: ynir
Size: 2233
main.o
Owner: ynir
Size: 9412
specs -C "ls -l" w9 1 write "Owner:" 3 w3 10 write "Size:" 3 w5 10-20 r

Owner:
Size: r
dataField.cc
Owner: sio
Size: 16432 r
InputPart.cc
Owner: sio
Size: 6187 r
item.h
Owner: sio
Size: 11717 r
specItems.cc
Owner: sio
Size: 35974 r
specItems.h
Owner: sio
Size: 1144 r
splitItem.cc
Owner: sio
Size: 13953 r

Finally, let's make our own version of the multi-column display:

ls -l | specs w9 1 read w9 26 read w9 51
Makefile README
conversion.h main.cc main.o
spec_build.cc spec_build.o spec_convert.cc
spec_convert.o spec_params.cc spec_params.o
spec_vars.cc spec_vars.o specs
specs.h
specs -C "ls -l" w9 1 read w9 26 read w9 51

dataField.cc InputPart.cc
item.h specItems.cc specItems.h
splitItem.cc

.SH SEE ALSO
sed(1), awk(1)
Expand Down
6 changes: 3 additions & 3 deletions specs/docs/alu.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,13 +120,13 @@ Others are `@cols`, which contains the number of columns in the terminal screen,
Build information is also available via the following labels:
- `@build-commit` — the git commit hash (short form) of the build
- `@build-branch` — the git branch name (may be empty)
- `@build-time` — the UTC timestamp when the build was created (format: `yyyy-MM-ddTHH:mm:ss`)
- `@build-time` — the timestamp when the build was created (format: `yyyy-MM-ddTHH:mm:ss`). It's local time for local builds, or UTC for GitHub builds.
- `@build-source` — either `local` or `github`
- `@build-number` — the GitHub Actions build number (empty for local builds)
- `@build-info` — a composite string with all build information, e.g.:
```
Built locally from commit 8bd11da on branch dev-1.0.0 at 2026-06-08T13:01:18
Built on github (build 217) from commit 8bd11da of version 1.0.0 at 2026-06-08T12:40:01
Built locally from commit 8bd11da on branch dev-1.0.0 at 2026-06-08T13:01:18 local
Built on github (build 217) from commit 8bd11da of version 1.0.0 at 2026-06-08T10:01:18 UTC
```

Additionally, the `@@` string stands for the entire input record. When rolling context is in effect (see [Streams and Records](streams.md#rolling-context)), `@@` always refers to the original input record. The `@!` string refers to the current record as affected by `CONTEXT`, which is the same as `@@` when no `CONTEXT` is active. The `@-n` and `@+n` syntax is an alternative to using that is effective within expressions. Note that reading beyond the input with `@+n` or `@-n` does not cause processing to stop, even if a `READSTOP` token is present in the specification. The following three specifications are equivalent:
Expand Down
2 changes: 1 addition & 1 deletion specs/docs/onepage.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ There are some pre-configured labels that do not need to be explicitly defined:
* rows - contains the number of screen rows.
* build-commit - contains the git commit hash (short form) of the build
* build-branch - contains the git branch name (may be empty)
* build-time - contains the UTC timestamp when the build was created (format: `yyyy-MM-ddTHH:mm:ss`)
* build-time - contains the timestamp when the build was created (format: `yyyy-MM-ddTHH:mm:ss`). It's local time for local builds, or UTC for GitHub builds.
* build-source - contains either `local` or `github`
* build-number - contains the GitHub Actions build number (empty for local builds)
* build-info - contains a composite string with all build information
Expand Down
11 changes: 7 additions & 4 deletions specs/src/generate_build_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,19 @@ def run_git(name, args):
if build_branch:
report_success("SPECS_BUILD_BRANCH (from SPECS_BRANCH env)", build_branch)

# Get UTC build time
build_time = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%S")
report_success("SPECS_BUILD_TIME", build_time)

# Get build source and number from environment
build_source = os.environ.get("SPECS_BUILD_SOURCE", "local")
report_success("SPECS_BUILD_SOURCE", build_source)
build_number = os.environ.get("SPECS_BUILD_NUMBER", "")
report_success("SPECS_BUILD_NUMBER", build_number)

# Get UTC build time
if build_source == "local":
build_time = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
else:
build_time = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%S")
report_success("SPECS_BUILD_TIME", build_time)

# Write the header file
with open("utils/build_info.h", "w") as f:
f.write('#define SPECS_BUILD_COMMIT "{}"\n'.format(build_commit))
Expand Down
10 changes: 7 additions & 3 deletions specs/src/processing/Config.cc
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ static std::string getTerminalRowsAndColumns(bool bGetRows)

}

void readConfigurationFile()
void readConfigurationFile(useKeyValueCB cb)
{
std::string line;
unsigned int lineCounter = 0;
Expand Down Expand Up @@ -194,7 +194,11 @@ void readConfigurationFile()
value = line.substr(idx2, idx-idx2);
}

useKeyValue(key, value);
if (cb) {
(*cb)(key, value);
} else {
useKeyValue(key, value);
}
}
} else {
}
Expand Down Expand Up @@ -241,7 +245,7 @@ void readConfigurationFile()
}
}
}
build_info += " at " + ExternalLiterals["build-time"];
build_info += " at " + ExternalLiterals["build-time"] + (ExternalLiterals["build-source"] == "github" ? " UTC" : " local");
ExternalLiterals["build-info"] = build_info;
}

Expand Down
3 changes: 2 additions & 1 deletion specs/src/processing/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ CONFIG_PARAMS
#define EXTERNAL_FUNC_ERR_ZERO "zero"
#define EXTERNAL_FUNC_ERR_NULLSTR "nullstr"

void readConfigurationFile();
typedef void (*useKeyValueCB)(std::string& key, std::string& value);
void readConfigurationFile(useKeyValueCB cb = nullptr);

bool configSpecLiteralExists(std::string& key);

Expand Down
70 changes: 70 additions & 0 deletions specs/src/test/specs-autocomplete.cc
Original file line number Diff line number Diff line change
@@ -1,12 +1,33 @@
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#include <filesystem>
#include "processing/Config.h"
#include "utils/platform.h"

typedef std::vector<std::string> StringVector;

StringVector SystemDefinedLabels = {
"@version",
"@cols",
"@rows",
"@python",
"@platform",
"@build-commit",
"@build-branch",
"@build-time",
"@build-source",
"@build-number",
"@build-info"
};

static void AddToSystemDefineLabels(std::string& key, std::string& value)
{
key.pop_back(); // remove the final colon
SystemDefinedLabels.push_back("@" + key);
}

void GetFilesByPrefix(StringVector& sv, const char* path, std::string& prefix)
{
std::filesystem::path specPath(path);
Expand Down Expand Up @@ -35,8 +56,32 @@ static void getFilenameVector(StringVector& sv, std::string& incomplete, std::st
}
}

int CompleteUncertain_SystemLabels(std::string& incomplete)
{
readConfigurationFile(AddToSystemDefineLabels);
StringVector vec;
for (auto s : SystemDefinedLabels) {
if (0==s.compare(0, incomplete.size(), incomplete)) {
vec.push_back(s);
}
}

// sort the vector
std::sort(vec.begin(), vec.end());

for (auto s : vec) {
std::cout << s << "\n";
}

return 0;
}

int CompleteUncertain(std::string& incomplete, std::string& prevToken, std::string& line)
{
if ('@'==incomplete[0]) {
return CompleteUncertain_SystemLabels(incomplete);
}

StringVector sv;

getFilenameVector(sv, incomplete, prevToken);
Expand All @@ -48,8 +93,33 @@ int CompleteUncertain(std::string& incomplete, std::string& prevToken, std::stri
return 0;
}

int CompleteIfUnambiguous_SystemLabels(std::string& incomplete)
{
readConfigurationFile(AddToSystemDefineLabels);
std::string res;
for (auto s : SystemDefinedLabels) {
if (0==s.compare(0, incomplete.size(), incomplete)) {
if (res.empty()) { // First match
res = s;
} else { // not-first match
// trim non-matching characters
while (s.compare(0,res.size(), res)) {
res.pop_back();
}
}
}
}

std::cout << res;
return 0;
}

int CompleteIfUnambiguous(std::string& incomplete, std::string& prevToken, std::string& line)
{
if ('@'==incomplete[0]) {
return CompleteIfUnambiguous_SystemLabels(incomplete);
}

StringVector sv;

getFilenameVector(sv, incomplete, prevToken);
Expand Down
Loading