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
|
||||
.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 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<()> {
|
||||
for path in paths {
|
||||
|
|
@ -11,7 +11,7 @@ pub fn save_file(workspace: &Workspace, paths: &[PathBuf]) -> Result<()> {
|
|||
|
||||
let address = workspace
|
||||
.db
|
||||
.save_file(&file)
|
||||
.save_blob(&file)
|
||||
.wrap_err("saving file to database")?;
|
||||
|
||||
utils::print(format_args!("{address}\n")).wrap_err("printing output")?;
|
||||
|
|
@ -19,3 +19,23 @@ pub fn save_file(workspace: &Workspace, paths: &[PathBuf]) -> Result<()> {
|
|||
|
||||
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 color_eyre::{eyre::Context, Result};
|
||||
|
||||
use crate::workspace;
|
||||
use crate::{db::Address, workspace};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(name = "fuf")]
|
||||
|
|
@ -41,6 +41,15 @@ pub enum DbCommand {
|
|||
#[arg(required = true)]
|
||||
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<()> {
|
||||
|
|
@ -69,6 +78,8 @@ pub fn run_command(cwd: &Path, cli: Cli) -> Result<()> {
|
|||
|
||||
match command {
|
||||
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!
|
||||
use color_eyre::{eyre::Context, Result};
|
||||
use color_eyre::{
|
||||
eyre::{bail, Context},
|
||||
Result,
|
||||
};
|
||||
use std::{
|
||||
fmt::Display,
|
||||
fs::File,
|
||||
io::{self, Write},
|
||||
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()))
|
||||
}
|
||||
|
||||
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.
|
||||
/// Does *not* exit for `BrokenPipe`
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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::str::FromStr;
|
||||
use std::{fmt::Display, io::Write};
|
||||
|
||||
use cap_std::fs::Dir;
|
||||
|
|
@ -51,15 +52,16 @@ impl Db {
|
|||
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 prefix_dir = self
|
||||
.open_prefix_dir(hash)
|
||||
.wrap_err("opening prefix dir for saving file")?;
|
||||
|
||||
let suffix = &hash.0.to_hex()[2..];
|
||||
match prefix_dir.create(suffix) {
|
||||
let full_name = hash.0.to_hex();
|
||||
let full_name = full_name.as_str();
|
||||
match prefix_dir.create(full_name) {
|
||||
Ok(mut file) => {
|
||||
file.write_all(content)
|
||||
.wrap_err("writing contents of file")?;
|
||||
|
|
@ -70,7 +72,7 @@ impl Db {
|
|||
if cfg!(debug_assertions) {
|
||||
let mut existing_content = Vec::new();
|
||||
let _: usize = prefix_dir
|
||||
.open(suffix)
|
||||
.open(full_name)
|
||||
.wrap_err("opening file")?
|
||||
.read_to_end(&mut existing_content)
|
||||
.wrap_err("reading existing content")?;
|
||||
|
|
@ -86,6 +88,11 @@ impl Db {
|
|||
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> {
|
||||
let hash_string = hash.0.to_hex();
|
||||
let prefix = &hash_string[..2];
|
||||
|
|
@ -129,3 +136,11 @@ impl Display for Address {
|
|||
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;
|
||||
mod db;
|
||||
mod tree;
|
||||
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