mirror of
https://github.com/Noratrieb/fix-und-fertig.git
synced 2026-01-14 10:45:05 +01:00
tree
This commit is contained in:
parent
382143e6da
commit
d28d923d43
9 changed files with 182 additions and 8 deletions
1
.envrc
Normal file
1
.envrc
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
use nix
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,2 +1,3 @@
|
||||||
/target
|
/target
|
||||||
.fuf
|
.fuf
|
||||||
|
/testing
|
||||||
|
|
|
||||||
3
shell.nix
Normal file
3
shell.nix
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{ pkgs ? import <nixpkgs> { } }: pkgs.mkShell {
|
||||||
|
packages = with pkgs; [ rustup tree ];
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
use color_eyre::{eyre::Context, Result};
|
use color_eyre::{eyre::Context, Result};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::{cli::utils, workspace::Workspace};
|
use crate::{cli::utils, db::Address, workspace::Workspace};
|
||||||
|
|
||||||
pub fn save_file(workspace: &Workspace, paths: &[PathBuf]) -> Result<()> {
|
pub fn save_file(workspace: &Workspace, paths: &[PathBuf]) -> Result<()> {
|
||||||
for path in paths {
|
for path in paths {
|
||||||
|
|
@ -11,7 +11,7 @@ pub fn save_file(workspace: &Workspace, paths: &[PathBuf]) -> Result<()> {
|
||||||
|
|
||||||
let address = workspace
|
let address = workspace
|
||||||
.db
|
.db
|
||||||
.save_file(&file)
|
.save_blob(&file)
|
||||||
.wrap_err("saving file to database")?;
|
.wrap_err("saving file to database")?;
|
||||||
|
|
||||||
utils::print(format_args!("{address}\n")).wrap_err("printing output")?;
|
utils::print(format_args!("{address}\n")).wrap_err("printing output")?;
|
||||||
|
|
@ -19,3 +19,23 @@ pub fn save_file(workspace: &Workspace, paths: &[PathBuf]) -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn save_tree(workspace: &Workspace, paths: &[PathBuf]) -> Result<()> {
|
||||||
|
for path in paths {
|
||||||
|
let dir = utils::dir_from_path(path)?;
|
||||||
|
let address = crate::tree::save_tree(workspace, &dir)?;
|
||||||
|
utils::print(format_args!("{address}\n")).wrap_err("printing output")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_blob(workspace: &Workspace, address: Address) -> Result<()> {
|
||||||
|
let content = workspace
|
||||||
|
.db
|
||||||
|
.read_blob(address)
|
||||||
|
.wrap_err_with(|| format!("reading blog {address}"))?;
|
||||||
|
|
||||||
|
utils::print_bytes(&content).wrap_err("printing bytes")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ use cap_std::fs::Dir;
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use color_eyre::{eyre::Context, Result};
|
use color_eyre::{eyre::Context, Result};
|
||||||
|
|
||||||
use crate::workspace;
|
use crate::{db::Address, workspace};
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
#[command(name = "fuf")]
|
#[command(name = "fuf")]
|
||||||
|
|
@ -41,6 +41,15 @@ pub enum DbCommand {
|
||||||
#[arg(required = true)]
|
#[arg(required = true)]
|
||||||
paths: Vec<PathBuf>,
|
paths: Vec<PathBuf>,
|
||||||
},
|
},
|
||||||
|
/// Save a tree (directory) into the database and get the hash.
|
||||||
|
SaveTree {
|
||||||
|
#[arg(required = true)]
|
||||||
|
paths: Vec<PathBuf>,
|
||||||
|
},
|
||||||
|
/// Reads the blob behind an address.
|
||||||
|
ReadBlob {
|
||||||
|
hash: Address,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main() -> Result<()> {
|
pub fn main() -> Result<()> {
|
||||||
|
|
@ -69,6 +78,8 @@ pub fn run_command(cwd: &Path, cli: Cli) -> Result<()> {
|
||||||
|
|
||||||
match command {
|
match command {
|
||||||
DbCommand::SaveFile { paths } => commands::db::save_file(&workspace, &paths),
|
DbCommand::SaveFile { paths } => commands::db::save_file(&workspace, &paths),
|
||||||
|
DbCommand::SaveTree { paths } => commands::db::save_tree(&workspace, &paths),
|
||||||
|
DbCommand::ReadBlob { hash: address } => commands::db::read_blob(&workspace, address),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
|
use cap_std::fs::Dir;
|
||||||
///! Utilities for the CLI functions. These should *not* be used outside of the CLI-specific code!
|
///! Utilities for the CLI functions. These should *not* be used outside of the CLI-specific code!
|
||||||
use color_eyre::{eyre::Context, Result};
|
use color_eyre::{
|
||||||
|
eyre::{bail, Context},
|
||||||
|
Result,
|
||||||
|
};
|
||||||
use std::{
|
use std::{
|
||||||
fmt::Display,
|
fmt::Display,
|
||||||
|
fs::File,
|
||||||
io::{self, Write},
|
io::{self, Write},
|
||||||
path::Path,
|
path::Path,
|
||||||
};
|
};
|
||||||
|
|
@ -11,6 +16,14 @@ pub(super) fn read_file(path: &Path) -> Result<Vec<u8>> {
|
||||||
std::fs::read(path).wrap_err_with(|| format!("trying to open {}", path.display()))
|
std::fs::read(path).wrap_err_with(|| format!("trying to open {}", path.display()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn dir_from_path(path: &Path) -> Result<Dir> {
|
||||||
|
let dir = File::open(path).wrap_err_with(|| format!("opening directory {}", path.display()))?;
|
||||||
|
if !dir.metadata()?.is_dir() {
|
||||||
|
bail!("{} is not a directory", path.display());
|
||||||
|
}
|
||||||
|
Ok(Dir::from_std_file(dir))
|
||||||
|
}
|
||||||
|
|
||||||
/// Prints the content to stdout. Handles `BrokenPipe` by ignoring the rror.
|
/// Prints the content to stdout. Handles `BrokenPipe` by ignoring the rror.
|
||||||
/// Does *not* exit for `BrokenPipe`
|
/// Does *not* exit for `BrokenPipe`
|
||||||
pub(super) fn print(content: impl Display) -> io::Result<()> {
|
pub(super) fn print(content: impl Display) -> io::Result<()> {
|
||||||
|
|
@ -21,3 +34,14 @@ pub(super) fn print(content: impl Display) -> io::Result<()> {
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Prints the bytes to stdout. Handles `BrokenPipe` by ignoring the rror.
|
||||||
|
/// Does *not* exit for `BrokenPipe`
|
||||||
|
pub(super) fn print_bytes(content: &[u8]) -> io::Result<()> {
|
||||||
|
let result = std::io::stdout().lock().write_all(content);
|
||||||
|
match result {
|
||||||
|
Ok(()) => Ok(()),
|
||||||
|
Err(e) if e.kind() == io::ErrorKind::BrokenPipe => Ok(()),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
23
src/db.rs
23
src/db.rs
|
|
@ -14,6 +14,7 @@
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use std::io::{self, Read};
|
use std::io::{self, Read};
|
||||||
|
use std::str::FromStr;
|
||||||
use std::{fmt::Display, io::Write};
|
use std::{fmt::Display, io::Write};
|
||||||
|
|
||||||
use cap_std::fs::Dir;
|
use cap_std::fs::Dir;
|
||||||
|
|
@ -51,15 +52,16 @@ impl Db {
|
||||||
Ok(Self { objects })
|
Ok(Self { objects })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save_file(&self, content: &[u8]) -> Result<Address> {
|
pub fn save_blob(&self, content: &[u8]) -> Result<Address> {
|
||||||
let hash = Address(blake3::hash(content));
|
let hash = Address(blake3::hash(content));
|
||||||
|
|
||||||
let prefix_dir = self
|
let prefix_dir = self
|
||||||
.open_prefix_dir(hash)
|
.open_prefix_dir(hash)
|
||||||
.wrap_err("opening prefix dir for saving file")?;
|
.wrap_err("opening prefix dir for saving file")?;
|
||||||
|
|
||||||
let suffix = &hash.0.to_hex()[2..];
|
let full_name = hash.0.to_hex();
|
||||||
match prefix_dir.create(suffix) {
|
let full_name = full_name.as_str();
|
||||||
|
match prefix_dir.create(full_name) {
|
||||||
Ok(mut file) => {
|
Ok(mut file) => {
|
||||||
file.write_all(content)
|
file.write_all(content)
|
||||||
.wrap_err("writing contents of file")?;
|
.wrap_err("writing contents of file")?;
|
||||||
|
|
@ -70,7 +72,7 @@ impl Db {
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
let mut existing_content = Vec::new();
|
let mut existing_content = Vec::new();
|
||||||
let _: usize = prefix_dir
|
let _: usize = prefix_dir
|
||||||
.open(suffix)
|
.open(full_name)
|
||||||
.wrap_err("opening file")?
|
.wrap_err("opening file")?
|
||||||
.read_to_end(&mut existing_content)
|
.read_to_end(&mut existing_content)
|
||||||
.wrap_err("reading existing content")?;
|
.wrap_err("reading existing content")?;
|
||||||
|
|
@ -86,6 +88,11 @@ impl Db {
|
||||||
Ok(hash)
|
Ok(hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn read_blob(&self, address: Address) -> Result<Vec<u8>> {
|
||||||
|
let prefix_dir = self.open_prefix_dir(address).wrap_err("opening prefix dir for reading file")?;
|
||||||
|
prefix_dir.read(address.0.to_hex().as_str()).wrap_err("reading file")
|
||||||
|
}
|
||||||
|
|
||||||
fn open_prefix_dir(&self, hash: Address) -> Result<Dir> {
|
fn open_prefix_dir(&self, hash: Address) -> Result<Dir> {
|
||||||
let hash_string = hash.0.to_hex();
|
let hash_string = hash.0.to_hex();
|
||||||
let prefix = &hash_string[..2];
|
let prefix = &hash_string[..2];
|
||||||
|
|
@ -129,3 +136,11 @@ impl Display for Address {
|
||||||
f.write_str(&self.0.to_hex())
|
f.write_str(&self.0.to_hex())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FromStr for Address {
|
||||||
|
type Err = blake3::HexError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> std::prelude::v1::Result<Self, Self::Err> {
|
||||||
|
blake3::Hash::from_str(s).map(Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
pub mod cli;
|
pub mod cli;
|
||||||
mod db;
|
mod db;
|
||||||
|
mod tree;
|
||||||
mod workspace;
|
mod workspace;
|
||||||
|
|
|
||||||
98
src/tree.rs
Normal file
98
src/tree.rs
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
|
use cap_std::fs::Dir;
|
||||||
|
use color_eyre::{
|
||||||
|
eyre::{bail, Context, ContextCompat},
|
||||||
|
Result,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{db::Address, workspace::Workspace};
|
||||||
|
|
||||||
|
pub(crate) struct Tree {
|
||||||
|
pub(crate) children: Vec<TreeChild>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct TreeChild {
|
||||||
|
address: Address,
|
||||||
|
name: String,
|
||||||
|
kind: TreeChildType,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) enum TreeChildType {
|
||||||
|
Dir,
|
||||||
|
File,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn save_tree(workspace: &Workspace, directory: &Dir) -> Result<Address> {
|
||||||
|
let mut tree = vec![];
|
||||||
|
|
||||||
|
for entry in directory.entries().wrap_err("reading directory")? {
|
||||||
|
let entry = entry.wrap_err("reading directory")?;
|
||||||
|
|
||||||
|
let filetype = entry.file_type().wrap_err("getting entry file type")?;
|
||||||
|
let file_name = entry.file_name();
|
||||||
|
let file_name = file_name.to_str().wrap_err("non-UTF-8 file name")?;
|
||||||
|
if file_name.contains(['\n', '\r']) {
|
||||||
|
bail!("file name {file_name} contains a newline, which is not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
match () {
|
||||||
|
() if filetype.is_dir() => {
|
||||||
|
let inner_dir = entry.open_dir().wrap_err("opening directory")?;
|
||||||
|
let inner_address = save_tree(workspace, &inner_dir)
|
||||||
|
.wrap_err_with(|| format!("saving directory {file_name}"))?;
|
||||||
|
tree.push(TreeChild {
|
||||||
|
address: inner_address,
|
||||||
|
name: file_name.to_owned(),
|
||||||
|
kind: TreeChildType::Dir,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
() if filetype.is_file() => {
|
||||||
|
let wrap_err = || format!("reading {file_name}");
|
||||||
|
let mut file = entry.open().wrap_err_with(wrap_err)?;
|
||||||
|
let mut content = vec![];
|
||||||
|
file.read_to_end(&mut content).wrap_err_with(wrap_err)?;
|
||||||
|
let address = workspace
|
||||||
|
.db
|
||||||
|
.save_blob(&content)
|
||||||
|
.wrap_err("saving file to database")?;
|
||||||
|
tree.push(TreeChild {
|
||||||
|
address,
|
||||||
|
name: file_name.to_owned(),
|
||||||
|
kind: TreeChildType::File,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
bail!("file {file_name} with type {filetype:?} is not supported")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut content = vec![];
|
||||||
|
Tree { children: tree }.serialize(&mut content);
|
||||||
|
|
||||||
|
workspace
|
||||||
|
.db
|
||||||
|
.save_blob(&content)
|
||||||
|
.wrap_err("saving tree blob")
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tree {
|
||||||
|
pub(crate) fn serialize(&self, out: &mut Vec<u8>) {
|
||||||
|
use std::io::Write;
|
||||||
|
for child in &self.children {
|
||||||
|
write!(
|
||||||
|
out,
|
||||||
|
"{}",
|
||||||
|
match child.kind {
|
||||||
|
TreeChildType::Dir => "D",
|
||||||
|
TreeChildType::File => "F",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
write!(out, " {}", child.address).unwrap();
|
||||||
|
write!(out, " {}", child.name).unwrap();
|
||||||
|
write!(out, "\n").unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue