mirror of
https://git.madhouse-project.org/algernon/iocaine.git
synced 2025-02-24 10:28:48 +01:00
Initial import
Signed-off-by: Gergely Nagy <me@gergo.csillger.hu>
This commit is contained in:
commit
f28642532d
24 changed files with 2792 additions and 0 deletions
7
.envrc
Normal file
7
.envrc
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
## SPDX-FileCopyrightText: 2025 Gergely Nagy
|
||||||
|
## SPDX-FileContributor: Gergely Nagy
|
||||||
|
##
|
||||||
|
## SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
use flake
|
||||||
|
export CARGO_HOME="$(pwd)/.cargo"
|
29
.forgejo/workflows/build.yaml
Normal file
29
.forgejo/workflows/build.yaml
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
## SPDX-FileCopyrightText: 2025 Gergely Nagy
|
||||||
|
## SPDX-FileContributor: Gergely Nagy
|
||||||
|
##
|
||||||
|
## SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
name: build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'main'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: nixos-latest
|
||||||
|
steps:
|
||||||
|
- name: checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: setup magic attic cache
|
||||||
|
uses: actions/magic-attic-cache@main
|
||||||
|
with:
|
||||||
|
ATTIC_TOKEN: ${{ secrets.ATTIC_TOKEN }}
|
||||||
|
|
||||||
|
- name: build
|
||||||
|
uses: actions/nix/build@main
|
||||||
|
with:
|
||||||
|
logs: true
|
||||||
|
package: iocaine
|
44
.forgejo/workflows/lint.yaml
Normal file
44
.forgejo/workflows/lint.yaml
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
## SPDX-FileCopyrightText: 2025 Gergely Nagy
|
||||||
|
## SPDX-FileContributor: Gergely Nagy
|
||||||
|
##
|
||||||
|
## SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
name: lint
|
||||||
|
|
||||||
|
on: [ push ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
linting:
|
||||||
|
runs-on: nixos-latest
|
||||||
|
steps:
|
||||||
|
- name: checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: setup magic attic cache
|
||||||
|
uses: actions/magic-attic-cache@main
|
||||||
|
with:
|
||||||
|
ATTIC_TOKEN: ${{ secrets.ATTIC_TOKEN }}
|
||||||
|
|
||||||
|
- name: license check
|
||||||
|
run: reuse lint
|
||||||
|
|
||||||
|
- name: formatting
|
||||||
|
uses: actions/nix/develop@main
|
||||||
|
with:
|
||||||
|
run: treefmt --fail-on-change
|
||||||
|
|
||||||
|
clippy:
|
||||||
|
runs-on: nixos-latest
|
||||||
|
steps:
|
||||||
|
- name: checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: setup magic attic cache
|
||||||
|
uses: actions/magic-attic-cache@main
|
||||||
|
with:
|
||||||
|
ATTIC_TOKEN: ${{ secrets.ATTIC_TOKEN }}
|
||||||
|
|
||||||
|
- name: clippy
|
||||||
|
uses: actions/nix/develop@main
|
||||||
|
with:
|
||||||
|
run: cargo clippy
|
10
.gitattributes
vendored
Normal file
10
.gitattributes
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
# SPDX-FileCopyrightText: 2025 Gergely Nagy
|
||||||
|
# SPDX-FileContributor: Gergely Nagy
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
# Files to ignore
|
||||||
|
/LICENSES/*.txt linguist-vendored
|
||||||
|
|
||||||
|
/Cargo.lock linguist-generated
|
||||||
|
/flake.lock linguist-generated
|
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
## SPDX-FileCopyrightText: 2025 Gergely Nagy
|
||||||
|
## SPDX-FileContributor: Gergely Nagy
|
||||||
|
##
|
||||||
|
## SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
/.cargo
|
||||||
|
/.direnv
|
||||||
|
/.pre-commit-config.yaml
|
||||||
|
/result
|
||||||
|
/target
|
1754
Cargo.lock
generated
Normal file
1754
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
46
Cargo.toml
Normal file
46
Cargo.toml
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
## SPDX-FileCopyrightText: 2025 Gergely Nagy
|
||||||
|
## SPDX-FileContributor: Gergely Nagy
|
||||||
|
##
|
||||||
|
## SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "iocaine"
|
||||||
|
version = "0.1.0-snapshot"
|
||||||
|
description = "The deadliest poison known to AI"
|
||||||
|
license = "MIT"
|
||||||
|
homepage = "https://git.madhouse-project.org/algernon/iocaine"
|
||||||
|
repository = "https://git.madhouse-project.org/algernon/iocaine"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.94"
|
||||||
|
axum = "0.8.1"
|
||||||
|
clap = { version = "4.5.23", features = ["derive"] }
|
||||||
|
console-subscriber = { version = "0.4.1", optional = true }
|
||||||
|
figment = { version = "0.10.19", features = ["toml", "env"] }
|
||||||
|
figment_file_provider_adapter = "0.1.1"
|
||||||
|
rand = "0.8.5"
|
||||||
|
rand_chacha = "0.3.1"
|
||||||
|
serde = { version = "1.0.217", features = ["derive"] }
|
||||||
|
sha2 = "0.10.8"
|
||||||
|
tokio = { version = "1.42.0", features = [
|
||||||
|
"rt-multi-thread",
|
||||||
|
"macros",
|
||||||
|
"signal",
|
||||||
|
] }
|
||||||
|
tower-http = { version = "0.6.2", features = ["trace"] }
|
||||||
|
tracing = "0.1.41"
|
||||||
|
tracing-subscriber = { version = "0.3.19", features = ["json", "env-filter"] }
|
||||||
|
xdg = "2.5.2"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
tokio-console = ["dep:console-subscriber", "tokio/tracing"]
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
strip = true
|
||||||
|
panic = "abort"
|
||||||
|
lto = true
|
||||||
|
codegen-units = 1
|
||||||
|
|
||||||
|
[lints.rust]
|
||||||
|
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tokio_unstable)'] }
|
5
LICENSES/MIT.txt
vendored
Normal file
5
LICENSES/MIT.txt
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
4
README.md
Normal file
4
README.md
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
iocaine
|
||||||
|
=======
|
||||||
|
|
||||||
|
> The deadliest poison known to AI.
|
21
REUSE.toml
Normal file
21
REUSE.toml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# SPDX-FileCopyrightText: 2025 Gergely Nagy
|
||||||
|
# SPDX-FileContributor: Gergely Nagy
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
#
|
||||||
|
version = 1
|
||||||
|
SPDX-PackageName = "iocaine"
|
||||||
|
SPDX-PackageSupplier = "Gergely Nagy"
|
||||||
|
SPDX-PackageDownloadLocation = "https://git.madhouse-project.org/algernon/iocaine"
|
||||||
|
|
||||||
|
[[annotations]]
|
||||||
|
path = ["flake.lock", "Cargo.lock"]
|
||||||
|
precedence = "aggregate"
|
||||||
|
SPDX-FileCopyrightText = "2025 Gergely Nagy"
|
||||||
|
SPDX-License-Identifier = "MIT"
|
||||||
|
|
||||||
|
[[annotations]]
|
||||||
|
path = ["README.md"]
|
||||||
|
precedence = "aggregate"
|
||||||
|
SPDX-FileCopyrightText = "2025 Gergely Nagy"
|
||||||
|
SPDX-License-Identifier = "MIT"
|
123
flake.lock
generated
Normal file
123
flake.lock
generated
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-compat": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1696426674,
|
||||||
|
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
||||||
|
"owner": "edolstra",
|
||||||
|
"repo": "flake-compat",
|
||||||
|
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "edolstra",
|
||||||
|
"repo": "flake-compat",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gitignore": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"pre-commit-hooks",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1709087332,
|
||||||
|
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "gitignore.nix",
|
||||||
|
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "gitignore.nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1736867362,
|
||||||
|
"narHash": "sha256-i/UJ5I7HoqmFMwZEH6vAvBxOrjjOJNU739lnZnhUln8=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "9c6b49aeac36e2ed73a8c472f1546f6d9cf1addc",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-24.11",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pre-commit-hooks": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-compat": "flake-compat",
|
||||||
|
"gitignore": "gitignore",
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1735882644,
|
||||||
|
"narHash": "sha256-3FZAG+pGt3OElQjesCAWeMkQ7C/nB1oTHLRQ8ceP110=",
|
||||||
|
"owner": "cachix",
|
||||||
|
"repo": "pre-commit-hooks.nix",
|
||||||
|
"rev": "a5a961387e75ae44cc20f0a57ae463da5e959656",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "cachix",
|
||||||
|
"repo": "pre-commit-hooks.nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs",
|
||||||
|
"pre-commit-hooks": "pre-commit-hooks",
|
||||||
|
"systems": "systems",
|
||||||
|
"treefmt-nix": "treefmt-nix"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "systems",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"treefmt-nix": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1736154270,
|
||||||
|
"narHash": "sha256-p2r8xhQZ3TYIEKBoiEhllKWQqWNJNoT9v64Vmg4q8Zw=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "treefmt-nix",
|
||||||
|
"rev": "13c913f5deb3a5c08bb810efd89dc8cb24dd968b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "treefmt-nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
93
flake.nix
Normal file
93
flake.nix
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
# SPDX-FileCopyrightText: 2025 Gergely Nagy
|
||||||
|
# SPDX-FileContributor: Gergely Nagy
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
{
|
||||||
|
description = "The deadliest poison known to AI";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
|
||||||
|
|
||||||
|
pre-commit-hooks = {
|
||||||
|
url = "github:cachix/pre-commit-hooks.nix";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
treefmt-nix = {
|
||||||
|
url = "github:numtide/treefmt-nix";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs =
|
||||||
|
{
|
||||||
|
self,
|
||||||
|
nixpkgs,
|
||||||
|
systems,
|
||||||
|
treefmt-nix,
|
||||||
|
...
|
||||||
|
}@inputs:
|
||||||
|
let
|
||||||
|
inherit (nixpkgs) lib;
|
||||||
|
|
||||||
|
forEachSystem =
|
||||||
|
f:
|
||||||
|
nixpkgs.lib.genAttrs (import systems) (
|
||||||
|
system:
|
||||||
|
let
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
|
||||||
|
overlays = [ self.overlays.default ];
|
||||||
|
};
|
||||||
|
in
|
||||||
|
f pkgs
|
||||||
|
);
|
||||||
|
|
||||||
|
treefmtEval = forEachSystem (pkgs: treefmt-nix.lib.evalModule pkgs ./nix/treefmt.nix);
|
||||||
|
treefmtWrapper = pkgs: treefmtEval.${pkgs.system}.config.build.wrapper;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
overlays.default = import ./nix/overlay.nix { inherit self lib; };
|
||||||
|
formatter = forEachSystem treefmtWrapper;
|
||||||
|
checks = forEachSystem (pkgs: {
|
||||||
|
formatting = treefmtEval.${pkgs.system}.config.build.check self;
|
||||||
|
pre-commit-check = inputs.pre-commit-hooks.lib.${pkgs.system}.run {
|
||||||
|
src = ./.;
|
||||||
|
hooks = import ./nix/pre-commit-check.nix {
|
||||||
|
inherit pkgs;
|
||||||
|
treefmt = treefmtWrapper pkgs;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
packages = forEachSystem (
|
||||||
|
pkgs:
|
||||||
|
(self.overlays.default pkgs pkgs)
|
||||||
|
// {
|
||||||
|
default = self.packages.${pkgs.hostPlatform.system}.iocaine;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
apps = forEachSystem (pkgs: {
|
||||||
|
iocaine = {
|
||||||
|
type = "app";
|
||||||
|
program = "${pkgs.lib.getExe pkgs.iocaine}";
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
devShells = forEachSystem (pkgs: {
|
||||||
|
default = pkgs.mkShell {
|
||||||
|
packages = with pkgs; [
|
||||||
|
clippy
|
||||||
|
reuse
|
||||||
|
rust-analyzer
|
||||||
|
];
|
||||||
|
inputsFrom = [
|
||||||
|
self.packages.${pkgs.system}.iocaine
|
||||||
|
treefmtEval.${pkgs.system}.config.build.devShell
|
||||||
|
];
|
||||||
|
shellHook =
|
||||||
|
"export RUSTFLAGS='--cfg tokio_unstable';" + self.checks.${pkgs.system}.pre-commit-check.shellHook;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
32
nix/iocaine.nix
Normal file
32
nix/iocaine.nix
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
# SPDX-FileCopyrightText: 2025 Gergely Nagy
|
||||||
|
# SPDX-FileContributor: Gergely Nagy
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
{
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
props = builtins.fromTOML (builtins.readFile ../Cargo.toml);
|
||||||
|
inherit (props.package) version;
|
||||||
|
in
|
||||||
|
pkgs.rustPlatform.buildRustPackage {
|
||||||
|
pname = "iocaine";
|
||||||
|
inherit version;
|
||||||
|
src = builtins.path {
|
||||||
|
name = "iocaine";
|
||||||
|
path = lib.cleanSource ../.;
|
||||||
|
};
|
||||||
|
cargoLock = {
|
||||||
|
lockFile = ../Cargo.lock;
|
||||||
|
};
|
||||||
|
doCheck = false;
|
||||||
|
meta = with lib; {
|
||||||
|
description = "The deadliest poison known to AI";
|
||||||
|
license = licenses.mit;
|
||||||
|
platforms = platforms.linux;
|
||||||
|
homepage = "https://git.madhouse-project.org/algernon/iocaine";
|
||||||
|
mainProgram = "iocaine";
|
||||||
|
};
|
||||||
|
}
|
7
nix/overlay.nix
Normal file
7
nix/overlay.nix
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# SPDX-FileCopyrightText: 2025 Gergely Nagy
|
||||||
|
# SPDX-FileContributor: Gergely Nagy
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
_: _final: prev: {
|
||||||
|
iocaine = prev.callPackage ./iocaine.nix { };
|
||||||
|
}
|
22
nix/pre-commit-check.nix
Normal file
22
nix/pre-commit-check.nix
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# SPDX-FileCopyrightText: 2025 Gergely Nagy
|
||||||
|
# SPDX-FileContributor: Gergely Nagy
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
{ pkgs, treefmt, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
treefmt = {
|
||||||
|
enable = true;
|
||||||
|
always_run = true;
|
||||||
|
package = treefmt;
|
||||||
|
};
|
||||||
|
reuse = {
|
||||||
|
enable = true;
|
||||||
|
name = "reuse";
|
||||||
|
description = "Run REUSE compliance tests";
|
||||||
|
entry = "${pkgs.reuse}/bin/reuse lint";
|
||||||
|
pass_filenames = false;
|
||||||
|
always_run = true;
|
||||||
|
};
|
||||||
|
}
|
28
nix/treefmt.nix
Normal file
28
nix/treefmt.nix
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
# SPDX-FileCopyrightText: 2025 Gergely Nagy
|
||||||
|
# SPDX-FileContributor: Gergely Nagy
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
_:
|
||||||
|
|
||||||
|
{
|
||||||
|
config = {
|
||||||
|
projectRootFile = "./Cargo.toml";
|
||||||
|
programs = {
|
||||||
|
nixfmt.enable = true; # nix
|
||||||
|
statix.enable = true; # nix static analysis
|
||||||
|
deadnix.enable = true; # find dead nix code
|
||||||
|
rustfmt.enable = true; # rust
|
||||||
|
taplo.enable = true; # toml
|
||||||
|
};
|
||||||
|
settings.formatter = {
|
||||||
|
taplo.options = [
|
||||||
|
"format"
|
||||||
|
"--option"
|
||||||
|
"indent_tables=true"
|
||||||
|
"--option"
|
||||||
|
"indent_entries=true"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
144
src/iocaine/app.rs
Normal file
144
src/iocaine/app.rs
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
// SPDX-FileCopyrightText: 2025 Gergely Nagy
|
||||||
|
// SPDX-FileContributor: Gergely Nagy
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use axum::{
|
||||||
|
extract::{Extension, Path},
|
||||||
|
response::Html,
|
||||||
|
routing::get,
|
||||||
|
Router,
|
||||||
|
};
|
||||||
|
use rand::{seq::SliceRandom, Rng};
|
||||||
|
|
||||||
|
use crate::iocaine::{
|
||||||
|
config::Config,
|
||||||
|
garglebargle::GargleBargle,
|
||||||
|
gobbledygook::GobbledyGook,
|
||||||
|
wurstsalat_generator_pro::{join_words, WurstsalatGeneratorPro},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Iocaine {
|
||||||
|
config: Config,
|
||||||
|
chain: WurstsalatGeneratorPro,
|
||||||
|
words: GargleBargle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iocaine {
|
||||||
|
pub fn new(config: Config) -> Result<Self> {
|
||||||
|
let mut chain = WurstsalatGeneratorPro::new();
|
||||||
|
tracing::info!("Loading the markov chain corpus");
|
||||||
|
chain.learn_from_files(&config.sources.markov)?;
|
||||||
|
tracing::info!("Corpus loaded");
|
||||||
|
|
||||||
|
tracing::info!("Loading word salad");
|
||||||
|
let words = GargleBargle::load_words(&config.sources.words)?;
|
||||||
|
tracing::info!("Word salad loaded");
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
config,
|
||||||
|
chain,
|
||||||
|
words,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(&self) -> std::result::Result<(), std::io::Error> {
|
||||||
|
let bind = &self.config.server.bind.clone();
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/", get(poison_root))
|
||||||
|
.route("/{*path}", get(poison_path))
|
||||||
|
.layer(Extension(self.clone()))
|
||||||
|
.layer(tower_http::trace::TraceLayer::new_for_http());
|
||||||
|
let listener = tokio::net::TcpListener::bind(bind).await?;
|
||||||
|
axum::serve(listener, app).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poison(iocaine: &Iocaine, headers: axum::http::HeaderMap, path: &str) -> Html<String> {
|
||||||
|
let default_host = axum::http::HeaderValue::from_static("<unknown>");
|
||||||
|
let host = headers
|
||||||
|
.get("host")
|
||||||
|
.unwrap_or(&default_host)
|
||||||
|
.to_str()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Generate paragraphs
|
||||||
|
|
||||||
|
let mut rng = GobbledyGook::for_url(format!("markov://{}/{}", host, path));
|
||||||
|
let paragraph_count = rng.gen_range(
|
||||||
|
iocaine.config.generator.markov.paragraphs.min
|
||||||
|
..=iocaine.config.generator.markov.paragraphs.max,
|
||||||
|
);
|
||||||
|
let paragraph_words: Vec<u32> = (0..paragraph_count)
|
||||||
|
.map(|_| {
|
||||||
|
rng.gen_range(
|
||||||
|
iocaine.config.generator.markov.words.min
|
||||||
|
..=iocaine.config.generator.markov.words.max,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let markov_chain = iocaine.chain.generate(rng).take(
|
||||||
|
iocaine.config.generator.markov.words.max as usize
|
||||||
|
* iocaine.config.generator.markov.paragraphs.max as usize,
|
||||||
|
);
|
||||||
|
|
||||||
|
let markov = paragraph_words
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, word_count)| {
|
||||||
|
let words = markov_chain
|
||||||
|
.clone()
|
||||||
|
.skip(i * iocaine.config.generator.markov.words.max as usize)
|
||||||
|
.take(word_count as usize);
|
||||||
|
format!("<p>{}</p>", join_words(words))
|
||||||
|
})
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
// Generate links
|
||||||
|
|
||||||
|
let mut links = String::new();
|
||||||
|
let rng = GobbledyGook::for_url(format!("titles://{}/{}", host, path));
|
||||||
|
let titles = iocaine
|
||||||
|
.chain
|
||||||
|
.generate(rng)
|
||||||
|
.take(iocaine.config.generator.links.max as usize * 5);
|
||||||
|
|
||||||
|
let mut rng = GobbledyGook::for_url(format!("links://{}/{}", host, path));
|
||||||
|
let link_count =
|
||||||
|
rng.gen_range(iocaine.config.generator.links.min..=(iocaine.config.generator.links.max));
|
||||||
|
|
||||||
|
for i in 0..link_count {
|
||||||
|
let word = iocaine.words.0.choose(&mut rng).unwrap();
|
||||||
|
|
||||||
|
let title = titles.clone().skip(i as usize * 5).take(5);
|
||||||
|
let title = join_words(title);
|
||||||
|
links.push_str(&format!("<li><a href=\"{}/\">{}</a></li>", word, title))
|
||||||
|
}
|
||||||
|
|
||||||
|
let backlink = if iocaine.config.generator.links.backlink {
|
||||||
|
"<ul><li><a href=\"../\">back</a></li></ul>"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
|
|
||||||
|
Html(format!("<!doctype html><html><head><title>{}</title></head><body>{}<p>{}</p><ul>{}</ul></body></html>", path, backlink, markov, links))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn poison_root(
|
||||||
|
headers: axum::http::HeaderMap,
|
||||||
|
Extension(iocaine): Extension<Iocaine>,
|
||||||
|
) -> Html<String> {
|
||||||
|
poison(&iocaine, headers, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn poison_path(
|
||||||
|
headers: axum::http::HeaderMap,
|
||||||
|
Extension(iocaine): Extension<Iocaine>,
|
||||||
|
Path(path): Path<String>,
|
||||||
|
) -> Html<String> {
|
||||||
|
poison(&iocaine, headers, &path)
|
||||||
|
}
|
25
src/iocaine/cli.rs
Normal file
25
src/iocaine/cli.rs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
// SPDX-FileCopyrightText: 2025 Gergely Nagy
|
||||||
|
// SPDX-FileContributor: Gergely Nagy
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
#[command(version, about)]
|
||||||
|
pub struct Args {
|
||||||
|
/// Configuration file to use.
|
||||||
|
#[arg(short = 'c', long, default_value_t = Self::xdg_config_file())]
|
||||||
|
pub config_file: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Args {
|
||||||
|
fn xdg_config_file() -> String {
|
||||||
|
xdg::BaseDirectories::with_prefix("iocaine")
|
||||||
|
.unwrap()
|
||||||
|
.place_config_file("config.toml")
|
||||||
|
.expect("cannot create configuration directory")
|
||||||
|
.display()
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
}
|
133
src/iocaine/config.rs
Normal file
133
src/iocaine/config.rs
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
// SPDX-FileCopyrightText: 2025 Gergely Nagy
|
||||||
|
// SPDX-FileContributor: Gergely Nagy
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
pub struct Config {
|
||||||
|
#[serde(default)]
|
||||||
|
pub server: ServerConfig,
|
||||||
|
pub sources: SourceConfig,
|
||||||
|
#[serde(default)]
|
||||||
|
pub generator: GeneratorConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Default)]
|
||||||
|
pub struct ServerConfig {
|
||||||
|
#[serde(default = "ServerConfig::default_bind")]
|
||||||
|
pub bind: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServerConfig {
|
||||||
|
fn default_bind() -> String {
|
||||||
|
"127.0.0.1:42069".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
pub struct SourceConfig {
|
||||||
|
pub markov: Vec<String>,
|
||||||
|
pub words: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Default)]
|
||||||
|
pub struct GeneratorConfig {
|
||||||
|
#[serde(default)]
|
||||||
|
pub markov: MarkovGeneratorConfig,
|
||||||
|
#[serde(default)]
|
||||||
|
pub links: LinkGeneratorConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Default)]
|
||||||
|
pub struct MarkovGeneratorConfig {
|
||||||
|
#[serde(default)]
|
||||||
|
pub paragraphs: MarkovParagraphsConfig,
|
||||||
|
#[serde(default)]
|
||||||
|
pub words: MarkovWordsConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
pub struct MarkovParagraphsConfig {
|
||||||
|
#[serde(default = "MarkovParagraphsConfig::default_min")]
|
||||||
|
pub min: u32,
|
||||||
|
#[serde(default = "MarkovParagraphsConfig::default_max")]
|
||||||
|
pub max: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for MarkovParagraphsConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
min: Self::default_min(),
|
||||||
|
max: Self::default_max(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MarkovParagraphsConfig {
|
||||||
|
fn default_min() -> u32 {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
fn default_max() -> u32 {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
pub struct MarkovWordsConfig {
|
||||||
|
#[serde(default = "MarkovWordsConfig::default_min")]
|
||||||
|
pub min: u32,
|
||||||
|
#[serde(default = "MarkovWordsConfig::default_max")]
|
||||||
|
pub max: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for MarkovWordsConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
min: Self::default_min(),
|
||||||
|
max: Self::default_max(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MarkovWordsConfig {
|
||||||
|
fn default_min() -> u32 {
|
||||||
|
10
|
||||||
|
}
|
||||||
|
fn default_max() -> u32 {
|
||||||
|
420
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
pub struct LinkGeneratorConfig {
|
||||||
|
#[serde(default = "LinkGeneratorConfig::default_min")]
|
||||||
|
pub min: u32,
|
||||||
|
#[serde(default = "LinkGeneratorConfig::default_max")]
|
||||||
|
pub max: u32,
|
||||||
|
#[serde(default = "LinkGeneratorConfig::default_backlink")]
|
||||||
|
pub backlink: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for LinkGeneratorConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
min: Self::default_min(),
|
||||||
|
max: Self::default_max(),
|
||||||
|
backlink: Self::default_backlink(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LinkGeneratorConfig {
|
||||||
|
fn default_min() -> u32 {
|
||||||
|
2
|
||||||
|
}
|
||||||
|
fn default_max() -> u32 {
|
||||||
|
5
|
||||||
|
}
|
||||||
|
fn default_backlink() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
20
src/iocaine/garglebargle.rs
Normal file
20
src/iocaine/garglebargle.rs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
// SPDX-FileCopyrightText: 2025 Gergely Nagy
|
||||||
|
// SPDX-FileContributor: Gergely Nagy
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{self, BufRead, BufReader};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct GargleBargle(pub Vec<String>);
|
||||||
|
|
||||||
|
impl GargleBargle {
|
||||||
|
pub fn load_words(file: &str) -> Result<Self, io::Error> {
|
||||||
|
let f = File::open(file)?;
|
||||||
|
let reader = BufReader::new(f);
|
||||||
|
|
||||||
|
#[allow(clippy::lines_filter_map_ok)]
|
||||||
|
Ok(Self(reader.lines().filter_map(|word| word.ok()).collect()))
|
||||||
|
}
|
||||||
|
}
|
22
src/iocaine/gobbledygook.rs
Normal file
22
src/iocaine/gobbledygook.rs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
// SPDX-FileCopyrightText: 2025 Gergely Nagy
|
||||||
|
// SPDX-FileContributor: Gergely Nagy
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
use rand::SeedableRng;
|
||||||
|
use rand_chacha::ChaCha20Rng;
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
|
pub struct GobbledyGook;
|
||||||
|
|
||||||
|
impl GobbledyGook {
|
||||||
|
pub fn for_url<S: AsRef<str>>(seed: S) -> ChaCha20Rng {
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
hasher.update(seed.as_ref().as_bytes());
|
||||||
|
let seed = hasher.finalize().into_iter().fold(0, |acc: u64, v| {
|
||||||
|
acc.wrapping_mul(256).wrapping_add(v as u64)
|
||||||
|
});
|
||||||
|
|
||||||
|
ChaCha20Rng::seed_from_u64(seed)
|
||||||
|
}
|
||||||
|
}
|
12
src/iocaine/mod.rs
Normal file
12
src/iocaine/mod.rs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
// SPDX-FileCopyrightText: 2025 Gergely Nagy
|
||||||
|
// SPDX-FileContributor: Gergely Nagy
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
pub mod app;
|
||||||
|
pub mod cli;
|
||||||
|
pub mod config;
|
||||||
|
|
||||||
|
pub mod garglebargle;
|
||||||
|
pub mod gobbledygook;
|
||||||
|
pub mod wurstsalat_generator_pro;
|
156
src/iocaine/wurstsalat_generator_pro.rs
Normal file
156
src/iocaine/wurstsalat_generator_pro.rs
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
// SPDX-FileCopyrightText: 2017-2024 Martin Geisler
|
||||||
|
// SPDX-FileCopyrightText: 2025 Gergely Nagy
|
||||||
|
// SPDX-FileContributor: Martin Geisler
|
||||||
|
// SPDX-FileContributor: Gergely Nagy
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// Originally based on code borrowed from https://github.com/mgeisler/lipsum
|
||||||
|
|
||||||
|
use rand::{seq::SliceRandom, Rng};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
pub type Bigram = (String, String);
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct WurstsalatGeneratorPro {
|
||||||
|
map: HashMap<Bigram, Vec<String>>,
|
||||||
|
keys: Vec<Bigram>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WurstsalatGeneratorPro {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn learn(&mut self, text: String) {
|
||||||
|
let words = text.split_whitespace().collect::<Vec<&str>>();
|
||||||
|
for window in words.windows(3) {
|
||||||
|
let (a, b, c) = (
|
||||||
|
window[0].to_string(),
|
||||||
|
window[1].to_string(),
|
||||||
|
window[2].to_string(),
|
||||||
|
);
|
||||||
|
self.map.entry((a, b)).or_default().push(c);
|
||||||
|
}
|
||||||
|
self.keys = self.map.keys().cloned().collect();
|
||||||
|
self.keys.sort_unstable();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn learn_from_files(&mut self, files: &[String]) -> Result<(), std::io::Error> {
|
||||||
|
for source in files.iter() {
|
||||||
|
let f = File::open(source)?;
|
||||||
|
let s = io::read_to_string(f)?.clone();
|
||||||
|
self.learn(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate<R: Rng>(&self, mut rng: R) -> Words<'_, R> {
|
||||||
|
let initial_bigram = if self.map.is_empty() {
|
||||||
|
("".to_string(), "".to_string())
|
||||||
|
} else {
|
||||||
|
let (a, b) = self.keys.choose(&mut rng).unwrap();
|
||||||
|
(a.to_string(), b.to_string())
|
||||||
|
};
|
||||||
|
self.iter_with_rng_from(rng, initial_bigram)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn iter_with_rng_from<R: Rng>(&self, rng: R, from: Bigram) -> Words<'_, R> {
|
||||||
|
Words {
|
||||||
|
map: &self.map,
|
||||||
|
rng,
|
||||||
|
keys: &self.keys,
|
||||||
|
state: from,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Words<'a, R: Rng> {
|
||||||
|
map: &'a HashMap<Bigram, Vec<String>>,
|
||||||
|
rng: R,
|
||||||
|
keys: &'a Vec<Bigram>,
|
||||||
|
state: Bigram,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, R: Rng> Iterator for Words<'a, R> {
|
||||||
|
type Item = String;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<String> {
|
||||||
|
if self.map.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = Some(self.state.0.clone());
|
||||||
|
|
||||||
|
while !self.map.contains_key(&self.state) {
|
||||||
|
let (a, b) = self.keys.choose(&mut self.rng).unwrap();
|
||||||
|
self.state = (a.to_string(), b.to_string());
|
||||||
|
}
|
||||||
|
let next_words = &self.map[&self.state];
|
||||||
|
let next = next_words.choose(&mut self.rng).unwrap();
|
||||||
|
self.state = (self.state.1.clone(), next.to_string());
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if `c` is an ASCII punctuation character.
|
||||||
|
fn is_ascii_punctuation(c: char) -> bool {
|
||||||
|
c.is_ascii_punctuation()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Capitalize the first character in a string.
|
||||||
|
fn capitalize(word: &str) -> String {
|
||||||
|
let idx = match word.chars().next() {
|
||||||
|
Some(c) => c.len_utf8(),
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut result = String::with_capacity(word.len());
|
||||||
|
result.push_str(&word[..idx].to_uppercase());
|
||||||
|
result.push_str(&word[idx..]);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Join words from an iterator. The first word is always capitalized
|
||||||
|
/// and the generated sentence will end with `'.'` if it doesn't
|
||||||
|
/// already end with some other ASCII punctuation character.
|
||||||
|
pub fn join_words<I: Iterator<Item = String>>(mut words: I) -> String {
|
||||||
|
match words.next() {
|
||||||
|
None => String::new(),
|
||||||
|
Some(word) => {
|
||||||
|
// Punctuation characters which ends a sentence.
|
||||||
|
let punctuation: &[char] = &['.', '!', '?'];
|
||||||
|
|
||||||
|
let mut sentence = capitalize(&word);
|
||||||
|
let mut needs_cap = sentence.ends_with(punctuation);
|
||||||
|
|
||||||
|
// Add remaining words.
|
||||||
|
for word in words {
|
||||||
|
sentence.push(' ');
|
||||||
|
|
||||||
|
if needs_cap {
|
||||||
|
sentence.push_str(&capitalize(&word));
|
||||||
|
} else {
|
||||||
|
sentence.push_str(&word);
|
||||||
|
}
|
||||||
|
|
||||||
|
needs_cap = word.ends_with(punctuation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the sentence ends with either one of ".!?".
|
||||||
|
if !sentence.ends_with(punctuation) {
|
||||||
|
// Trim all trailing punctuation characters to avoid
|
||||||
|
// adding '.' after a ',' or similar.
|
||||||
|
let idx = sentence.trim_end_matches(is_ascii_punctuation).len();
|
||||||
|
sentence.truncate(idx);
|
||||||
|
sentence.push('.');
|
||||||
|
}
|
||||||
|
|
||||||
|
sentence
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
45
src/main.rs
Normal file
45
src/main.rs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
// SPDX-FileCopyrightText: 2025 Gergely Nagy
|
||||||
|
// SPDX-FileContributor: Gergely Nagy
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use clap::Parser;
|
||||||
|
use figment::{
|
||||||
|
providers::{Env, Format, Toml},
|
||||||
|
Figment,
|
||||||
|
};
|
||||||
|
use figment_file_provider_adapter::FileAdapter;
|
||||||
|
|
||||||
|
mod iocaine;
|
||||||
|
use iocaine::{app::Iocaine, cli, config::Config};
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<()> {
|
||||||
|
#[cfg(all(feature = "tokio-console", tokio_unstable))]
|
||||||
|
console_subscriber::init();
|
||||||
|
#[cfg(all(feature = "tokio-console", not(tokio_unstable)))]
|
||||||
|
compile_error!("`tokio-console` requires manually enabling the `--cfg tokio_unstable` rust flag during compilation!");
|
||||||
|
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let args = cli::Args::parse();
|
||||||
|
tracing::debug!(config_file = &args.config_file, "loading configuration");
|
||||||
|
|
||||||
|
let config: Config = Figment::new()
|
||||||
|
.merge(FileAdapter::wrap(Env::prefixed("IOCAINE_").split("__")))
|
||||||
|
.merge(FileAdapter::wrap(Toml::file(&args.config_file)))
|
||||||
|
.extract()
|
||||||
|
.unwrap_or_else(|err| {
|
||||||
|
tracing::error!(err = format!("{}", err), "Failed to load configuration");
|
||||||
|
panic!(
|
||||||
|
"Failed to load configuration from {} (and environment variables): {}",
|
||||||
|
&args.config_file, err
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
let app = Iocaine::new(config)?;
|
||||||
|
app.run().await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in a new issue