mirror of
https://github.com/daylinmorgan/oizys.git
synced 2024-12-24 03:40:44 -06:00
Compare commits
2 commits
27530513f9
...
86c4fb428f
Author | SHA1 | Date | |
---|---|---|---|
86c4fb428f | |||
8fe319e525 |
21 changed files with 1046 additions and 51 deletions
121
flake.lock
121
flake.lock
|
@ -20,11 +20,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1724273991,
|
"lastModified": 1724850097,
|
||||||
"narHash": "sha256-+aUSOXKGpS5CRm1oTitgNAr05ThQNbKIXalZHl3nC6Y=",
|
"narHash": "sha256-3BHxvFb3NJzch1X8puRMkVZujOoarQ1llu3ZcwuvsKU=",
|
||||||
"owner": "hyprwm",
|
"owner": "hyprwm",
|
||||||
"repo": "aquamarine",
|
"repo": "aquamarine",
|
||||||
"rev": "9a3161ad4c78dc420d1cbb3aae638222608c7de4",
|
"rev": "23c7925dd31e79e8c06086ace3edb129a070ac01",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -323,11 +323,11 @@
|
||||||
"xdph": "xdph"
|
"xdph": "xdph"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1724701003,
|
"lastModified": 1724938863,
|
||||||
"narHash": "sha256-kTceEi5B4t2u5oG3bKnGgZkHxFTsvYAUiAmyYA/6Y3o=",
|
"narHash": "sha256-BXoaqc2NfAfh1g3nPPk4nzufxbMoW80NYXv3twgjq1c=",
|
||||||
"ref": "refs/heads/main",
|
"ref": "refs/heads/main",
|
||||||
"rev": "eb42adc4c090918ad6be9fcb24066da8cdfd9bd0",
|
"rev": "92a0dd164e9cc74060b63abae67b0204b6b6074c",
|
||||||
"revCount": 5145,
|
"revCount": 5156,
|
||||||
"submodules": true,
|
"submodules": true,
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/hyprwm/Hyprland/"
|
"url": "https://github.com/hyprwm/Hyprland/"
|
||||||
|
@ -444,11 +444,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1722869141,
|
"lastModified": 1724863980,
|
||||||
"narHash": "sha256-0KU4qhyMp441qfwbirNg3+wbm489KnEjXOz2I/RbeFs=",
|
"narHash": "sha256-7Ke9wFRYPUIXwm5ZndGHkWBKj6BsFTkSEXUNXQRHE54=",
|
||||||
"owner": "hyprwm",
|
"owner": "hyprwm",
|
||||||
"repo": "hyprutils",
|
"repo": "hyprutils",
|
||||||
"rev": "0252fd13e78e60fb0da512a212e56007515a49f7",
|
"rev": "aadf9a27dddd2272ca354ba5a22a0c2d1f919039",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -504,11 +504,11 @@
|
||||||
"lix": {
|
"lix": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1724714224,
|
"lastModified": 1724864109,
|
||||||
"narHash": "sha256-Fn72c2ycgYERAdwWEPmIUhuOmgOwuvZhTNdqOuiR0uw=",
|
"narHash": "sha256-IjSu5PnS+LFqHfJgueDXrqSBd9/j9GxAbrFK8F1/Z5Y=",
|
||||||
"rev": "0dc486a5bf218870aafd5552586ab4330881647e",
|
"rev": "a510d1748416ff29b1ed3cab92ac0ad943b6e590",
|
||||||
"type": "tarball",
|
"type": "tarball",
|
||||||
"url": "https://git.lix.systems/api/v1/repos/lix-project/lix/archive/0dc486a5bf218870aafd5552586ab4330881647e.tar.gz?rev=0dc486a5bf218870aafd5552586ab4330881647e"
|
"url": "https://git.lix.systems/api/v1/repos/lix-project/lix/archive/a510d1748416ff29b1ed3cab92ac0ad943b6e590.tar.gz?rev=a510d1748416ff29b1ed3cab92ac0ad943b6e590"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"type": "tarball",
|
"type": "tarball",
|
||||||
|
@ -538,11 +538,29 @@
|
||||||
"url": "https://git.lix.systems/lix-project/nixos-module/archive/main.tar.gz"
|
"url": "https://git.lix.systems/lix-project/nixos-module/archive/main.tar.gz"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"nim2nix": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs_3"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1724878933,
|
||||||
|
"narHash": "sha256-VP0+Lal3jJJqDH1EzQX73rP9Ue8ZTIyAErJvDz6PQSg=",
|
||||||
|
"owner": "daylinmorgan",
|
||||||
|
"repo": "nim2nix",
|
||||||
|
"rev": "5153c772e4c6a4f5645efa85ce536fe1c5063ebb",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "daylinmorgan",
|
||||||
|
"repo": "nim2nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"nix-eval-jobs": {
|
"nix-eval-jobs": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-parts": "flake-parts",
|
"flake-parts": "flake-parts",
|
||||||
"nix-github-actions": "nix-github-actions",
|
"nix-github-actions": "nix-github-actions",
|
||||||
"nixpkgs": "nixpkgs_5",
|
"nixpkgs": "nixpkgs_6",
|
||||||
"treefmt-nix": "treefmt-nix"
|
"treefmt-nix": "treefmt-nix"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
|
@ -630,7 +648,7 @@
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-compat": "flake-compat",
|
"flake-compat": "flake-compat",
|
||||||
"flake-utils": "flake-utils_2",
|
"flake-utils": "flake-utils_2",
|
||||||
"nixpkgs": "nixpkgs_3"
|
"nixpkgs": "nixpkgs_4"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1724664098,
|
"lastModified": 1724664098,
|
||||||
|
@ -648,11 +666,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1724224976,
|
"lastModified": 1724819573,
|
||||||
"narHash": "sha256-Z/ELQhrSd7bMzTO8r7NZgi9g5emh+aRKoCdaAv5fiO0=",
|
"narHash": "sha256-GnR7/ibgIH1vhoy8cYdmXE6iyZqKqFxQSVkFgosBh6w=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "c374d94f1536013ca8e92341b540eba4c22f9c62",
|
"rev": "71e91c409d1e654808b2621f28a327acfdad8dc2",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -682,14 +700,14 @@
|
||||||
"flake-compat": "flake-compat_2",
|
"flake-compat": "flake-compat_2",
|
||||||
"lib-aggregate": "lib-aggregate",
|
"lib-aggregate": "lib-aggregate",
|
||||||
"nix-eval-jobs": "nix-eval-jobs",
|
"nix-eval-jobs": "nix-eval-jobs",
|
||||||
"nixpkgs": "nixpkgs_6"
|
"nixpkgs": "nixpkgs_7"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1724746994,
|
"lastModified": 1724927668,
|
||||||
"narHash": "sha256-krOFIWrWMW44GBpjjKKo4dcM6oPXI9YpFj+Wqf2PwxU=",
|
"narHash": "sha256-hOkuflGoYCO5fykoJzCkzI3/7wti8Db62KADHu+ySPo=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "nixpkgs-wayland",
|
"repo": "nixpkgs-wayland",
|
||||||
"rev": "f42d86203af2967f81c50cf4f0722c013bc22170",
|
"rev": "9ec93a8a2911b6fafafb8dfd600440124d098528",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -715,6 +733,22 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs_3": {
|
"nixpkgs_3": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1724479785,
|
||||||
|
"narHash": "sha256-pP3Azj5d6M5nmG68Fu4JqZmdGt4S4vqI5f8te+E/FTw=",
|
||||||
|
"owner": "nixos",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "d0e1602ddde669d5beb01aec49d71a51937ed7be",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nixos",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_4": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1724316499,
|
"lastModified": 1724316499,
|
||||||
"narHash": "sha256-Qb9MhKBUTCfWg/wqqaxt89Xfi6qTD3XpTzQ9eXi3JmE=",
|
"narHash": "sha256-Qb9MhKBUTCfWg/wqqaxt89Xfi6qTD3XpTzQ9eXi3JmE=",
|
||||||
|
@ -730,13 +764,13 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs_4": {
|
"nixpkgs_5": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1724395761,
|
"lastModified": 1724748588,
|
||||||
"narHash": "sha256-zRkDV/nbrnp3Y8oCADf5ETl1sDrdmAW6/bBVJ8EbIdQ=",
|
"narHash": "sha256-NlpGA4+AIf1dKNq76ps90rxowlFXUsV9x7vK/mN37JM=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "ae815cee91b417be55d43781eb4b73ae1ecc396c",
|
"rev": "a6292e34000dc93d43bccf78338770c1c5ec8a99",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -746,7 +780,7 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs_5": {
|
"nixpkgs_6": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1723221148,
|
"lastModified": 1723221148,
|
||||||
"narHash": "sha256-7pjpeQlZUNQ4eeVntytU3jkw9dFK3k1Htgk2iuXjaD8=",
|
"narHash": "sha256-7pjpeQlZUNQ4eeVntytU3jkw9dFK3k1Htgk2iuXjaD8=",
|
||||||
|
@ -762,13 +796,13 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs_6": {
|
"nixpkgs_7": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1724479785,
|
"lastModified": 1724819573,
|
||||||
"narHash": "sha256-pP3Azj5d6M5nmG68Fu4JqZmdGt4S4vqI5f8te+E/FTw=",
|
"narHash": "sha256-GnR7/ibgIH1vhoy8cYdmXE6iyZqKqFxQSVkFgosBh6w=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "d0e1602ddde669d5beb01aec49d71a51937ed7be",
|
"rev": "71e91c409d1e654808b2621f28a327acfdad8dc2",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -809,11 +843,11 @@
|
||||||
"rust-overlay": "rust-overlay"
|
"rust-overlay": "rust-overlay"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1724759134,
|
"lastModified": 1724859070,
|
||||||
"narHash": "sha256-E4nBGiBGkdB1e01rGLAEiDhM5cKMnVbRPUXwXf3CjaU=",
|
"narHash": "sha256-/jeh/OJm5+gmg+dQxpWetL9jgjk+zGe7uCRLvbNvukA=",
|
||||||
"owner": "roc-lang",
|
"owner": "roc-lang",
|
||||||
"repo": "roc",
|
"repo": "roc",
|
||||||
"rev": "7811d8876865dc9927d707edb8fd8a426a595a0e",
|
"rev": "479feca39618a58770db68b44072be1f29df8d84",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -830,9 +864,10 @@
|
||||||
"hyprman": "hyprman",
|
"hyprman": "hyprman",
|
||||||
"lix": "lix",
|
"lix": "lix",
|
||||||
"lix-module": "lix-module",
|
"lix-module": "lix-module",
|
||||||
|
"nim2nix": "nim2nix",
|
||||||
"nix-index-database": "nix-index-database",
|
"nix-index-database": "nix-index-database",
|
||||||
"nixos-wsl": "nixos-wsl",
|
"nixos-wsl": "nixos-wsl",
|
||||||
"nixpkgs": "nixpkgs_4",
|
"nixpkgs": "nixpkgs_5",
|
||||||
"nixpkgs-wayland": "nixpkgs-wayland",
|
"nixpkgs-wayland": "nixpkgs-wayland",
|
||||||
"pixi": "pixi",
|
"pixi": "pixi",
|
||||||
"roc": "roc",
|
"roc": "roc",
|
||||||
|
@ -1040,11 +1075,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1723841055,
|
"lastModified": 1724782685,
|
||||||
"narHash": "sha256-ykjmNEXFJ4lrW1LeB4dVsNlPKlNB0VWNnOTp1sLbOqQ=",
|
"narHash": "sha256-/gzaaWVqXbnj78pNC7cOW/MDZidO127v9QRpAyBgczU=",
|
||||||
"ref": "refs/heads/main",
|
"ref": "refs/heads/main",
|
||||||
"rev": "eca60579ef577ad45aa1a371f006697ddc2611d6",
|
"rev": "5289c14095ec7fe0676a0dd3c3084ead095da1a5",
|
||||||
"revCount": 5,
|
"revCount": 6,
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.dayl.in/daylin/utils.git"
|
"url": "https://git.dayl.in/daylin/utils.git"
|
||||||
},
|
},
|
||||||
|
@ -1092,11 +1127,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1724718400,
|
"lastModified": 1724933442,
|
||||||
"narHash": "sha256-i83icUP6218jkNLBiHj2JyhKrVbgkU2mOGFvpFjw0WM=",
|
"narHash": "sha256-3Wgq2zGQyM61ElJL30qDREruyXZtryrxXdvdwQ61OFo=",
|
||||||
"owner": "mitchellh",
|
"owner": "mitchellh",
|
||||||
"repo": "zig-overlay",
|
"repo": "zig-overlay",
|
||||||
"rev": "20a8e0316531f825284ef15fe20b4f9848fbb4e4",
|
"rev": "004106b4749886ae47894555a904a9a6cba12ead",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
|
@ -23,10 +23,10 @@
|
||||||
hyprland.url = "git+https://github.com/hyprwm/Hyprland/?submodules=1";
|
hyprland.url = "git+https://github.com/hyprwm/Hyprland/?submodules=1";
|
||||||
hyprland-contrib.url = "github:hyprwm/contrib";
|
hyprland-contrib.url = "github:hyprwm/contrib";
|
||||||
roc.url = "github:roc-lang/roc";
|
roc.url = "github:roc-lang/roc";
|
||||||
# zls build failing on master?
|
zig-overlay.url = "github:mitchellh/zig-overlay";
|
||||||
zig-overlay.url = "github:mitchellh/zig-overlay/20a8e0316531f825284ef15fe20b4f9848fbb4e4";
|
zls.url = "github:zigtools/zls";
|
||||||
zls.url = "github:zigtools/zls/b481aaeac81ffccffb0ce5cf2f05310f3661c6b2";
|
|
||||||
|
|
||||||
|
nim2nix.url = "github:daylinmorgan/nim2nix";
|
||||||
pixi.url = "github:daylinmorgan/pixi-flake";
|
pixi.url = "github:daylinmorgan/pixi-flake";
|
||||||
f1multiviewer.url = "github:daylinmorgan/f1multiviewer-flake";
|
f1multiviewer.url = "github:daylinmorgan/f1multiviewer-flake";
|
||||||
tsm.url = "github:daylinmorgan/tsm?dir=nix";
|
tsm.url = "github:daylinmorgan/tsm?dir=nix";
|
||||||
|
|
|
@ -12,7 +12,8 @@
|
||||||
nix-ld = enabled // {
|
nix-ld = enabled // {
|
||||||
overkill = enabled;
|
overkill = enabled;
|
||||||
};
|
};
|
||||||
languages = "misc|nim|node|nushell|python|roc|tex|zig" |> listify;
|
# languages = "misc|nim|node|nushell|python|roc|tex|zig" |> listify;
|
||||||
|
languages = "misc|nim|node|nushell|python|roc|tex" |> listify;
|
||||||
}
|
}
|
||||||
// (
|
// (
|
||||||
''
|
''
|
||||||
|
|
|
@ -9,7 +9,10 @@ let
|
||||||
inherit (import ./generators.nix { inherit lib self inputs; }) mkIso mkSystem;
|
inherit (import ./generators.nix { inherit lib self inputs; }) mkIso mkSystem;
|
||||||
#supportedSystems = ["x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin"];
|
#supportedSystems = ["x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin"];
|
||||||
supportedSystems = [ "x86_64-linux" ];
|
supportedSystems = [ "x86_64-linux" ];
|
||||||
forAllSystems = f: genAttrs supportedSystems (system: f (import nixpkgs { inherit system; }));
|
forAllSystems = f: genAttrs supportedSystems (system: f (import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
overlays = [inputs.nim2nix.overlays.default];
|
||||||
|
}));
|
||||||
|
|
||||||
inheritFlakePkgs =
|
inheritFlakePkgs =
|
||||||
pkgs: flakes:
|
pkgs: flakes:
|
||||||
|
@ -27,6 +30,7 @@ let
|
||||||
pkgs:
|
pkgs:
|
||||||
rec {
|
rec {
|
||||||
default = oizys-cli;
|
default = oizys-cli;
|
||||||
|
oizys-nim = pkgs.callPackage ../pkgs/oizys-nim { };
|
||||||
oizys-cli = pkgs.callPackage ../pkgs/oizys { };
|
oizys-cli = pkgs.callPackage ../pkgs/oizys { };
|
||||||
iso = mkIso.config.system.build.isoImage;
|
iso = mkIso.config.system.build.isoImage;
|
||||||
roc = (pkgsFromSystem pkgs.system "roc").full;
|
roc = (pkgsFromSystem pkgs.system "roc").full;
|
||||||
|
@ -39,10 +43,11 @@ let
|
||||||
);
|
);
|
||||||
|
|
||||||
devShells = forAllSystems (pkgs: {
|
devShells = forAllSystems (pkgs: {
|
||||||
default = pkgs.mkShell {
|
oizys = pkgs.mkShell {
|
||||||
packages = with pkgs; [
|
packages = with pkgs; [
|
||||||
git
|
openssl
|
||||||
deadnix
|
nim
|
||||||
|
nimble
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
1
pkgs/oizys-nim/.envrc
Normal file
1
pkgs/oizys-nim/.envrc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
use flake "../..#oizys"
|
9
pkgs/oizys-nim/.gitignore
vendored
Normal file
9
pkgs/oizys-nim/.gitignore
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
nimbledeps
|
||||||
|
nimble.paths
|
||||||
|
nimble.develop
|
||||||
|
|
||||||
|
# binaries
|
||||||
|
oizys*
|
||||||
|
!src/oizys
|
||||||
|
!src/oizys.nim*
|
||||||
|
oizys.out
|
8
pkgs/oizys-nim/config.nims
Normal file
8
pkgs/oizys-nim/config.nims
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
task build, "build oizys":
|
||||||
|
selfExec "c -o:oizys src/oizys.nim"
|
||||||
|
|
||||||
|
# begin Nimble config (version 2)
|
||||||
|
--noNimblePath
|
||||||
|
when withDir(thisDir(), system.fileExists("nimble.paths")):
|
||||||
|
include "nimble.paths"
|
||||||
|
# end Nimble config
|
12
pkgs/oizys-nim/default.nix
Normal file
12
pkgs/oizys-nim/default.nix
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
lib,
|
||||||
|
openssl,
|
||||||
|
buildNimblePackage,
|
||||||
|
}:
|
||||||
|
buildNimblePackage {
|
||||||
|
name = "oizys";
|
||||||
|
verions = "unstable";
|
||||||
|
src = lib.cleanSource ./.;
|
||||||
|
nativeBuildInputs = [ openssl ];
|
||||||
|
nimbleDepsHash = "sha256-Eheeve4MbB3v1oVj0mB36Mv2Q3vJGLEbbShds1af23g=";
|
||||||
|
}
|
36
pkgs/oizys-nim/nimble.lock
Normal file
36
pkgs/oizys-nim/nimble.lock
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"packages": {
|
||||||
|
"bbansi": {
|
||||||
|
"version": "0.1.1",
|
||||||
|
"vcsRevision": "9a85d9ed028f06f1ed1ee6851480a51408a6004e",
|
||||||
|
"url": "https://github.com/daylinmorgan/bbansi",
|
||||||
|
"downloadMethod": "git",
|
||||||
|
"dependencies": [],
|
||||||
|
"checksums": {
|
||||||
|
"sha1": "b338433f9a7a1b788b7583674c2b14096ced29ee"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cligen": {
|
||||||
|
"version": "1.7.0",
|
||||||
|
"vcsRevision": "4193f802796f15559c81c6dd56724d6f20345917",
|
||||||
|
"url": "https://github.com/c-blake/cligen.git",
|
||||||
|
"downloadMethod": "git",
|
||||||
|
"dependencies": [],
|
||||||
|
"checksums": {
|
||||||
|
"sha1": "300bd7fdb6e48d2d98e34ed0661206b50331e99c"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"jsony": {
|
||||||
|
"version": "1.1.5",
|
||||||
|
"vcsRevision": "ea811bec7fa50f5abd3088ba94cda74285e93f18",
|
||||||
|
"url": "https://github.com/treeform/jsony",
|
||||||
|
"downloadMethod": "git",
|
||||||
|
"dependencies": [],
|
||||||
|
"checksums": {
|
||||||
|
"sha1": "6aeb83e7481ca8686396a568096054bc668294df"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tasks": {}
|
||||||
|
}
|
16
pkgs/oizys-nim/oizys.nimble
Normal file
16
pkgs/oizys-nim/oizys.nimble
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# Package
|
||||||
|
|
||||||
|
version = "0.1.0"
|
||||||
|
author = "Daylin Morgan"
|
||||||
|
description = "nix begat oizys"
|
||||||
|
license = "MIT"
|
||||||
|
srcDir = "src"
|
||||||
|
bin = @["oizys"]
|
||||||
|
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
|
||||||
|
requires "nim >= 2.0.8"
|
||||||
|
requires "cligen"
|
||||||
|
requires "jsony"
|
||||||
|
requires "https://github.com/daylinmorgan/bbansi#9a85d9e"
|
128
pkgs/oizys-nim/src/oizys.nim
Normal file
128
pkgs/oizys-nim/src/oizys.nim
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
## nix begat oizys
|
||||||
|
import std/[os, tables, sequtils, strformat,]
|
||||||
|
|
||||||
|
import cligen, bbansi
|
||||||
|
import oizys/[context, github, nix, overlay, logging]
|
||||||
|
|
||||||
|
|
||||||
|
addHandler(
|
||||||
|
newFancyConsoleLogger(
|
||||||
|
levelThreshold=lvlAll,
|
||||||
|
useStderr = true,
|
||||||
|
fmtPrefix = $bb"[b magenta]oizys"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
overlay:
|
||||||
|
proc pre(
|
||||||
|
flake: string = "",
|
||||||
|
host: seq[string] = @[],
|
||||||
|
debug: bool = false,
|
||||||
|
resetCache: bool = false,
|
||||||
|
rest: seq[string],
|
||||||
|
) =
|
||||||
|
if not debug: setLogFilter(lvlInfo)
|
||||||
|
updateContext(host, flake, debug, resetCache)
|
||||||
|
|
||||||
|
proc dry(minimal: bool = false) =
|
||||||
|
## dry run build
|
||||||
|
nixBuildHostDry(minimal, rest)
|
||||||
|
|
||||||
|
proc output(yes: bool = false) =
|
||||||
|
## output
|
||||||
|
echo nixosConfigAttrs().join(" ")
|
||||||
|
|
||||||
|
proc update(yes: bool = false) =
|
||||||
|
## *TBD* update and run nixos-rebuild
|
||||||
|
fatal "not implemented"
|
||||||
|
|
||||||
|
proc build(minimal: bool = false) =
|
||||||
|
## nix build
|
||||||
|
nixBuild(minimal, rest)
|
||||||
|
|
||||||
|
proc cache(name: string = "daylin") =
|
||||||
|
## *TBD* build and push to cachix
|
||||||
|
fatal "not implemented"
|
||||||
|
|
||||||
|
proc osCmd() =
|
||||||
|
## nixos-rebuild
|
||||||
|
if len(rest) == 0: quit "please provide subcmd"
|
||||||
|
let subcmd = rest[0]
|
||||||
|
if subcmd notin nixosSubcmds:
|
||||||
|
error (
|
||||||
|
&"unknown nixos-rebuild subcmd: {subcmd}\nexpected one of: \n" &
|
||||||
|
nixosSubcmds.mapIt(" " & it).join("\n")
|
||||||
|
)
|
||||||
|
quit QuitFailure
|
||||||
|
nixosRebuild(subcmd, rest[1..^1])
|
||||||
|
|
||||||
|
proc ci(`ref`: string = "main") =
|
||||||
|
## *TBD* trigger GHA update flow
|
||||||
|
if rest.len == 0:
|
||||||
|
fatal "expected workflow file name"; quit QuitFailure
|
||||||
|
createDispatch(rest[0], `ref`)
|
||||||
|
|
||||||
|
proc checkExes() =
|
||||||
|
if findExe("nix") == "":
|
||||||
|
quit("oizys requires nix", QuitFailure)
|
||||||
|
|
||||||
|
proc `//`(t1: Table[string, string], t2: Table[string, string]): Table[string, string] =
|
||||||
|
# nix style table merge
|
||||||
|
for k, v in t1.pairs(): result[k] = v
|
||||||
|
for k, v in t2.pairs(): result[k] = v
|
||||||
|
|
||||||
|
proc setupCligen() =
|
||||||
|
let isColor = getEnv("NO_COLOR") == ""
|
||||||
|
if clCfg.useMulti == "":
|
||||||
|
clCfg.useMulti =
|
||||||
|
if isColor:
|
||||||
|
"${doc}\e[1mUsage\e[m:\n $command {SUBCMD} [sub-command options & parameters]\n\n\e[1msubcommands\e[m:\n$subcmds"
|
||||||
|
else:
|
||||||
|
"${doc}Usage:\n $command {SUBCMD} [sub-command options & parameters]\n\nsubcommands:\n$subcmds"
|
||||||
|
if not isColor: return
|
||||||
|
if clCfg.helpAttr.len == 0:
|
||||||
|
clCfg.helpAttr = {"cmd": "\e[1;36m", "clDescrip": "", "clDflVal": "\e[33m",
|
||||||
|
"clOptKeys": "\e[32m", "clValType": "\e[31m", "args": "\e[3m"}.toTable()
|
||||||
|
clCfg.helpAttrOff = {"cmd": "\e[m", "clDescrip": "\e[m", "clDflVal": "\e[m",
|
||||||
|
"clOptKeys": "\e[m", "clValType": "\e[m", "args": "\e[m"}.toTable()
|
||||||
|
# clCfg.use does nothing?
|
||||||
|
clCfg.useHdr = "\e[1mUsage\e[m:\n "
|
||||||
|
|
||||||
|
when isMainModule:
|
||||||
|
checkExes()
|
||||||
|
setupCligen()
|
||||||
|
let (optOpen, optClose) =
|
||||||
|
if getEnv("NO_COLOR") == "": ("\e[1m","\e[m")
|
||||||
|
else: ("","")
|
||||||
|
let
|
||||||
|
usage = &"$command [flags]\n$doc{optOpen}Options{optClose}:\n$options"
|
||||||
|
osUsage = &"$command [subcmd] [flags]\n$doc{optOpen}Options{optClose}:\n$options"
|
||||||
|
|
||||||
|
const
|
||||||
|
sharedHelp = {
|
||||||
|
"flake" : "path/to/flake",
|
||||||
|
"host" : "host(s) to build",
|
||||||
|
"debug" : "enable debug mode",
|
||||||
|
"resetCache" : "set cache timeout to 0"
|
||||||
|
}.toTable()
|
||||||
|
updateHelp = {
|
||||||
|
"yes" : "skip all confirmation prompts"
|
||||||
|
}.toTable() // sharedHelp
|
||||||
|
ciHelp = {
|
||||||
|
"ref" : "git ref/branch/tag to trigger workflow on"
|
||||||
|
}.toTable()
|
||||||
|
cacheHelp = {
|
||||||
|
"name" : "name of cachix binary cache"
|
||||||
|
}.toTable() // sharedHelp
|
||||||
|
|
||||||
|
# setting clCfg.use wasn't working?
|
||||||
|
dispatchMulti(
|
||||||
|
[build, help = sharedHelp, usage = usage],
|
||||||
|
[cache, help = cacheHelp, usage = usage],
|
||||||
|
[ci, help = ciHelp, usage = usage],
|
||||||
|
[dry, help = sharedHelp, usage = usage],
|
||||||
|
[osCmd, help = sharedHelp, usage = osUsage, cmdName = "os"],
|
||||||
|
[output, help = sharedHelp, usage = usage],
|
||||||
|
[update, help = updateHelp, usage = usage],
|
||||||
|
)
|
||||||
|
|
1
pkgs/oizys-nim/src/oizys.nim.cfg
Normal file
1
pkgs/oizys-nim/src/oizys.nim.cfg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
-d:ssl
|
54
pkgs/oizys-nim/src/oizys/context.nim
Normal file
54
pkgs/oizys-nim/src/oizys/context.nim
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
import std/[logging, os, strformat, strutils]
|
||||||
|
from std/nativesockets import getHostname
|
||||||
|
|
||||||
|
type
|
||||||
|
OizysContext* = object
|
||||||
|
flake, host: string
|
||||||
|
hosts: seq[string]
|
||||||
|
debug: bool
|
||||||
|
ci: bool
|
||||||
|
resetCache: bool
|
||||||
|
|
||||||
|
proc initContext*(): OizysContext =
|
||||||
|
result.hosts = @[getHostname()]
|
||||||
|
result.flake = "github:daylinmorgan/oizys"
|
||||||
|
let localDir = getHomeDir() / "oizys"
|
||||||
|
if localDir.dirExists:
|
||||||
|
result.flake = localDir
|
||||||
|
let envVar = getEnv("OIZYS_DIR")
|
||||||
|
if envVar != "":
|
||||||
|
result.flake = envVar
|
||||||
|
result.ci = getEnv("GITHUB_STEP_SUMMARY") != ""
|
||||||
|
|
||||||
|
var oc = initContext()
|
||||||
|
proc checkPath(s: string): string =
|
||||||
|
## fail if path doesn't exist
|
||||||
|
if not s.dirExists:
|
||||||
|
error fmt"flake path: {s} does not exist"
|
||||||
|
quit()
|
||||||
|
s
|
||||||
|
|
||||||
|
# public api -------------------------------------
|
||||||
|
|
||||||
|
proc updateContext*(
|
||||||
|
host: seq[string],
|
||||||
|
flake: string,
|
||||||
|
debug: bool,
|
||||||
|
resetCache: bool
|
||||||
|
) =
|
||||||
|
oc.debug = debug
|
||||||
|
oc.resetCache = resetCache
|
||||||
|
if host.len > 0:
|
||||||
|
oc.hosts = host
|
||||||
|
if flake != "":
|
||||||
|
oc.flake =
|
||||||
|
if flake.startsWith("github") or flake.startsWith("git+"): flake
|
||||||
|
else: checkPath(flake.normalizedPath().absolutePath())
|
||||||
|
|
||||||
|
proc getHosts*(): seq[string] = return oc.hosts
|
||||||
|
proc getFlake*(): string = return oc.flake
|
||||||
|
proc isDebug*(): bool = return oc.debug
|
||||||
|
proc isResetCache*(): bool = return oc.resetCache
|
||||||
|
proc isCi*(): bool = return oc.ci
|
||||||
|
|
||||||
|
|
43
pkgs/oizys-nim/src/oizys/exec.nim
Normal file
43
pkgs/oizys-nim/src/oizys/exec.nim
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import std/[
|
||||||
|
osproc, strformat,
|
||||||
|
strutils, streams, logging
|
||||||
|
]
|
||||||
|
|
||||||
|
import ./spin
|
||||||
|
|
||||||
|
|
||||||
|
func addArgs*(cmd: var string, args: seq[string]) =
|
||||||
|
cmd &= " " & args.join(" ")
|
||||||
|
|
||||||
|
func addArg*(cmd: var string, arg: string) =
|
||||||
|
cmd &= " " & arg
|
||||||
|
|
||||||
|
proc runCmd*(cmd: string): int =
|
||||||
|
debug fmt"running cmd: {cmd}"
|
||||||
|
execCmd cmd
|
||||||
|
|
||||||
|
proc runCmdCapt*(cmd: string): tuple[stdout, stderr: string, exitCode: int] =
|
||||||
|
let args = cmd.splitWhitespace()
|
||||||
|
let p = startProcess(args[0], args = args[1..^1], options = {poUsePath})
|
||||||
|
result = (
|
||||||
|
readAll p.outputStream,
|
||||||
|
readAll p.errorStream,
|
||||||
|
waitForExit p
|
||||||
|
)
|
||||||
|
close p
|
||||||
|
|
||||||
|
proc runCmdCaptWithSpinner*(cmd: string, msg: string = ""): tuple[output, err: string] =
|
||||||
|
debug fmt"running command: {cmd}"
|
||||||
|
withSpinner(msg):
|
||||||
|
let (output, err, code) = runCmdCapt(cmd)
|
||||||
|
if code != 0:
|
||||||
|
stderr.writeLine("stdout\n" & output)
|
||||||
|
stderr.writeLine("stderr\n" & err)
|
||||||
|
error fmt"{cmd} had non zero exit"
|
||||||
|
quit code
|
||||||
|
return (output, err)
|
||||||
|
|
||||||
|
proc quitWithCmd*(cmd: string) =
|
||||||
|
debug cmd
|
||||||
|
quit(execCmd cmd)
|
||||||
|
|
39
pkgs/oizys-nim/src/oizys/github.nim
Normal file
39
pkgs/oizys-nim/src/oizys/github.nim
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import std/[httpclient,logging, os, strformat, strutils, json]
|
||||||
|
|
||||||
|
var ghToken = getEnv("GITHUB_TOKEN")
|
||||||
|
|
||||||
|
#[curl -L \
|
||||||
|
-X POST \
|
||||||
|
-H "Accept: application/vnd.github+json" \
|
||||||
|
-H "Authorization: Bearer <YOUR-TOKEN>" \
|
||||||
|
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||||
|
https://api.github.com/repos/OWNER/REPO/actions/workflows/WORKFLOW_ID/dispatches \
|
||||||
|
-d '{"ref":"topic-branch","inputs":{"name":"Mona the Octocat","home":"San Francisco, CA"}}'
|
||||||
|
]#
|
||||||
|
|
||||||
|
proc postGhApi(url: string, body: JsonNode) =
|
||||||
|
if ghToken == "": fatal "GITHUB_TOKEN not set"; quit QuitFailure
|
||||||
|
let client = newHttpClient()
|
||||||
|
client.headers = newHttpHeaders({
|
||||||
|
"Accept" : "application/vnd.github+json",
|
||||||
|
"Authorization" : fmt"Bearer {ghToken}",
|
||||||
|
"X-GitHub-Api-Version": "2022-11-28",
|
||||||
|
})
|
||||||
|
echo $body
|
||||||
|
let response = client.post(url, body = $body)
|
||||||
|
info fmt"Status: {response.status}"
|
||||||
|
|
||||||
|
proc createDispatch*(workflowFileName: string, `ref`: string) =
|
||||||
|
## https://docs.github.com/en/rest/actions/workflows?apiVersion=2022-11-28#create-a-workflow-dispatch-event
|
||||||
|
var workflow =
|
||||||
|
if workflowFileName.endsWith(".yml") or workflowFileName.endsWith(".yaml"): workflowFileName
|
||||||
|
else: workflowFileName & ".yml"
|
||||||
|
info fmt"creating dispatch event for {workflow}"
|
||||||
|
postGhApi(
|
||||||
|
fmt"https://api.github.com/repos/daylinmorgan/oizys/actions/workflows/{workflow}/dispatches",
|
||||||
|
%*{
|
||||||
|
"ref": `ref`,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
21
pkgs/oizys-nim/src/oizys/ignored.txt
Normal file
21
pkgs/oizys-nim/src/oizys/ignored.txt
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
ld-library-path
|
||||||
|
builder.pl
|
||||||
|
profile
|
||||||
|
system-path
|
||||||
|
nixos-help
|
||||||
|
nixos-install
|
||||||
|
nixos-version
|
||||||
|
nixos-manual-html
|
||||||
|
nixos-rebuild
|
||||||
|
nixos-configuration-reference-manpage
|
||||||
|
nixos-generate-config
|
||||||
|
nixos-enter
|
||||||
|
nixos-container
|
||||||
|
nixos-build-vms
|
||||||
|
nixos-wsl-version
|
||||||
|
nixos-wsl-welcome-message
|
||||||
|
nixos-wsl-welcome
|
||||||
|
restic-gdrive
|
||||||
|
gitea
|
||||||
|
lock
|
||||||
|
code
|
108
pkgs/oizys-nim/src/oizys/logging.nim
Normal file
108
pkgs/oizys-nim/src/oizys/logging.nim
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
import std/[logging,strutils]
|
||||||
|
export logging
|
||||||
|
|
||||||
|
import bbansi
|
||||||
|
|
||||||
|
var
|
||||||
|
handlers {.threadvar.}: seq[Logger]
|
||||||
|
|
||||||
|
#[
|
||||||
|
Level* = enum ## \
|
||||||
|
lvlAll, ## All levels active
|
||||||
|
lvlDebug, ## Debug level and above are active
|
||||||
|
lvlInfo, ## Info level and above are active
|
||||||
|
lvlNotice, ## Notice level and above are active
|
||||||
|
lvlWarn, ## Warn level and above are active
|
||||||
|
lvlError, ## Error level and above are active
|
||||||
|
lvlFatal, ## Fatal level and above are active
|
||||||
|
lvlNone ## No levels active; nothing is logged
|
||||||
|
]#
|
||||||
|
|
||||||
|
type
|
||||||
|
FancyConsoleLogger* = ref object of Logger
|
||||||
|
## A logger that writes log messages to the console.
|
||||||
|
##
|
||||||
|
## Create a new ``FancyConsoleLogger`` with the `newFancyConsoleLogger proc
|
||||||
|
## <#newConsoleLogger>`_.
|
||||||
|
##
|
||||||
|
useStderr*: bool ## If true, writes to stderr; otherwise, writes to stdout
|
||||||
|
flushThreshold*: Level ## Only messages that are at or above this
|
||||||
|
## threshold will be flushed immediately
|
||||||
|
fmtPrefix: string
|
||||||
|
fmtSep: string
|
||||||
|
fmtStrs: array[Level, string]
|
||||||
|
|
||||||
|
|
||||||
|
const defaultFlushThreshold = lvlAll
|
||||||
|
|
||||||
|
proc genFmtStr(
|
||||||
|
fmtPrefix, fmtSep, fmtSuffix, levelBb: string,
|
||||||
|
level: Level
|
||||||
|
): string =
|
||||||
|
var parts: seq[string]
|
||||||
|
if fmtPrefix != "": parts.add fmtPrefix
|
||||||
|
parts.add $LevelNames[level].bb(levelBb)
|
||||||
|
return parts.join(fmtSep) & fmtSuffix
|
||||||
|
|
||||||
|
|
||||||
|
proc newFancyConsoleLogger*(
|
||||||
|
levelThreshold = lvlAll,
|
||||||
|
fmtPrefix= "",
|
||||||
|
fmtSep = "|",
|
||||||
|
fmtSuffix ="| ",
|
||||||
|
useStderr = false,
|
||||||
|
flushThreshold = defaultFlushThreshold,
|
||||||
|
debugBb: string = "faint",
|
||||||
|
infoBb: string = "bold",
|
||||||
|
noticeBb: string = "bold",
|
||||||
|
warnBb: string = "bold yellow",
|
||||||
|
errorBb: string = "bold red",
|
||||||
|
fatalBb: string = "bold red"
|
||||||
|
): FancyConsoleLogger =
|
||||||
|
## Creates a new `ColoredConsoleLogger<#ConsoleLogger>`_.
|
||||||
|
new result
|
||||||
|
let fmtStrs: array[Level, string] = [
|
||||||
|
genFmtStr(fmtPrefix,fmtSep, fmtSuffix, "", lvlAll),
|
||||||
|
genFmtStr(fmtPrefix,fmtSep, fmtSuffix, debugBb, lvlDebug),
|
||||||
|
genFmtStr(fmtPrefix,fmtSep, fmtSuffix, infobb, lvlInfo),
|
||||||
|
genFmtStr(fmtPrefix,fmtSep, fmtSuffix, noticeBb, lvlNotice),
|
||||||
|
genFmtStr(fmtPrefix,fmtSep, fmtSuffix, warnBb, lvlWarn),
|
||||||
|
genFmtStr(fmtPrefix,fmtSep, fmtSuffix, errorBb, lvlError),
|
||||||
|
genFmtStr(fmtPrefix,fmtSep, fmtSuffix, fatalBb, lvlFatal),
|
||||||
|
genFmtStr(fmtPrefix, fmtSep, fmtSuffix, "", lvlNone)
|
||||||
|
]
|
||||||
|
result.fmtPrefix = fmtPrefix
|
||||||
|
result.fmtSep = fmtSep
|
||||||
|
result.levelThreshold = levelThreshold
|
||||||
|
result.flushThreshold = flushThreshold
|
||||||
|
result.useStderr = useStderr
|
||||||
|
result.fmtStrs = fmtStrs
|
||||||
|
|
||||||
|
|
||||||
|
method log*(logger: FancyConsoleLogger, level: Level, args: varargs[string, `$`]) {.gcsafe.} =
|
||||||
|
## Logs to the console with the given `FancyConsoleLogger<#ConsoleLogger>`_ only.
|
||||||
|
##
|
||||||
|
## This method ignores the list of registered handlers.
|
||||||
|
##
|
||||||
|
## Whether the message is logged depends on both the ConsoleLogger's
|
||||||
|
## ``levelThreshold`` field and the global log filter set using the
|
||||||
|
## `setLogFilter proc<#setLogFilter,Level>`_.
|
||||||
|
##
|
||||||
|
## **Note:** Only error and fatal messages will cause the output buffer
|
||||||
|
## to be flushed immediately by default. Set ``flushThreshold`` when creating
|
||||||
|
## the logger to change this.
|
||||||
|
|
||||||
|
if level >= logger.levelThreshold:
|
||||||
|
let ln = substituteLog(logger.fmtStrs[level], level, args)
|
||||||
|
when defined(js): {.fatal: "handler does note support JS".}
|
||||||
|
try:
|
||||||
|
let handle =
|
||||||
|
if logger.useStderr: stderr
|
||||||
|
else: stdout
|
||||||
|
writeLine(handle, ln)
|
||||||
|
if level >= logger.flushThreshold: flushFile(handle)
|
||||||
|
except IOError:
|
||||||
|
discard
|
||||||
|
|
||||||
|
proc addHandlers*(handler: Logger) =
|
||||||
|
handlers.add(handler)
|
217
pkgs/oizys-nim/src/oizys/nix.nim
Normal file
217
pkgs/oizys-nim/src/oizys/nix.nim
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
import std/[
|
||||||
|
algorithm, json,
|
||||||
|
enumerate, os, osproc, sequtils, strformat,
|
||||||
|
strutils, sugar, logging, tables
|
||||||
|
]
|
||||||
|
import bbansi, jsony
|
||||||
|
import ./[context, exec]
|
||||||
|
|
||||||
|
|
||||||
|
proc nixCommand(cmd: string): string =
|
||||||
|
result = "nix"
|
||||||
|
if isResetCache():
|
||||||
|
result.addArg "--narinfo-cache-negative-ttl 0"
|
||||||
|
result.addArg "--log-format multiline"
|
||||||
|
result.addArg cmd
|
||||||
|
|
||||||
|
proc nixosConfigAttrs*(): seq[string] =
|
||||||
|
for host in getHosts():
|
||||||
|
result.add fmt"{getFlake()}#nixosConfigurations.{host}.config.system.build.toplevel"
|
||||||
|
|
||||||
|
const nixosSubcmds* =
|
||||||
|
"""switch boot test build dry-build dry-activate edit
|
||||||
|
repl build-vm build-vm-with-bootloader list-generations""".splitWhitespace()
|
||||||
|
|
||||||
|
proc nixosRebuild*(subcmd: string, rest: seq[string] = @[]) =
|
||||||
|
var cmd = fmt"sudo nixos-rebuild {subcmd} --flake {getFlake()} --log-format multiline"
|
||||||
|
if getHosts().len > 1:
|
||||||
|
error "nixos-rebuild only supports one host"
|
||||||
|
quit QuitFailure
|
||||||
|
cmd.addArgs rest
|
||||||
|
quitWithCmd cmd
|
||||||
|
|
||||||
|
type
|
||||||
|
Derivation = object
|
||||||
|
storePath, hash, name: string
|
||||||
|
|
||||||
|
DryRunOutput = object
|
||||||
|
toBuild: seq[Derivation]
|
||||||
|
toFetch: seq[Derivation]
|
||||||
|
|
||||||
|
func toDerivation(pkg: string): Derivation =
|
||||||
|
let path = pkg.strip()
|
||||||
|
let s = path.split("-", 1)
|
||||||
|
result.storePath = path
|
||||||
|
result.hash = s[0].rsplit("/")[^1]
|
||||||
|
result.name = s[^1].replace(".drv","")
|
||||||
|
|
||||||
|
func toDerivations(lines: seq[string]): seq[Derivation] =
|
||||||
|
for pkg in lines:
|
||||||
|
result.add (toDerivation pkg)
|
||||||
|
|
||||||
|
proc cmpDrv(x, y: Derivation): int =
|
||||||
|
cmp(x.name, y.name)
|
||||||
|
|
||||||
|
proc parseDryRunOutput(err: string): DryRunOutput =
|
||||||
|
let lines = err.strip().splitLines()
|
||||||
|
let theseLines = collect:
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
if line.startswith("these"): i
|
||||||
|
|
||||||
|
case theseLines.len:
|
||||||
|
of 2:
|
||||||
|
let (firstIdx, secondIdx) = (theseLines[0], theseLines[1])
|
||||||
|
result.toBuild = lines[(firstIdx + 1) .. (secondIdx - 1)].toDerivations()
|
||||||
|
result.toFetch = lines[(secondIdx + 1) .. ^1].toDerivations()
|
||||||
|
of 1:
|
||||||
|
let idx = theseLines[0]
|
||||||
|
let line = lines[idx]
|
||||||
|
let drvs = lines[idx .. ^1].toDerivations()
|
||||||
|
if line.contains("built:"):
|
||||||
|
result.toBuild = drvs
|
||||||
|
elif line.contains("will be fetched"):
|
||||||
|
result.toFetch =drvs
|
||||||
|
else:
|
||||||
|
fatal "expected on of the lines to contain built or fetched check the output below"
|
||||||
|
stderr.writeLine err
|
||||||
|
quit()
|
||||||
|
of 0:
|
||||||
|
info "nothing to do";
|
||||||
|
quit(QuitSuccess)
|
||||||
|
else:
|
||||||
|
fatal "unexpected output from nix"
|
||||||
|
stderr.writeLine err
|
||||||
|
quit()
|
||||||
|
|
||||||
|
result.toBuild.sort(cmpDrv)
|
||||||
|
result.toFetch.sort(cmpDrv)
|
||||||
|
|
||||||
|
proc trunc(s: string, limit: int): string =
|
||||||
|
if s.len <= limit:
|
||||||
|
s
|
||||||
|
else:
|
||||||
|
s[0..(limit-4)] & "..."
|
||||||
|
|
||||||
|
proc display(msg: string, drvs: seq[Derivation]) =
|
||||||
|
echo fmt"{msg}: [bold cyan]{drvs.len()}[/]".bb
|
||||||
|
let maxLen = min(max drvs.mapIt(it.name.len), 40)
|
||||||
|
for drv in drvs:
|
||||||
|
echo " ", drv.name.trunc(maxLen).alignLeft(maxLen), " ", drv.hash.bb("faint")
|
||||||
|
|
||||||
|
proc display(output: DryRunOutput) =
|
||||||
|
if isDebug():
|
||||||
|
display("to fetch", output.toFetch)
|
||||||
|
else:
|
||||||
|
echo fmt"to fetch: [bold cyan]{output.toFetch.len()}[/]".bb
|
||||||
|
display("to build", output.toBuild)
|
||||||
|
|
||||||
|
proc toBuildNixosConfiguration(): seq[string] =
|
||||||
|
var cmd = nixCommand("build")
|
||||||
|
cmd.addArg "--dry-run"
|
||||||
|
cmd.addArgs nixosConfigAttrs()
|
||||||
|
let (_, err) = runCmdCaptWithSpinner(cmd, "running dry run build for: " & getHosts().join(" "))
|
||||||
|
let output = parseDryRunOutput err
|
||||||
|
return output.toBuild.mapIt(it.storePath)
|
||||||
|
|
||||||
|
type
|
||||||
|
NixDerivation = object
|
||||||
|
inputDrvs: Table[string, JsonNode]
|
||||||
|
name: string
|
||||||
|
|
||||||
|
proc evaluateDerivations(drvs: seq[string]): Table[string,NixDerivation] =
|
||||||
|
var cmd = "nix derivation show -r"
|
||||||
|
cmd.addArgs drvs
|
||||||
|
let (output, _) =
|
||||||
|
runCmdCaptWithSpinner(cmd, "evaluating derivations")
|
||||||
|
output.fromJson(Table[string,NixDerivation])
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: replace asserts in this proc
|
||||||
|
proc findSystemPaths(drvs: Table[string, NixDerivation]): seq[string] =
|
||||||
|
let hosts = getHosts()
|
||||||
|
let systemDrvs = collect(
|
||||||
|
for k in drvs.keys():
|
||||||
|
if k.split("-",1)[1].startswith("nixos-system-"): k
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(hosts) == len(systemDrvs)
|
||||||
|
for name in systemDrvs:
|
||||||
|
for drv in drvs[name].inputDrvs.keys():
|
||||||
|
if drv.endsWith("system-path.drv"):
|
||||||
|
result.add drv
|
||||||
|
|
||||||
|
assert len(hosts) == len(result)
|
||||||
|
|
||||||
|
func isIgnored(drv: string): bool =
|
||||||
|
const ignoredPackages = (slurp "ignored.txt").splitLines()
|
||||||
|
drv.split("-", 1)[1].replace(".drv","") in ignoredPackages
|
||||||
|
|
||||||
|
proc systemPathDrvsToBuild(): seq[string] =
|
||||||
|
let toBuild = toBuildNixosConfiguration()
|
||||||
|
let drvs = evaluateDerivations(nixosConfigAttrs())
|
||||||
|
let systemPaths = findSystemPaths(drvs)
|
||||||
|
var inputDrvs: seq[string]
|
||||||
|
for p in systemPaths:
|
||||||
|
inputDrvs &= drvs[p].inputDrvs.keys().toSeq()
|
||||||
|
result = collect(
|
||||||
|
for drv in inputDrvs:
|
||||||
|
if (drv in toBuild) and (not drv.isIgnored()):
|
||||||
|
drv & "^*"
|
||||||
|
)
|
||||||
|
|
||||||
|
func splitDrv(drv: string): tuple[name, hash:string] =
|
||||||
|
let s = drv.split("-", 1)
|
||||||
|
(s[1].replace(".drv^*",""),s[0].split("/")[^1])
|
||||||
|
|
||||||
|
proc writeDervationsToStepSummary(drvs: seq[string]) =
|
||||||
|
let rows = collect(
|
||||||
|
for drv in drvs:
|
||||||
|
let (name,hash) = splitDrv(drv)
|
||||||
|
fmt"| {name} | {hash} |"
|
||||||
|
)
|
||||||
|
let summaryFilePath = getEnv("GITHUB_STEP_SUMMARY")
|
||||||
|
if summaryFilePath == "":
|
||||||
|
fatal "no github step summary found"
|
||||||
|
quit QuitFailure
|
||||||
|
let output = open(summaryFilePath,fmAppend)
|
||||||
|
output.writeLine("| derivation | hash |\n|---|---|")
|
||||||
|
output.writeLine(rows.join("\n"))
|
||||||
|
|
||||||
|
proc nixBuild*(minimal: bool, rest: seq[string]) =
|
||||||
|
var cmd = nixCommand("build")
|
||||||
|
if minimal:
|
||||||
|
debug "populating args with derivations not built/cached"
|
||||||
|
let drvs = systemPathDrvsToBuild()
|
||||||
|
if drvs.len == 0:
|
||||||
|
info "nothing to build"
|
||||||
|
quit "exiting...", QuitSuccess
|
||||||
|
cmd.addArgs drvs
|
||||||
|
cmd.addArg "--no-link"
|
||||||
|
if isCi():
|
||||||
|
writeDervationsToStepSummary drvs
|
||||||
|
cmd.addArgs rest
|
||||||
|
quitWithCmd cmd
|
||||||
|
|
||||||
|
|
||||||
|
proc nixBuildHostDry*(minimal: bool, rest: seq[string]) =
|
||||||
|
var cmd = nixCommand("build")
|
||||||
|
if minimal:
|
||||||
|
debug "populating args with derivations not built/cached"
|
||||||
|
let drvs = systemPathDrvsToBuild()
|
||||||
|
if drvs.len == 0:
|
||||||
|
info "nothing to build"
|
||||||
|
quit "exiting...", QuitSuccess
|
||||||
|
cmd.addArgs drvs
|
||||||
|
cmd.addArg "--no-link"
|
||||||
|
if isCi():
|
||||||
|
writeDervationsToStepSummary drvs
|
||||||
|
else:
|
||||||
|
cmd.addArgs nixosConfigAttrs()
|
||||||
|
cmd.addArg "--dry-run"
|
||||||
|
cmd.addArgs rest
|
||||||
|
let (_, err) =
|
||||||
|
runCmdCaptWithSpinner(cmd, "evaluating derivation for: " & getHosts().join(" "))
|
||||||
|
let output = parseDryRunOutput err
|
||||||
|
display output
|
||||||
|
|
||||||
|
|
64
pkgs/oizys-nim/src/oizys/overlay.nim
Normal file
64
pkgs/oizys-nim/src/oizys/overlay.nim
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import std/macros
|
||||||
|
|
||||||
|
type
|
||||||
|
OverlayKind = enum
|
||||||
|
oPre
|
||||||
|
oPost
|
||||||
|
OverlayProc = object
|
||||||
|
node: NimNode
|
||||||
|
kind: OverlayKind
|
||||||
|
|
||||||
|
|
||||||
|
proc applyOverlay(child: NimNode, overlayProc: OverlayProc) =
|
||||||
|
let node = overlayProc.node
|
||||||
|
for p in node.params:
|
||||||
|
if p.kind == nnkIdentDefs:
|
||||||
|
child.params.add copyNimTree(p)
|
||||||
|
case overlayProc.kind:
|
||||||
|
of oPre:
|
||||||
|
let startIdx = if child.body[0].kind == nnkCommentStmt: 1 else: 0
|
||||||
|
for i in countdown(node.body.len()-1, 0):
|
||||||
|
child.body.insert(startIdx, copyNimTree(node.body[i]))
|
||||||
|
of oPost:
|
||||||
|
for stmt in node.body.children():
|
||||||
|
child.body.add copyNimTree(stmt)
|
||||||
|
|
||||||
|
|
||||||
|
macro overlay*(x: untyped): untyped =
|
||||||
|
##[
|
||||||
|
apply pre and post operations to procs:
|
||||||
|
```nim
|
||||||
|
overlay:
|
||||||
|
proc pre(a: bool) =
|
||||||
|
echo "before"
|
||||||
|
proc post(c: bool) =
|
||||||
|
echo "after"
|
||||||
|
proc mine(b: bool) =
|
||||||
|
echo "inside mine"
|
||||||
|
```
|
||||||
|
would result in:
|
||||||
|
```nim
|
||||||
|
proc pre(a: bool; b: bool; c: bool) =
|
||||||
|
echo "before"
|
||||||
|
echo "inside mine"
|
||||||
|
echo "after"
|
||||||
|
```
|
||||||
|
]##
|
||||||
|
result = newStmtList()
|
||||||
|
var overlays: seq[OverlayProc]
|
||||||
|
for child in x.children():
|
||||||
|
case child.kind:
|
||||||
|
of nnkProcDef:
|
||||||
|
case ($child.name):
|
||||||
|
of "pre": overlays.add OverlayProc(node: child, kind: oPre)
|
||||||
|
of "post": overlays.add OverlayProc(node: child, kind: oPost)
|
||||||
|
else: result.add child
|
||||||
|
else: result.add child
|
||||||
|
|
||||||
|
if overlays.len == 0:
|
||||||
|
error "failed to create overlays: didn't find proc pre() or proc post()"
|
||||||
|
|
||||||
|
for i, child in result.pairs():
|
||||||
|
if child.kind == nnkProcDef:
|
||||||
|
for overlay in overlays:
|
||||||
|
applyOverlay(child, overlay)
|
189
pkgs/oizys-nim/src/oizys/spin.nim
Normal file
189
pkgs/oizys-nim/src/oizys/spin.nim
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
import std/[os, locks, sequtils, terminal]
|
||||||
|
|
||||||
|
# https://github.com/molnarmark/colorize
|
||||||
|
proc reset(): string = "\e[0m"
|
||||||
|
|
||||||
|
# foreground colors
|
||||||
|
proc fgRed*(s: string): string = "\e[31m" & s & reset()
|
||||||
|
proc fgBlack*(s: string): string = "\e[30m" & s & reset()
|
||||||
|
proc fgGreen*(s: string): string = "\e[32m" & s & reset()
|
||||||
|
proc fgYellow*(s: string): string = "\e[33m" & s & reset()
|
||||||
|
proc fgBlue*(s: string): string = "\e[34m" & s & reset()
|
||||||
|
proc fgMagenta*(s: string): string = "\e[35m" & s & reset()
|
||||||
|
proc fgCyan*(s: string): string = "\e[36m" & s & reset()
|
||||||
|
proc fgLightGray*(s: string): string = "\e[37m" & s & reset()
|
||||||
|
proc fgDarkGray*(s: string): string = "\e[90m" & s & reset()
|
||||||
|
proc fgLightRed*(s: string): string = "\e[91m" & s & reset()
|
||||||
|
proc fgLightGreen*(s: string): string = "\e[92m" & s & reset()
|
||||||
|
proc fgLightYellow*(s: string): string = "\e[93m" & s & reset()
|
||||||
|
proc fgLightBlue*(s: string): string = "\e[94m" & s & reset()
|
||||||
|
proc fgLightMagenta*(s: string): string = "\e[95m" & s & reset()
|
||||||
|
proc fgLightCyan*(s: string): string = "\e[96m" & s & reset()
|
||||||
|
proc fgWhite*(s: string): string = "\e[97m" & s & reset()
|
||||||
|
|
||||||
|
# background colors
|
||||||
|
proc bgBlack*(s: string): string = "\e[40m" & s & reset()
|
||||||
|
proc bgRed*(s: string): string = "\e[41m" & s & reset()
|
||||||
|
proc bgGreen*(s: string): string = "\e[42m" & s & reset()
|
||||||
|
proc bgYellow*(s: string): string = "\e[43m" & s & reset()
|
||||||
|
proc bgBlue*(s: string): string = "\e[44m" & s & reset()
|
||||||
|
proc bgMagenta*(s: string): string = "\e[45m" & s & reset()
|
||||||
|
proc bgCyan*(s: string): string = "\e[46m" & s & reset()
|
||||||
|
proc bgLightGray*(s: string): string = "\e[47m" & s & reset()
|
||||||
|
proc bgDarkGray*(s: string): string = "\e[100m" & s & reset()
|
||||||
|
proc bgLightRed*(s: string): string = "\e[101m" & s & reset()
|
||||||
|
proc bgLightGreen*(s: string): string = "\e[102m" & s & reset()
|
||||||
|
proc bgLightYellow*(s: string): string = "\e[103m" & s & reset()
|
||||||
|
proc bgLightBlue*(s: string): string = "\e[104m" & s & reset()
|
||||||
|
proc bgLightMagenta*(s: string): string = "\e[105m" & s & reset()
|
||||||
|
proc bgLightCyan*(s: string): string = "\e[106m" & s & reset()
|
||||||
|
proc bgWhite*(s: string): string = "\e[107m" & s & reset()
|
||||||
|
|
||||||
|
# formatting functions
|
||||||
|
proc bold*(s: string): string = "\e[1m" & s & reset()
|
||||||
|
proc underline*(s: string): string = "\e[4m" & s & reset()
|
||||||
|
proc hidden*(s: string): string = "\e[8m" & s & reset()
|
||||||
|
proc invert*(s: string): string = "\e[7m" & s & reset()
|
||||||
|
|
||||||
|
type
|
||||||
|
SpinnerKind* = enum
|
||||||
|
Dots
|
||||||
|
Spinner* = object
|
||||||
|
interval*: int
|
||||||
|
frames*: seq[string]
|
||||||
|
|
||||||
|
proc makeSpinner*(interval: int, frames: seq[string]): Spinner =
|
||||||
|
Spinner(interval: interval, frames: frames)
|
||||||
|
|
||||||
|
const Spinners*: array[SpinnerKind, Spinner] = [
|
||||||
|
# Dots
|
||||||
|
Spinner(interval: 80, frames: @["⠋","⠙","⠹","⠸","⠼","⠴","⠦","⠧","⠇","⠏"]),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
type
|
||||||
|
Spinny = ref object
|
||||||
|
t: Thread[Spinny]
|
||||||
|
lock: Lock
|
||||||
|
text: string
|
||||||
|
running: bool
|
||||||
|
frames: seq[string]
|
||||||
|
frame: string
|
||||||
|
interval: int
|
||||||
|
customSymbol: bool
|
||||||
|
|
||||||
|
EventKind = enum
|
||||||
|
Stop, StopSuccess, StopError,
|
||||||
|
SymbolChange, TextChange,
|
||||||
|
|
||||||
|
SpinnyEvent = object
|
||||||
|
kind: EventKind
|
||||||
|
payload: string
|
||||||
|
|
||||||
|
var spinnyChannel: Channel[SpinnyEvent]
|
||||||
|
|
||||||
|
proc newSpinny*(text: string, s: Spinner): Spinny =
|
||||||
|
Spinny(
|
||||||
|
text: text,
|
||||||
|
running: true,
|
||||||
|
frames: s.frames,
|
||||||
|
customSymbol: false,
|
||||||
|
interval: s.interval
|
||||||
|
)
|
||||||
|
|
||||||
|
proc newSpinny*(text: string, spinType: SpinnerKind): Spinny =
|
||||||
|
newSpinny(text, Spinners[spinType])
|
||||||
|
|
||||||
|
proc setSymbolColor*(spinny: Spinny, color: proc(x: string): string) =
|
||||||
|
spinny.frames = mapIt(spinny.frames, color(it))
|
||||||
|
|
||||||
|
proc setSymbol*(spinny: Spinny, symbol: string) =
|
||||||
|
spinnyChannel.send(SpinnyEvent(kind: SymbolChange, payload: symbol))
|
||||||
|
|
||||||
|
proc setText*(spinny: Spinny, text: string) =
|
||||||
|
spinnyChannel.send(SpinnyEvent(kind: TextChange, payload: text))
|
||||||
|
|
||||||
|
proc handleEvent(spinny: Spinny, eventData: SpinnyEvent): bool =
|
||||||
|
result = true
|
||||||
|
case eventData.kind
|
||||||
|
of Stop:
|
||||||
|
result = false
|
||||||
|
of SymbolChange:
|
||||||
|
spinny.customSymbol = true
|
||||||
|
spinny.frame = eventData.payload
|
||||||
|
of TextChange:
|
||||||
|
spinny.text = eventData.payload
|
||||||
|
of StopSuccess:
|
||||||
|
spinny.customSymbol = true
|
||||||
|
spinny.frame = "✔".bold.fgGreen
|
||||||
|
spinny.text = eventData.payload.bold.fgGreen
|
||||||
|
of StopError:
|
||||||
|
spinny.customSymbol = true
|
||||||
|
spinny.frame = "✖".bold.fgRed
|
||||||
|
spinny.text = eventData.payload.bold.fgRed
|
||||||
|
|
||||||
|
proc spinnyLoop(spinny: Spinny) {.thread.} =
|
||||||
|
var frameCounter = 0
|
||||||
|
|
||||||
|
while spinny.running:
|
||||||
|
let data = spinnyChannel.tryRecv()
|
||||||
|
if data.dataAvailable:
|
||||||
|
# If we received a Stop event
|
||||||
|
if not spinny.handleEvent(data.msg):
|
||||||
|
spinnyChannel.close()
|
||||||
|
# This is required so we can reopen the same channel more than once
|
||||||
|
# See https://github.com/nim-lang/Nim/issues/6369
|
||||||
|
spinnyChannel = default(typeof(spinnyChannel))
|
||||||
|
# TODO: Do we need spinny.running at all?
|
||||||
|
spinny.running = false
|
||||||
|
break
|
||||||
|
|
||||||
|
stdout.flushFile()
|
||||||
|
if not spinny.customSymbol:
|
||||||
|
spinny.frame = spinny.frames[frameCounter]
|
||||||
|
|
||||||
|
withLock spinny.lock:
|
||||||
|
eraseLine()
|
||||||
|
stdout.write(spinny.frame & " " & spinny.text)
|
||||||
|
stdout.flushFile()
|
||||||
|
|
||||||
|
sleep(spinny.interval)
|
||||||
|
|
||||||
|
if frameCounter >= spinny.frames.len - 1:
|
||||||
|
frameCounter = 0
|
||||||
|
else:
|
||||||
|
frameCounter += 1
|
||||||
|
|
||||||
|
proc start*(spinny: Spinny) =
|
||||||
|
initLock(spinny.lock)
|
||||||
|
spinnyChannel.open()
|
||||||
|
createThread(spinny.t, spinnyLoop, spinny)
|
||||||
|
|
||||||
|
proc stop(spinny: Spinny, kind: EventKind, payload = "") =
|
||||||
|
spinnyChannel.send(SpinnyEvent(kind: kind, payload: payload))
|
||||||
|
spinnyChannel.send(SpinnyEvent(kind: Stop))
|
||||||
|
joinThread(spinny.t)
|
||||||
|
eraseLine stdout
|
||||||
|
flushFile stdout
|
||||||
|
|
||||||
|
|
||||||
|
proc stop*(spinny: Spinny) =
|
||||||
|
spinny.stop(Stop)
|
||||||
|
|
||||||
|
proc success*(spinny: Spinny, msg: string) =
|
||||||
|
spinny.stop(StopSuccess, msg)
|
||||||
|
|
||||||
|
proc error*(spinny: Spinny, msg: string) =
|
||||||
|
spinny.stop(StopError, msg)
|
||||||
|
|
||||||
|
template withSpinner*(msg: string = "", body: untyped): untyped =
|
||||||
|
var spinner {.inject.} = newSpinny(msg, Dots)
|
||||||
|
spinner.setSymbolColor(fgBlue)
|
||||||
|
start spinner
|
||||||
|
body
|
||||||
|
stop spinner
|
||||||
|
|
||||||
|
template withSpinner*(body: untyped): untyped =
|
||||||
|
withSpinner("", body)
|
||||||
|
|
||||||
|
|
8
pkgs/oizys-nim/todo.md
Normal file
8
pkgs/oizys-nim/todo.md
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# oizys-nim todo's
|
||||||
|
|
||||||
|
- [x] nix commands including dry runs
|
||||||
|
- [ ] gh api commands
|
||||||
|
- [ ] ci <- start with the easier one
|
||||||
|
- [ ] update
|
||||||
|
|
||||||
|
<!-- generated with <3 by daylinmorgan/todo -->
|
Loading…
Reference in a new issue