Feature 57 (config and provisioning): modules declare config and provisions, their files rendered from templates; synced with main (issues of feature 57, 874)
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
The bars as a module (novox/hq ADR 0208, research 026/05).
|
||||
|
||||
- Installs `i3status-rust`. The update block's `checkupdates` comes from `pacman-contrib`, which the `pacman` module installs on every node; two modules declaring one package is refused at composition.
|
||||
Claims the mesh's `node-bar` seat (no verbs yet, ADR 0208 §2) and renders the blocks other modules
|
||||
Claims the mesh's `node-bar` seat (no verbs yet, ADR 0208 §2) and places the blocks other modules
|
||||
contribute to it (ADR 0255, below). Requires `x11-display` on its own
|
||||
machine: its bars are i3bar's.
|
||||
- Owns `~/.config/i3status-rust/`, both bars (`top-bar.toml`, `bottom-bar.toml`) and their icon set
|
||||
@@ -29,18 +29,19 @@ The bars as a module (novox/hq ADR 0208, research 026/05).
|
||||
## Blocks other modules contribute (novox/hq ADR 0255)
|
||||
|
||||
A block that depends on the machine's hardware is not written here. The module that knows the hardware
|
||||
contributes it to `node-bar` as data that says what it shows, and this module renders it:
|
||||
contributes it as text, in this module's grammar, to the bar's collection of blocks:
|
||||
|
||||
- **What a contribution says:** which bar (`bottom`, `top`), which place (`resources`, beside the
|
||||
processor, memory and disks; `status`, beside the sound), an order from 0 to 99, what it shows
|
||||
(`battery`, `command`) and that kind's options. Nothing in it is i3status-rust's.
|
||||
- **How it is rendered:** the claim's `renders.block` is a template (its source is `files/block.tmpl`)
|
||||
that turns one piece into a `[[block]]`. The controller runs it and places the result where each bar
|
||||
says `${contribution:node-bar:block:bar=…,place=…}`. Each bar places both of its places once; the
|
||||
catalogue check refuses a holder that misses one.
|
||||
- **Where it applies:** a contribution may name a capability the machine must report. The `power`
|
||||
module's battery block names `battery` and its power-draw block names `power-meter`, so the laptop's
|
||||
bar gets both and a desktop only the power draw.
|
||||
- **What a contributor gives:** an item of the collection `contribution:node-bar:block`, the block text in
|
||||
i3status-rust's grammar (a `[[block]]`), with the attributes `bar` (`bottom`, `top`), `place` (`resources`,
|
||||
beside the processor, memory and disks; `status`, beside the sound) and `order` (0 to 99), and, where the
|
||||
block depends on the hardware, the capability the machine must report.
|
||||
- **How it is placed:** each bar says `${collected:contribution:node-bar:block:bar=…,place=…}`, where the node's
|
||||
provisioner client fills the items that carry those attributes, in order, each under a line naming its
|
||||
contributor. Each bar places both of its places once. Where the client does not fill the file, the controller
|
||||
fills the same text the same way.
|
||||
- **Where it applies:** an item may name a capability the machine must report. The `power` module's battery item
|
||||
names `battery` and its power-draw item names `power-meter`, so the laptop's bar gets both and a desktop only
|
||||
the power draw.
|
||||
- **A value from the bus:** a block that `shows: state` names another module's state, such as
|
||||
`power.draw`. The mesh grants this module the read, because a module on its machine offers it; the
|
||||
manifest names no other module. This module's bundle watches it and leaves the value's text in the
|
||||
|
||||
@@ -1,87 +1,27 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// i3status-rust renders the blocks other modules contribute (novox/hq ADR 0255): a bar-neutral piece
|
||||
// — what it shows, on which bar, where — in its own grammar, through the template its claim gives. The
|
||||
// controller runs the template; these tests run it the same way, with the one function it is given.
|
||||
|
||||
func blockTemplate(t *testing.T) *template.Template {
|
||||
t.Helper()
|
||||
m := readManifest(t)
|
||||
src, err := os.ReadFile("../../files/block.tmpl")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if m.Claims[0].Renders["block"] != string(src) {
|
||||
t.Fatal("the claim's template is not files/block.tmpl")
|
||||
}
|
||||
quote := func(v any) (string, error) {
|
||||
b, err := json.Marshal(fmt.Sprint(v))
|
||||
return string(b), err
|
||||
}
|
||||
return template.Must(template.New("block").Funcs(template.FuncMap{"quote": quote}).Parse(string(src)))
|
||||
}
|
||||
|
||||
func render(t *testing.T, tmpl *template.Template, piece string) string {
|
||||
t.Helper()
|
||||
view := map[string]any{"module": "power", "machine": "laptop"}
|
||||
if err := json.Unmarshal([]byte(piece), &view); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var b bytes.Buffer
|
||||
if err := tmpl.Execute(&b, view); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if strings.Contains(b.String(), "<no value>") {
|
||||
t.Fatalf("%s rendered a missing value: %q", piece, b.String())
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func TestAContributedBatteryIsI3statusRustsBatteryBlock(t *testing.T) {
|
||||
tmpl := blockTemplate(t)
|
||||
got := render(t, tmpl, `{"bar":"bottom","place":"status","shows":"battery"}`)
|
||||
if !strings.HasPrefix(got, "[[block]]\nblock = \"battery\"\nformat = ") || strings.Contains(got, "device") ||
|
||||
!strings.Contains(got, `missing_format = ""`) || !strings.HasSuffix(got, "\n\n") {
|
||||
t.Fatalf("%q", got)
|
||||
}
|
||||
got = render(t, tmpl, `{"bar":"bottom","place":"status","shows":"battery","options":{"device":"BAT1"}}`)
|
||||
if !strings.Contains(got, "block = \"battery\"\ndevice = \"BAT1\"\nformat = ") {
|
||||
t.Fatalf("%q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAContributedCommandIsACustomBlockQuoted(t *testing.T) {
|
||||
got := render(t, blockTemplate(t),
|
||||
`{"bar":"bottom","place":"status","shows":"command","options":{"command":"say \"hi\"","interval":5}}`)
|
||||
want := "[[block]]\nblock = \"custom\"\ncommand = \"say \\\"hi\\\"\"\ninterval = 5\n\n"
|
||||
if got != want {
|
||||
t.Fatalf("%q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
// The blocks other modules contribute are a collection of text (design 57 L3, novox/hq ADR 0349 clause 13): each
|
||||
// contributor gives its block in i3status-rust's grammar, and the bars place the collection where they want it.
|
||||
|
||||
func TestEachBarPlacesBothPlacesOnce(t *testing.T) {
|
||||
m := readManifest(t)
|
||||
for _, bar := range []string{"bottom", "top"} {
|
||||
content := m.resource(t, bar+"-bar")["content"].(string)
|
||||
content := m.text(t, bar+"-bar")
|
||||
for _, place := range []string{"resources", "status"} {
|
||||
ph := "${contribution:node-bar:block:bar=" + bar + ",place=" + place + "}"
|
||||
ph := "${collected:contribution:node-bar:block:bar=" + bar + ",place=" + place + "}"
|
||||
if strings.Count(content, ph) != 1 {
|
||||
t.Errorf("the %s bar places %s %d times", bar, ph, strings.Count(content, ph))
|
||||
}
|
||||
}
|
||||
}
|
||||
bottom := m.resource(t, "bottom-bar")["content"].(string)
|
||||
bottom := m.text(t, "bottom-bar")
|
||||
resources := strings.Index(bottom, "place=resources")
|
||||
if !(strings.Index(bottom, `block = "memory"`) < resources && resources < strings.Index(bottom, `block = "docker"`)) ||
|
||||
!(strings.Index(bottom, `block = "sound"`) < strings.Index(bottom, "place=status") &&
|
||||
@@ -91,30 +31,33 @@ func TestEachBarPlacesBothPlacesOnce(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTheMemoryIsWhatProgramsHoldNotTheCache(t *testing.T) {
|
||||
bottom := readManifest(t).resource(t, "bottom-bar")["content"].(string)
|
||||
bottom := readManifest(t).text(t, "bottom-bar")
|
||||
if !strings.Contains(bottom, `format = "$icon $mem_used.eng(w:2) ($mem_used_percents.eng(w:2))"`) ||
|
||||
strings.Contains(bottom, "mem_total_used") {
|
||||
t.Fatal("the memory block counts the cache as used")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAContributedStateIsACustomBlockReadingThisMachinesValue(t *testing.T) {
|
||||
tmpl := blockTemplate(t)
|
||||
got := render(t, tmpl, `{"bar":"bottom","place":"status","shows":"state","options":{"state":"power.draw"}}`)
|
||||
want := "[[block]]\nblock = \"custom\"\ncommand = \"~/.local/bin/i3status-value power.draw laptop\"\ninterval = 5\nhide_when_empty = true\n\n"
|
||||
if got != want {
|
||||
t.Fatalf("%q, want %q", got, want)
|
||||
func TestABarNamesNoTemplateOfItsOwnForTheBlocksItPlaces(t *testing.T) {
|
||||
m := readManifest(t)
|
||||
for _, id := range []string{"top-bar", "bottom-bar"} {
|
||||
if strings.Contains(m.text(t, id), "${contribution:") {
|
||||
t.Errorf("the %s still places a contribution the old way", id)
|
||||
}
|
||||
}
|
||||
got = render(t, tmpl, `{"bar":"bottom","place":"status","shows":"state","options":{"state":"power.draw","key":"desk"}}`)
|
||||
if !strings.Contains(got, "i3status-value power.draw desk") {
|
||||
t.Fatalf("%q", got)
|
||||
if _, err := os.Stat("../../files/block.tmpl"); err == nil {
|
||||
t.Error("files/block.tmpl is still there, and no claim renders it")
|
||||
}
|
||||
// And the bundle finds what it rendered.
|
||||
}
|
||||
|
||||
func TestTheBundleFindsTheValueARenderedBlockShows(t *testing.T) {
|
||||
// What the power module's block renders on a machine called laptop.
|
||||
got := "[[block]]\nblock = \"custom\"\ncommand = \"~/.local/bin/i3status-value power.draw laptop\"\ninterval = 5\nhide_when_empty = true\n\n"
|
||||
f := filepath.Join(t.TempDir(), "bottom-bar.toml")
|
||||
if err := os.WriteFile(f, []byte(got), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if w := wantedIn([]string{f}); len(w) != 1 || w[0] != (Wanted{"power.draw", "desk"}) {
|
||||
if w := wantedIn([]string{f}); len(w) != 1 || w[0] != (Wanted{"power.draw", "laptop"}) {
|
||||
t.Fatalf("%v", w)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ import (
|
||||
type manifest struct {
|
||||
Module string `json:"module"`
|
||||
Version string `json:"version"`
|
||||
Moved bool `json:"moved"`
|
||||
Items []item `json:"items"`
|
||||
Capabilities []string `json:"capabilities"`
|
||||
Requires []string `json:"requires"`
|
||||
Claims []claim `json:"claims"`
|
||||
@@ -23,7 +25,7 @@ type manifest struct {
|
||||
Shell []shellCode `json:"shell"`
|
||||
Resources []map[string]any `json:"resources"`
|
||||
// Lines for other modules' seats (novox/hq ADR 0212): the window manager's, here.
|
||||
Contributions []contribution `json:"contributions"`
|
||||
Contributions []any `json:"contributions"`
|
||||
Build struct {
|
||||
Artifacts []map[string]any `json:"artifacts"`
|
||||
} `json:"build"`
|
||||
@@ -48,6 +50,53 @@ type shellCode struct {
|
||||
Code string `json:"code"`
|
||||
}
|
||||
|
||||
// An item of a collection (novox/hq ADR 0349 clause 13): its text is the template file beside the manifest.
|
||||
type item struct {
|
||||
Collection string `json:"collection"`
|
||||
Name string `json:"name"`
|
||||
Attributes map[string]string `json:"attributes"`
|
||||
Template string `json:"template"`
|
||||
}
|
||||
|
||||
// shell is the code the module adds to a shell's or the session's start, read from the templates of its items of
|
||||
// the collections shell:<file>:<slot>, in the order they are declared.
|
||||
func (m manifest) shell(t *testing.T) []shellCode {
|
||||
t.Helper()
|
||||
var out []shellCode
|
||||
for _, it := range m.Items {
|
||||
parts := strings.Split(it.Collection, ":")
|
||||
if len(parts) != 3 || parts[0] != "shell" {
|
||||
continue
|
||||
}
|
||||
text, err := os.ReadFile(filepath.Join("..", "..", it.Template))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
out = append(out, shellCode{For: parts[1], Slot: parts[2], Code: string(text)})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// environmentOf is the variables the module sets, read from the templates of its items of the environment.
|
||||
func (m manifest) environmentOf(t *testing.T) *environment {
|
||||
t.Helper()
|
||||
var env *environment
|
||||
for _, it := range m.Items {
|
||||
if it.Collection != "environment" || it.Attributes["kind"] != "variable" {
|
||||
continue
|
||||
}
|
||||
text, err := os.ReadFile(filepath.Join("..", "..", it.Template))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if env == nil {
|
||||
env = &environment{Variables: map[string]string{}}
|
||||
}
|
||||
env.Variables[it.Attributes["name"]] = strings.TrimSuffix(string(text), "\n")
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
func readManifest(t *testing.T) manifest {
|
||||
t.Helper()
|
||||
raw, err := os.ReadFile(filepath.Join("..", "..", "module.json"))
|
||||
@@ -60,6 +109,7 @@ func readManifest(t *testing.T) manifest {
|
||||
if err := dec.Decode(&m); err != nil {
|
||||
t.Fatalf("module.json: %v", err)
|
||||
}
|
||||
m.Environment = m.environmentOf(t)
|
||||
return m
|
||||
}
|
||||
|
||||
@@ -87,26 +137,66 @@ func (m manifest) packages() (present, absent []string) {
|
||||
return present, absent
|
||||
}
|
||||
|
||||
// sameAsSource checks that a file resource's content is byte for byte the module's source file, so
|
||||
// the readable file in the repository is what the machine gets.
|
||||
func (m manifest) sameAsSource(t *testing.T, id, source string) {
|
||||
// fromTemplate checks that a file resource takes its text from its template file in the module's
|
||||
// repository (novox/hq ADR 0349 §2): it names the template, carries no text of its own beside it, and the
|
||||
// template is there. The readable file in the repository is the one place the machine's text is written.
|
||||
func (m manifest) fromTemplate(t *testing.T, id, template string) {
|
||||
t.Helper()
|
||||
want, err := os.ReadFile(filepath.Join("..", "..", source))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r := m.resource(t, id)
|
||||
if r["type"] != "file" {
|
||||
t.Fatalf("%s is a %v, not a file", id, r["type"])
|
||||
}
|
||||
if got, _ := r["content"].(string); got != string(want) {
|
||||
t.Fatalf("resource %s's content is not %s: edit the source and copy it into module.json", id, source)
|
||||
if r["template"] != template {
|
||||
t.Fatalf("resource %s does not take its text from %s: template %v", id, template, r["template"])
|
||||
}
|
||||
if _, inline := r["content"]; inline {
|
||||
t.Fatalf("resource %s keeps its text inline beside its template: a file's text is in one place", id)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join("..", "..", template)); err != nil {
|
||||
t.Fatalf("resource %s names %s: %v", id, template, err)
|
||||
}
|
||||
if r["owner"] != "${machine:account}" && !strings.HasPrefix(r["path"].(string), "/etc/") {
|
||||
t.Fatalf("%s under the home is the account's", id)
|
||||
}
|
||||
}
|
||||
|
||||
// everyFileIsATemplate checks that no file resource carries its text inline, and that the build packs
|
||||
// files/ into the one archive the controller reads the templates back from (novox/hq ADR 0349 §2).
|
||||
func (m manifest) everyFileIsATemplate(t *testing.T) {
|
||||
t.Helper()
|
||||
for _, r := range m.Resources {
|
||||
if _, inline := r["content"]; inline {
|
||||
t.Errorf("resource %v keeps its text inline: keep it in files/ and name it as the template", r["id"])
|
||||
}
|
||||
}
|
||||
packs := 0
|
||||
for _, a := range m.Build.Artifacts {
|
||||
if a["kind"] == "archive" && a["from"] == "files" {
|
||||
packs++
|
||||
if a["name"] != "files" {
|
||||
t.Errorf("the templates archive is named %v, not files", a["name"])
|
||||
}
|
||||
}
|
||||
}
|
||||
if packs != 1 {
|
||||
t.Errorf("the build packs files/ into %d archives, not one", packs)
|
||||
}
|
||||
}
|
||||
|
||||
// text is the text a file resource places on the machine: its template file's.
|
||||
func (m manifest) text(t *testing.T, id string) string {
|
||||
t.Helper()
|
||||
name, _ := m.resource(t, id)["template"].(string)
|
||||
if name == "" {
|
||||
t.Fatalf("resource %s names no template", id)
|
||||
}
|
||||
raw, err := os.ReadFile(filepath.Join("..", "..", name))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return string(raw)
|
||||
}
|
||||
|
||||
// checkTheToolsAgree checks that the manifest lists the module's own tools exactly, that the bundle
|
||||
// serves each seat verb the claims promise as <seat>.<verb>, and that the Go bundle is declared.
|
||||
func checkTheToolsAgree(t *testing.T, m manifest) {
|
||||
@@ -178,31 +268,42 @@ func checkNoSecretsOrInstallationNames(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type contribution struct {
|
||||
Seat string `json:"seat"`
|
||||
Kind string `json:"kind"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
// displaySession is the collection the module's lines for the window manager are an item of.
|
||||
const displaySession = "contribution:node-display-session:config"
|
||||
|
||||
// i3Lines is what the module contributes to the window manager.
|
||||
// i3Lines is what the module contributes to the window manager: the text of its item of the collection
|
||||
// contribution:node-display-session:config (design 57 L3, issue 848).
|
||||
func (m manifest) i3Lines() string {
|
||||
var out string
|
||||
for _, c := range m.Contributions {
|
||||
if c.Seat == "node-display-session" && c.Kind == "config" {
|
||||
out += c.Content
|
||||
for _, it := range m.Items {
|
||||
if it.Collection == displaySession {
|
||||
text, err := os.ReadFile(filepath.Join("..", "..", it.Template))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
out += string(text)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// i3LinesAreSource checks the window-manager contribution is the source file it is written from.
|
||||
// i3LinesAreSource checks the window-manager lines are one item whose template is the source file, and that no
|
||||
// old-form contribution to node-display-session is left.
|
||||
func (m manifest) i3LinesAreSource(t *testing.T, source string) {
|
||||
t.Helper()
|
||||
want, err := os.ReadFile(filepath.Join("..", "..", source))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
if len(m.Contributions) != 0 {
|
||||
t.Fatalf("the module still holds contributions: the lines are an item of %s", displaySession)
|
||||
}
|
||||
if m.i3Lines() != string(want) {
|
||||
t.Fatalf("the contribution to node-display-session is not %s: edit the source and copy it into module.json", source)
|
||||
var templates []string
|
||||
for _, it := range m.Items {
|
||||
if it.Collection == displaySession {
|
||||
templates = append(templates, it.Template)
|
||||
}
|
||||
}
|
||||
if len(templates) != 1 || templates[0] != source {
|
||||
t.Fatalf("the items of %s are %v, want the one template %s", displaySession, templates, source)
|
||||
}
|
||||
if m.i3Lines() == "" {
|
||||
t.Fatalf("the template %s holds no line", source)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,14 @@ import (
|
||||
|
||||
func TestItClaimsTheBarSeatAndRequiresTheXDisplay(t *testing.T) {
|
||||
m := readManifest(t)
|
||||
if !m.Moved {
|
||||
t.Fatal("the module is not marked moved")
|
||||
}
|
||||
if m.Module != "i3status-rust" || m.Seats != nil {
|
||||
t.Fatalf("module %q declares seats %v", m.Module, m.Seats)
|
||||
}
|
||||
if len(m.Claims) != 1 || m.Claims[0].Name != "node-bar" || m.Claims[0].Scope != "node" ||
|
||||
len(m.Claims[0].Renders) != 1 || m.Claims[0].Renders["block"] == "" {
|
||||
len(m.Claims[0].Renders) != 0 {
|
||||
t.Fatalf("claims: %+v", m.Claims)
|
||||
}
|
||||
if !reflect.DeepEqual(m.Requires, []string{"x11-display"}) {
|
||||
@@ -35,13 +38,13 @@ func TestItOwnsItsFilesAsWrittenInTheModule(t *testing.T) {
|
||||
"updates": "files/bin/i3status-updates", "watchdog": "files/bin/i3bar-watchdog",
|
||||
"value": "files/bin/i3status-value",
|
||||
} {
|
||||
m.sameAsSource(t, id, src)
|
||||
m.fromTemplate(t, id, src)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoBlockFollowsAMachinesHardwareOrAPersonsDevicesOrCarriesAKey(t *testing.T) {
|
||||
m := readManifest(t)
|
||||
bottom := m.resource(t, "bottom-bar")["content"].(string)
|
||||
bottom := m.text(t, "bottom-bar")
|
||||
for _, never := range []string{`block = "battery"`, `block = "amd_gpu"`, `block = "nvidia_gpu"`, "mac = ", "api_key", "city_id", "openweathermap", "~/scripts/"} {
|
||||
if strings.Contains(bottom, never) {
|
||||
t.Errorf("the bottom bar has %s", never)
|
||||
@@ -57,9 +60,9 @@ func TestTheBarsAreAContributionToTheWindowManagerAndTheWatchdogRunsOncePerSessi
|
||||
!strings.Contains(c, "font pango:JetBrainsMono Nerd Font 11") {
|
||||
t.Fatalf("%s", c)
|
||||
}
|
||||
if len(m.Shell) != 1 || m.Shell[0].For != "xinitrc" || m.Shell[0].Slot != "normal" ||
|
||||
!strings.Contains(m.Shell[0].Code, `"$HOME/.local/bin/i3bar-watchdog" "$$" &`) {
|
||||
t.Fatalf("%+v", m.Shell)
|
||||
if len(m.shell(t)) != 1 || m.shell(t)[0].For != "xinitrc" || m.shell(t)[0].Slot != "normal" ||
|
||||
!strings.Contains(m.shell(t)[0].Code, `"$HOME/.local/bin/i3bar-watchdog" "$$" &`) {
|
||||
t.Fatalf("%+v", m.shell(t))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,3 +71,8 @@ func TestTheToolsAgreeWithTheManifest(t *testing.T) {
|
||||
checkTheToolsAgree(t, m)
|
||||
checkNoSecretsOrInstallationNames(t)
|
||||
}
|
||||
|
||||
// No file resource carries its text inline (novox/hq ADR 0349 §2).
|
||||
func TestEveryFileTakesItsTextFromATemplate(t *testing.T) {
|
||||
readManifest(t).everyFileIsATemplate(t)
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
{{- /* A block other modules contribute, in i3status-rust's grammar (novox/hq ADR 0255). The controller
|
||||
renders one piece at a time: its fields (bar, place, order, shows, options), module and machine. */ -}}
|
||||
{{- if eq .shows "battery" -}}
|
||||
[[block]]
|
||||
block = "battery"
|
||||
{{with .options}}{{with .device}}device = {{quote .}}
|
||||
{{end}}{{end -}}
|
||||
format = "$icon $percentage{ $time|}"
|
||||
charging_format = "$icon $percentage{ $time|}"
|
||||
not_charging_format = "$icon $percentage"
|
||||
full_format = "$icon $percentage"
|
||||
empty_format = "$icon $percentage"
|
||||
missing_format = ""
|
||||
{{- else if eq .shows "command" -}}
|
||||
[[block]]
|
||||
block = "custom"
|
||||
command = {{quote .options.command}}
|
||||
interval = {{.options.interval}}
|
||||
{{- else if eq .shows "state" -}}
|
||||
[[block]]
|
||||
block = "custom"
|
||||
command = {{quote (printf "~/.local/bin/i3status-value %s %s" .options.state (or .options.key .machine))}}
|
||||
interval = 5
|
||||
hide_when_empty = true
|
||||
{{- end}}
|
||||
|
||||
@@ -74,7 +74,7 @@ format_alt = "$icon_swap $swap_used.eng(w:2) ($swap_used_percents.eng(w:2))"
|
||||
|
||||
# Blocks other modules contribute beside the machine's resources (novox/hq ADR 0255), rendered from
|
||||
# what they show by this module's template, and only on a machine that has what they show.
|
||||
${contribution:node-bar:block:bar=bottom,place=resources}
|
||||
${collected:contribution:node-bar:block:bar=bottom,place=resources}
|
||||
[[block]]
|
||||
block = "docker"
|
||||
interval = 2
|
||||
@@ -88,7 +88,7 @@ cmd = "pavucontrol"
|
||||
|
||||
# Blocks other modules contribute beside the sound (novox/hq ADR 0255): the power module's battery and
|
||||
# power draw, where the machine has them.
|
||||
${contribution:node-bar:block:bar=bottom,place=status}
|
||||
${collected:contribution:node-bar:block:bar=bottom,place=status}
|
||||
# The weather from the Norwegian Meteorological Institute, which needs no key, where the machine is.
|
||||
[[block]]
|
||||
block = "weather"
|
||||
|
||||
@@ -34,4 +34,4 @@ interval = 60
|
||||
|
||||
# Blocks other modules contribute to the top bar (novox/hq ADR 0255). None do yet; both places are kept
|
||||
# so that one that does is never dropped.
|
||||
${contribution:node-bar:block:bar=top,place=resources}${contribution:node-bar:block:bar=top,place=status}
|
||||
${collected:contribution:node-bar:block:bar=top,place=resources}${collected:contribution:node-bar:block:bar=top,place=status}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# The bar watchdog (module i3status-rust, novox/hq ADR 0208): i3 never restarts a bar that dies; this
|
||||
# brings it back. Once per session, ending with the session's own process ($$).
|
||||
"$HOME/.local/bin/i3bar-watchdog" "$$" &
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user