Compare commits

...

No commits in common. "v0.1.0-alpha4" and "main" have entirely different histories.

30 changed files with 424 additions and 100 deletions

25
.idea/jsonSchemas.xml Normal file
View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JsonSchemaMappingsProjectConfiguration">
<state>
<map>
<entry key="Woodpecker pipeline config">
<value>
<SchemaInfo>
<option name="name" value="Woodpecker pipeline config" />
<option name="relativePathToSchema" value="https://raw.githubusercontent.com/woodpecker-ci/woodpecker/master/pipeline/schema/schema.json" />
<option name="applicationDefined" value="true" />
<option name="patterns">
<list>
<Item>
<option name="path" value=".woodpecker.yml" />
</Item>
</list>
</option>
</SchemaInfo>
</value>
</entry>
</map>
</state>
</component>
</project>

View file

@ -5,6 +5,43 @@
> "A package manager and builder but like rust." > "A package manager and builder but like rust."
> -- Gandhi (2050) > -- Gandhi (2050)
> ***not even close to done*** :) > ***Almost to a point you can use it*** :)
Packager is a simple yet powerful package manager Packager is a simple yet powerful package manager
## Install a package
pkgr supports fetching packages with a:
- path
- url (http/https)
- package locator
```shell
# example with path
pkgr install ./foxy/snek.pkg
# example with url
pkgr install https://example.com/doggo.pkg
# example with package locator
pkgr install foo:stable,bar
```
## Composing a package
Right now, pkgr does not have a compose/package command. (it's in the works dw!)
You can create a package with `pkg.py`!
```shell
# Usage: pkg.py <package_toml_path> <output_path> [<directories_to_include> ...]
# example:
python pkg.py manifest.toml example.pkg root/ source/ scripts/
# this will create a package with an archive that look like this:
# [header+manifest]
# ...
# [archive:
# root/
# source/
# scripts/
# ]
```
It's not perfect, but it'll get the job done :)

View file

@ -30,12 +30,12 @@ pub fn mani_from_str(s: &str) -> Manifest<Option<PKGR>> {
fs: mani.fs, fs: mani.fs,
bin: mani.bin, bin: mani.bin,
build: mani.build, build: mani.build,
pkgr: bmani.pkgr, ext: bmani.pkgr,
} }
} }
pub fn run_bootstrap(mani: Manifest<Option<PKGR>>) -> bool { pub fn run_bootstrap(mani: Manifest<Option<PKGR>>) -> bool {
if let Some(pkgr) = mani.pkgr { if let Some(pkgr) = mani.ext {
if let Some(bootstrap) = pkgr.bootstrap { if let Some(bootstrap) = pkgr.bootstrap {
fn run_command<S: Into<String>>(s: S) -> i32 { fn run_command<S: Into<String>>(s: S) -> i32 {
std::process::Command::new("sh") std::process::Command::new("sh")

2
build
View file

@ -3,7 +3,7 @@
build_pkgr() { build_pkgr() {
cd pkgr cd pkgr
mkdir -p dist/root/{usr/bin,etc/pkgr} mkdir -p dist/root/{usr/bin,etc/pkgr}
echo -e "You can't use pkgr to update pkgr because the file will be in use while updating.\nuse bootpkg" > dist/root/etc/pkgr/YOU-CAN-NOT-USE-PKGR-TO-UPDATE-PKGR.txt echo -e "You can't use pkgr to update pkgr because the file will be in use while updating.\nuse bootpkg" > dist/root/etc/pkgr.d/YOU-CAN-NOT-USE-PKGR-TO-UPDATE-PKGR.txt
# for bin # for bin
cargo build -r cargo build -r

View file

@ -9,7 +9,9 @@ pkgr is the main tool for the packager.
Because this tool is so feature rich, it has its own [README](./pkgr/README.md). Because this tool is so feature rich, it has its own [README](./pkgr/README.md).
## bootpkg ## bootpkg
bootpkg is a tool primariliy used to bootstrap the packager. `bootpkg` is a simple pkgr variant.
It doesn't contain anything fancy and is only used to install and update pkgr.
Using it for anything other than pkgr is not recommended.
### Usage ### Usage

View file

@ -7,7 +7,7 @@ PKGFiles are an all-in-one solution to install a package.
## Overhead ## Overhead
Because pkgfiles include everything, many times you can download more than what is needed. Because pkgfiles include everything, many times you're downloading more than what is needed.
This may seem stupid but pkgfiles are *not* meant to stay on your system. This may seem stupid but pkgfiles are *not* meant to stay on your system.
## Format ## Format

View file

@ -2,9 +2,9 @@ use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PKGR {} pub struct Extension {}
impl Display for PKGR { impl Display for Extension {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "") write!(f, "")
} }

View file

@ -6,17 +6,17 @@ pub mod bin;
pub mod build; pub mod build;
pub mod fs; pub mod fs;
pub mod package; pub mod package;
pub mod pkgr; pub mod ext;
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct Manifest<P: Clone = Option<pkgr::PKGR>> { pub struct Manifest<E: Clone = Option<ext::Extension>> {
pub package: package::Package, pub package: package::Package,
pub dependencies: HashMap<String, String>, pub dependencies: HashMap<String, String>,
pub fs: fs::FS, pub fs: fs::FS,
pub bin: Option<bin::Bin>, pub bin: Option<bin::Bin>,
pub build: Option<build::Build>, pub build: Option<build::Build>,
pub pkgr: P, pub ext: E,
} }
impl<P: Clone> Manifest<P> { impl<P: Clone> Manifest<P> {
@ -50,7 +50,7 @@ impl Default for Manifest {
fs: fs::FS::default(), fs: fs::FS::default(),
bin: None, bin: None,
build: None, build: None,
pkgr: None, ext: None,
} }
} }
} }

View file

@ -84,16 +84,16 @@ install_script = "scripts/install" # relative to pkg
[build.dependencies] [build.dependencies]
base = "latest,stable" # selected by default base = "latest,stable" # selected by default
## pkgr.* ## ext.*
# packager is the official client but you may use other clients supporting the "pkgr v1 spec". # packager is the official client but you may use other clients supporting the "pkgr v1 spec".
# other clients may offer extra functionality this must be put under "pkgr.*" # other clients may offer extra functionality this must be put under "ext.*"
[pkgr] [ext]
## pkgr.bootstrap ## ext.bootstrap
# This section is used for bootpkg. An edition of packager that bootstraps the full version. # This section is used for bootpkg. An edition of packager that bootstraps the full version.
# This exists so that packager is easy to install on anything! # This exists so that packager is easy to install on anything!
# and only 1 release channel for pkgr # and only 1 release channel for pkgr
[pkgr.bootstrap] [ext.bootstrap]
## any non-zero = installed ## any non-zero = installed
check_installed_commands = [ check_installed_commands = [
"sh scripts/check_installed" "sh scripts/check_installed"

View file

@ -25,3 +25,5 @@ reqwest = { version = "0.11.18", features = ["blocking"] }
tar = "0.4.39" tar = "0.4.39"
humantime = "2.1.0" humantime = "2.1.0"
expanduser = "1.2.2" expanduser = "1.2.2"
url = { version = "2.4.0", features = ["serde"] }
dns-lookup = "2.0.3"

View file

@ -1,66 +1,32 @@
[package] [package]
name = "packager" #* name = "packager"
description = "A package installation tool" #* description = "A package installation tool"
version = 1 # this can automatically be incremented when publishing by running `pkgr publish -i ...` version = 1
tags = [ "prod", "pkgr-spec-1" ] tags = [ "prod", "pkgr-spec-1" ]
type = "application" type = "application"
arch = "x86_64"
arch = "x86_64" # this is automatically filled by `pkgr publish ...`
## dependencies
# you may use the following syntax
# "<version/tag>" or "<version/tag>,<version/tag>,..."
[dependencies] [dependencies]
## bin [bin]
# Used for systems that don't want to build pkgs. root = "/root"
[bin] # binary files root
## bin.root
# ** RELATIVE TO PACKAGE ROOT **
# bin.root specifies the root of the installed tree.
# anything in here will be overlayed on top of the system.
root = "/root" #*
## bin.checksums
# ** KEYS are relative to BIN.ROOT **
# ** VALUES are relative to PKG ROOT **
# checksums is used to perform checksums before install
# values may be paths or a uri. You may include variables.
# supported variables:
# - @name
# - @version
# - @display_version
[bin.checksums] [bin.checksums]
"/usr/bin/pkgr" = "https://ixvd.net/checksums/packager/@version" "/usr/bin/pkgr" = "https://ixvd.net/checksums/packager/@version"
## build
# Scripts will be copied to a custom root.
# After the pacakge is built, the install script is ran.
# After the install script the build directory will be deleted and an CTREE (changed tree) will be generated.
# Then the CTREE will be used to copy the files over.
[build] [build]
build_script = "scripts/build" # relative to pkg build_script = "scripts/build"
install_script = "scripts/install" # relative to pkg install_script = "scripts/install"
[build.dependencies] [build.dependencies]
## pkgr.* [ext]
# packager is the official client but you may use other clients supporting the "pkgr v1 spec".
# other clients may offer extra functionality this must be put under "pkgr.*"
[pkgr]
## pkgr.bootstrap [ext.bootstrap]
# This section is used for bootpkg. An edition of packager that bootstraps the full version.
# This exists so that packager is easy to install on anything!
# and only 1 release channel for pkgr
[pkgr.bootstrap]
## any non-zero = installed
check_installed_commands = [ check_installed_commands = [
"file /usr/bin/pkgr" "file /usr/bin/pkgr"
] ]
# any non-zero = fail
commands = [ commands = [
"cp root/usr/bin/pkgr /usr/bin/pkgr" "cp root/usr/bin/pkgr /usr/bin/pkgr"
] ]

View file

@ -0,0 +1,4 @@
[repo.main]
name = "Main"
url = "tcp://pkgs.ixvd.net:1050"

7
pkgr/skel/etc/pkgr.toml Normal file
View file

@ -0,0 +1,7 @@
build_by_default = false
tmp_dir = "/tmp/pkgr"
[storage]
repo_file = "/etc/pkgr.d/repos.toml"
data_dir = "/var/lib/pkgr/packages"
index_dir = "/var/lib/pkgr/indexes"

View file

@ -0,0 +1,20 @@
[index.main]
last_update = "1610219288"
[index.main.packager]
versions = [1, 2, 3]
tags = ["tag"]
## rust
# IndexedPackage
# - name -> packager
# - origin_repo -> main
# - versions -> [1,2,3]
# - tags -> ["tag"]
# - get_package() -> Package
## rust
# Repo
# - name -> main
# - last_update -> 1610219288
# - update_repo() -> io::Result<()>
# - searchIndex(p: PackageIdentifier) -> IndexedPackage

0
pkgr/src/api/client.rs Normal file
View file

View file

@ -1,27 +1,63 @@
use reqwest::blocking::Response; use std::collections::HashMap;
use serde;
use serde::{Deserialize, Serialize};
pub struct APITransaction<R = Response> { pub mod client;
/// Query made to API.
query: String, //TODO: enum
/// Response struct from abstracted library
_response: Option<R>
}
impl<R> APITransaction<R> { #[derive(Serialize, Deserialize)]
pub fn new(_response: Option<R>) -> Self { pub enum Query {
APITransaction { #[serde(rename = "pull")]
query: String::default(), Pull {
_response name: String,
} version: Option<u128>,
tags: Vec<String>
},
#[serde(rename = "push")]
Push {
// todo: review me pls
_data: Vec<u8>
},
#[serde(rename = "index")]
Index {
request_update: bool,
fetch: bool
} }
} }
pub struct API { #[derive(Serialize, Deserialize)]
base_url: String, pub struct Request {
version: u32,
id: String,
token: Option<String>, token: Option<String>,
history: Vec<APITransaction> query: HashMap<String, Query>
} }
impl API { #[derive(Serialize, Deserialize)]
pub struct DataErrorDetails {
actor: String,
detailed_cause: String,
recovery_options: Vec<String>,
}
#[derive(Serialize, Deserialize)]
pub struct DataError {
name: String,
cause: Option<String>,
details: Option<DataErrorDetails>
}
#[derive(Serialize, Deserialize)]
pub enum Data {
Pull {
_data: Option<Vec<u8>>,
}
}
#[derive(Serialize, Deserialize)]
pub struct Response {
version: u32,
id: String,
reply_to: String,
errors: HashMap<String, DataError>,
data: HashMap<String, Data>
} }

View file

@ -1,16 +1,17 @@
use std::process::exit; use std::process::exit;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use colored::Colorize;
use log::{debug, error, info, trace, warn}; use log::{debug, error, info, trace, warn};
use colored::Colorize;
use manifest::package::PackageType; use manifest::package::PackageType;
use crate::CONFIG;
use crate::package::identifier::PackageIdentifier; use crate::package::identifier::PackageIdentifier;
use crate::package::Package; use crate::package::Package;
use crate::package::queue::PackageQueue; use crate::package::queue::PackageQueue;
use crate::process::Process; use crate::process::Process;
use crate::types::fetch::TryFetch; use crate::types::fetch::TryFetch;
use crate::util::prompts::prompt_bool;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[clap(name = "pkgr", version)] #[clap(name = "pkgr", version)]
@ -25,6 +26,8 @@ pub enum Command {
Install { Install {
#[arg(short, long, default_value_t = false)] #[arg(short, long, default_value_t = false)]
build: bool, build: bool,
#[arg(short, long, default_value_t = false)]
ask: bool,
package_identifier: PackageIdentifier, package_identifier: PackageIdentifier,
}, },
/// Remove a package from the system /// Remove a package from the system
@ -57,6 +60,7 @@ impl Command {
match self { match self {
Command::Install { Command::Install {
build, build,
ask,
package_identifier, package_identifier,
} => { } => {
warn!("Installer does not run in isolation."); warn!("Installer does not run in isolation.");
@ -80,6 +84,15 @@ impl Command {
let mut queue = PackageQueue::new(); let mut queue = PackageQueue::new();
queue.add_package(pkg, *build); queue.add_package(pkg, *build);
trace!("Installing queue..."); trace!("Installing queue...");
{
if *ask {
info!("Install following packages?");
info!(target: "item", "{}", queue);
if !prompt_bool("Continue?", false) {
return;
}
}
}
queue.install(*build); queue.install(*build);
let end = std::time::Instant::now(); let end = std::time::Instant::now();

View file

@ -1,4 +1,8 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use storage::Storage;
mod storage;
pub mod repos;
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct Config { pub struct Config {
@ -6,13 +10,16 @@ pub struct Config {
pub build_by_default: bool, pub build_by_default: bool,
#[serde(default)] #[serde(default)]
pub tmp_dir: Option<String>, pub tmp_dir: Option<String>,
#[serde(default)]
pub storage: Storage
} }
impl Default for Config { impl Default for Config {
fn default() -> Config { fn default() -> Config {
Config { Config {
build_by_default: false, build_by_default: false,
tmp_dir: Some(String::from("/tmp/pkgr")) tmp_dir: Some(String::from("/tmp/pkgr")),
storage: Storage::default()
} }
} }
} }

37
pkgr/src/config/repos.rs Normal file
View file

@ -0,0 +1,37 @@
use std::collections::HashMap;
use std::str::FromStr;
use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Clone, Serialize, Deserialize)]
pub struct Repo {
#[serde(default)]
pub name: String,
pub uri: Url
}
impl Default for Repo {
fn default() -> Self {
Repo {
name: String::from("Repo"),
uri: Url::parse("tcp://0.0.0.0:0000").unwrap()
}
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct RepoFile {
pub repos: HashMap<String, Repo>
}
impl RepoFile {
pub fn from_path(path: &str) -> Result<RepoFile, String> {
match std::fs::read_to_string(path) {
Ok(s) => match toml::from_str(&s) {
Ok(c) => Ok(c),
Err(e) => Err(format!("failed to parse config: {}", e)),
},
Err(e) => Err(format!("failed to read config: {}", e)),
}
}
}

View file

@ -0,0 +1,24 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct Storage {
/// Where the repositories are defined.
#[serde(default)]
pub repo_file: String,
/// Where to store pkgs data
#[serde(default)]
pub data_dir: String,
/// Where to store repo indexes
#[serde(default)]
pub index_dir: String,
}
impl Default for Storage {
fn default() -> Self {
Storage {
repo_file: String::from("/etc/pkgr.d/repos.toml"),
data_dir: String::from("/var/lib/pkgr/packages"),
index_dir: String::from("/var/lib/pkgr/indexes"),
}
}
}

View file

@ -9,6 +9,8 @@ mod commands;
mod logging; mod logging;
/// Package and helpers. /// Package and helpers.
mod package; mod package;
/// Repo and helpers
mod repo;
/// Process wrapper with logging wrapper. /// Process wrapper with logging wrapper.
mod process; mod process;
/// tmpfs wrapper. /// tmpfs wrapper.

View file

@ -1,22 +1,17 @@
use std::io;
use std::fs::remove_dir_all; use std::fs::remove_dir_all;
use std::path::Path; use std::path::Path;
use std::process::exit;
use std::{io, thread};
use libc::SIGINT;
use log::{debug, error, info, trace}; use log::{debug, trace};
use errors::{BinError, BuildError, InstallError}; use errors::{BinError, BuildError, InstallError};
use manifest::Manifest; use manifest::Manifest;
use pkgfile::PKGFile; use pkgfile::PKGFile;
use crate::CONFIG; use crate::CONFIG;
use crate::package::identifier::{PackageIdentifier, PackageLocator};
use crate::package::Package;
use crate::tmpfs::TempDir; use crate::tmpfs::TempDir;
use crate::types::fetch::TryFetch; use crate::types::fetch::TryFetch;
use crate::util::fs::{copy_recursively, visit_dirs}; use crate::util::fs::copy_recursively;
use crate::util::prompts::{is_noninteractive, prompt_bool};
pub mod errors; pub mod errors;
@ -114,11 +109,17 @@ impl PackageInstaller {
} }
} }
match self.install_type { let r = match self.install_type {
InstallType::Bin => self.bin() InstallType::Bin => self.bin()
.map_err(|e| InstallError::BinError(e)), .map_err(|e| InstallError::BinError(e)),
InstallType::Build => self.build() InstallType::Build => self.build()
.map_err(|e| InstallError::BuildError(e)), .map_err(|e| InstallError::BuildError(e)),
} };
if let Err(e) = r {
return Err(e);
}
Ok(())
} }
} }

View file

@ -1,8 +1,11 @@
use std::fmt::{Display, Formatter};
use log::trace; use log::trace;
use crate::package::Package; use crate::package::Package;
pub struct PackageQueue { pub struct PackageQueue {
packages: Vec<Package> packages: Vec<Package>,
} }
impl PackageQueue { impl PackageQueue {
@ -17,7 +20,7 @@ impl PackageQueue {
for dependency in dependencies { for dependency in dependencies {
trace!("Checking package: {}", &dependency.identifier); trace!("Checking package: {}", &dependency.identifier);
if self.packages.contains(&dependency) { if self.packages.contains(&dependency) {
continue continue;
} }
trace!("Adding package: {}", &dependency.identifier); trace!("Adding package: {}", &dependency.identifier);
self.packages.push(dependency); self.packages.push(dependency);
@ -31,7 +34,18 @@ impl PackageQueue {
self.packages self.packages
.iter_mut() .iter_mut()
.for_each(|pkg| { .for_each(|pkg| {
pkg.install(build); pkg.install(build).expect("TODO: panic message");
}); });
} }
} }
impl Display for PackageQueue {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
for pkg in &self.packages {
if let Err(e) = write!(f, "{}", pkg.identifier) {
return Err(e);
}
}
write!(f, "")
}
}

View file

@ -0,0 +1,18 @@
use std::io;
use std::path::Path;
use serde::{Deserialize, Serialize};
use url::Url;
use pkgfile::PKGFile;
use crate::api::Query;
use crate::CONFIG;
use crate::package::Package;
use crate::repo::index::RepoIndex;
/// This struct solely exists for indexing and has no real functionality.
#[derive(Serialize, Deserialize)]
pub struct IndexPackage {
name: String,
versions: Vec<u32>,
tags: Vec<String>,
uri: Url
}

View file

@ -0,0 +1,58 @@
use std::collections::HashMap;
use std::io;
use std::path::Path;
use serde::{Deserialize, Serialize};
use crate::api::Request;
use crate::CONFIG;
use crate::repo::index::index_package::IndexPackage;
use crate::repo::Repo;
use crate::types::fetch::{Fetch, TryFetch};
use crate::util::create_uuid;
pub mod index_package;
#[derive(Serialize, Deserialize)]
pub struct RepoIndex {
origin_repo: String,
packages: HashMap<String, IndexPackage>
}
impl RepoIndex {
// /// Fetch existing index or create a new one.
// pub fn from_repo(repo: Repo) -> Self {
//
// }
//
// /// Create new index.
// pub fn create_with_repo(repo: Repo) -> Self {
// }
/// Get repo
pub fn get_repo(&self) -> io::Result<Repo> {
Ok(Repo::from_name(&self.origin_repo)?)
}
pub fn from_path(path: &str) -> Result<RepoIndex, String> {
match std::fs::read_to_string(path) {
Ok(s) => match toml::from_str(&s) {
Ok(c) => Ok(c),
Err(e) => Err(format!("failed to parse config: {}", e)),
},
Err(e) => Err(format!("failed to read config: {}", e)),
}
}
}
impl TryFetch<Repo> for RepoIndex {
type Error = ();
/// Fetch
fn try_fetch(query: Repo) -> Result<Self, Self::Error> {
let path = CONFIG.with(|c| c.storage.index_dir.clone()) + query.uri.as_str() + ".toml";
if Path::new(path.as_str()).exists() {
RepoIndex::from_path(path.as_str())
.map_err(|_| ())
} else {
Err(())
}
}
}

42
pkgr/src/repo/mod.rs Normal file
View file

@ -0,0 +1,42 @@
use std::io;
use std::io::{Error, ErrorKind};
use std::path::Path;
use url::Url;
use crate::CONFIG;
use crate::config::repos::RepoFile;
/// Indexed repos
pub mod index;
pub struct Repo {
name: String,
uri: Url,
}
impl Repo {
pub fn from_name(name: &String) -> io::Result<Repo> {
let r = RepoFile::from_path(CONFIG.with(|c| c.storage.repo_file.clone()).as_str())
.map_err(|e| Error::new(ErrorKind::Other, e))?;
let r = r
.repos
.get(name)
.ok_or(Error::new(ErrorKind::InvalidData, "Could not get repo"))?;
Ok(Repo {
name: r.name.clone(),
uri: r.uri.clone()
})
}
pub fn get_name(&self) -> String {
self.name.clone()
}
pub fn get_uri(&self) -> Url {
self.uri.clone()
}
/// Fetch indexed repo
pub fn get_index(&self) {
}
}

View file

@ -1,4 +1,4 @@
use std::path::{Path, PathBuf}; use std::path::PathBuf;
use crate::CONFIG; use crate::CONFIG;

View file

@ -1,6 +1,7 @@
use std::path::Path;
use std::fs::DirEntry;
use std::{fs, io}; use std::{fs, io};
use std::fs::DirEntry;
use std::path::Path;
use log::trace; use log::trace;
pub fn visit_dirs(dir: &Path, cb: &dyn Fn(&DirEntry)) -> std::io::Result<()> { pub fn visit_dirs(dir: &Path, cb: &dyn Fn(&DirEntry)) -> std::io::Result<()> {

View file

@ -1,2 +1,9 @@
pub mod prompts; pub mod prompts;
/// Helpers for fs
pub mod fs; pub mod fs;
/// Create a UUID
pub fn create_uuid() -> String {
// TODO
String::from("rand")
}

View file

@ -1,4 +1,5 @@
use std::io::Write; use std::io::Write;
use log::trace; use log::trace;
pub fn is_noninteractive() -> bool { pub fn is_noninteractive() -> bool {