feat: initial commit

Signed-off-by: Christina Sørensen <christina@cafkafk.com>
This commit is contained in:
Christina Sørensen 2024-05-21 21:08:47 +02:00
commit 0d4668f071
Signed by: cafkafk
GPG key ID: 26C542FD97F965CE
8 changed files with 1897 additions and 0 deletions

3
.envrc Normal file
View file

@ -0,0 +1,3 @@
if has nix; then
use flake .
fi

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
/config.yaml
/target
/result
.direnv/*

1385
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

12
Cargo.toml Normal file
View file

@ -0,0 +1,12 @@
[package]
name = "hilsen"
version = "0.1.0"
edition = "2021"
[dependencies]
chatgpt_rs = "1.2.3"
clap = { version = "4.5.4", features = ["cargo"] }
rand = "0.8.5"
serde = { version = "1.0.202", features = ["derive", "serde_derive"] }
serde_yaml = "0.9.34"
tokio = { version = "1.37.0", features = ["full"] }

148
flake.lock Normal file
View file

@ -0,0 +1,148 @@
{
"nodes": {
"advisory-db": {
"flake": false,
"locked": {
"lastModified": 1714183630,
"narHash": "sha256-1BVft7ggSN2XXFeXQjazU3jN9wVECd9qp2mZx/8GDMk=",
"owner": "rustsec",
"repo": "advisory-db",
"rev": "35e7459a331d3e0c585e56dabd03006b9b354088",
"type": "github"
},
"original": {
"owner": "rustsec",
"repo": "advisory-db",
"type": "github"
}
},
"crane": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1715274763,
"narHash": "sha256-3Iv1PGHJn9sV3HO4FlOVaaztOxa9uGLfOmUWrH7v7+A=",
"owner": "ipetkov",
"repo": "crane",
"rev": "27025ab71bdca30e7ed0a16c88fd74c5970fc7f5",
"type": "github"
},
"original": {
"owner": "ipetkov",
"repo": "crane",
"type": "github"
}
},
"fenix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"rust-analyzer-src": []
},
"locked": {
"lastModified": 1715581585,
"narHash": "sha256-/JjvIn1NXW3yOaDcD8Me987/QcXjo+rhg+uThasPAnI=",
"owner": "nix-community",
"repo": "fenix",
"rev": "2c4905096782e8e908205e7fa54ef987fba62793",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "fenix",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1714562304,
"narHash": "sha256-Mr3U37Rh6tH0FbaDFu0aZDwk9mPAe7ASaqDOGgLqqLU=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "bcd44e224fd68ce7d269b4f44d24c2220fd821e7",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"advisory-db": "advisory-db",
"crane": "crane",
"fenix": "fenix",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay",
"systems": "systems"
}
},
"rust-overlay": {
"inputs": {
"flake-utils": [
"flake-utils"
],
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1715652909,
"narHash": "sha256-aCLEDvzL1j51Rf2mCFOqK1mieMO3pAn5ItCIdr5h2LA=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "1d8fcbbfcfd3476c2665384a46ee9d07ef2b4dd9",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

172
flake.nix Normal file
View file

@ -0,0 +1,172 @@
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
systems.url = "github:nix-systems/default";
flake-utils = {
url = "github:numtide/flake-utils";
inputs = {
systems.follows = "systems";
};
};
crane = {
url = "github:ipetkov/crane";
inputs.nixpkgs.follows = "nixpkgs";
};
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs = {
nixpkgs.follows = "nixpkgs";
flake-utils.follows = "flake-utils";
};
};
fenix = {
url = "github:nix-community/fenix";
inputs.nixpkgs.follows = "nixpkgs";
inputs.rust-analyzer-src.follows = "";
};
advisory-db = {
url = "github:rustsec/advisory-db";
flake = false;
};
};
outputs = {
self,
flake-utils,
nixpkgs,
crane,
rust-overlay,
advisory-db,
fenix,
...
}:
flake-utils.lib.eachDefaultSystem (
system: let
overlays = [(import rust-overlay)];
pkgs = (import nixpkgs) {
inherit system overlays;
};
inherit system;
inherit (pkgs) lib;
toolchain = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
craneLib = (crane.mkLib pkgs).overrideToolchain toolchain;
src = craneLib.cleanCargoSource (craneLib.path ./.);
# Common arguments can be set here to avoid repeating them later
commonArgs = {
inherit src;
strictDeps = true;
cargoTestExtraArgs =
"-- " +
" --skip=utils::tests::parse_timestamp_h" +
" --skip=utils::tests::parse_timestamp_hm" +
" --skip=utils::tests::parse_timestamp_hms";
cargoCheckFlags = ["--show-output"];
nativeBuildInputs = with pkgs; [xclip];
buildInputs = [
# Add additional build inputs here
] ++ lib.optionals pkgs.stdenv.isDarwin [
# Additional darwin specific inputs can be set here
pkgs.libiconv
];
# Additional environment variables can be set directly
# MY_CUSTOM_VAR = "some value";
};
craneLibLLvmTools = craneLib.overrideToolchain
(fenix.packages.${system}.complete.withComponents [
"cargo"
"llvm-tools"
"rustc"
]);
# Build *just* the cargo dependencies, so we can reuse
# all of that work (e.g. via cachix) when running in CI
cargoArtifacts = craneLib.buildDepsOnly commonArgs;
# Build the actual crate itself, reusing the dependency
# artifacts from above.
hilsen = craneLib.buildPackage (commonArgs // {
inherit cargoArtifacts;
});
in {
checks = {
# Build the crate as part of `nix flake check` for convenience
inherit hilsen;
# Run clippy (and deny all warnings) on the crate source,
# again, reusing the dependency artifacts from above.
#
# Note that this is done as a separate derivation so that
# we can block the CI if there are issues here, but not
# prevent downstream consumers from building our crate by itself.
hilsen-clippy = craneLib.cargoClippy (commonArgs // {
inherit cargoArtifacts;
cargoClippyExtraArgs = "--all-targets -- --deny warnings";
});
hilsen-doc = craneLib.cargoDoc (commonArgs // {
inherit cargoArtifacts;
});
# Check formatting
hilsen-fmt = craneLib.cargoFmt {
inherit src;
};
# Audit dependencies
hilsen-audit = craneLib.cargoAudit {
inherit src advisory-db;
};
# Audit licenses
hilsen-deny = craneLib.cargoDeny {
inherit src;
};
# Run tests with cargo-nextest
# Consider setting `doCheck = false` on `my-crate` if you do not want
# the tests to run twice
hilsen-nextest = craneLib.cargoNextest (commonArgs // {
inherit cargoArtifacts;
partitions = 1;
partitionType = "count";
});
};
packages = {
default = hilsen;
} // lib.optionalAttrs (!pkgs.stdenv.isDarwin) {
hilsen-llvm-coverage = craneLibLLvmTools.cargoLlvmCov (commonArgs // {
inherit cargoArtifacts;
});
};
apps.default = flake-utils.lib.mkApp {
drv = hilsen;
};
devShells.default = craneLib.devShell {
# Inherit inputs from checks.
checks = self.checks.${system};
# Additional dev-shell environment variables can be set directly
# MY_CUSTOM_DEVELOPMENT_VAR = "something else";
# Extra inputs can be added here; cargo and rustc are provided by default.
packages = with pkgs; [
rustup
toolchain
];
};
});
}

11
rust-toolchain.toml Normal file
View file

@ -0,0 +1,11 @@
[toolchain]
channel = "nightly"
components = [
"rustfmt",
"rustc",
"rust-src",
"rust-analyzer",
"cargo",
"clippy",
]
profile = "minimal"

162
src/main.rs Normal file
View file

@ -0,0 +1,162 @@
use std::{error::Error, path::PathBuf};
mod config {
use std::{
error::Error,
fs,
path::{Path, PathBuf},
};
use rand::seq::SliceRandom;
use rand::thread_rng;
use serde::{Deserialize, Serialize};
use chatgpt::client::ChatGPT;
#[derive(Serialize, Deserialize, Default, Debug)]
pub struct Config {
#[serde(skip_serializing_if = "Option::is_none")]
gpt_api_key: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
greetings: Option<Greetings>,
}
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
struct Greetings {
#[serde(skip_serializing_if = "Option::is_none")]
morning: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
afk: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
evening: Option<Vec<String>>,
}
impl Config {
/// Loads the configuration toml from a path into the Config struct.
#[inline]
pub fn new(path: PathBuf) -> Result<Self, Box<dyn Error>> {
let yaml = fs::read_to_string(path)?;
let config = serde_yaml::from_str(&yaml)?;
Ok(config)
}
pub fn create_gpt_client(&self) -> ChatGPT {
use chatgpt::client::ChatGPT;
match ChatGPT::new(self.gpt_api_key.as_ref().unwrap()) {
Ok(client) => client,
_ => panic!(),
}
}
pub fn get_morning(&self) -> Option<String> {
if let Some(greeting) =
<std::option::Option<Greetings> as Clone>::clone(&self.greetings)
.expect("no morning greetings specified")
.morning
{
Some(greeting.choose(&mut thread_rng())?.clone())
} else {
None
}
}
}
}
mod cli {
use clap::{arg, command, crate_authors, value_parser, ArgAction, Command};
use std::path::PathBuf;
/// Parses command-line arguments using the `clap` library.
///
/// # Returns
///
/// Returns an instance of `ArgMatches` which contains the parsed arguments.
///
pub fn build_cli() -> Command {
command!()
.author(crate_authors!("\n"))
.arg(arg!(--init <path> "Init config.yaml"))
.arg(
arg!(showConfigParse: "Show parsed config.yaml as debug")
.action(ArgAction::SetTrue)
.required(false)
.long("show-config-parse"),
)
.arg(
arg!(-c --config [config] "Specify config file")
.value_parser(value_parser!(PathBuf)),
)
.arg(
arg!(-m --morning "Get random morning greeting")
.conflicts_with("afk")
.conflicts_with("evening"),
)
.arg(
arg!(-a --afk "Get random afk greeting")
.conflicts_with("morning")
.conflicts_with("evening"),
)
.arg(
arg!(-e --evening "Get random evening greeting")
.conflicts_with("afk")
.conflicts_with("morning"),
)
}
}
mod chatgpt_utils {
use chatgpt::prelude::*;
// let response = config
// .create_gpt_client()
// .send_message("Describe in five words the Rust programming language.")
// .await?;
// println!("Response: {}", response.message().content);
}
use std::process::{Command, Stdio};
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let matches = crate::cli::build_cli().get_matches();
let config;
if let Some(config_file) = matches.get_one::<PathBuf>("config") {
config = config::Config::new(config_file.clone())?;
} else {
panic!();
}
if matches.get_flag("showConfigParse") {
dbg!(&config);
}
if matches.get_flag("morning") {
// let mut clipboard = Clipboard::new().unwrap();
// clipboard.set_text(config.get_morning().unwrap()).unwrap();
// clipboard.set_text("bob".to_string()).unwrap();
//
let greeting = Command::new("echo")
.arg(&config.get_morning().unwrap())
.stdout(Stdio::piped())
.spawn()
.unwrap();
let greeting_out = greeting.stdout.unwrap();
let xclip = Command::new("xclip")
.stdin(Stdio::from(greeting_out))
.spawn()
.unwrap();
} else if matches.get_flag("evening") {
unimplemented!()
} else if matches.get_flag("afk") {
unimplemented!()
}
Ok(())
}