diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d29256c..03490c86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +- `blockset get` can create directories recursively. PR [#168](https://github.com/datablockset/blockset/pull/168) - `blockset add` works with directories. PR [#165](https://github.com/datablockset/blockset/pull/165). ## 0.4.2 diff --git a/Cargo.toml b/Cargo.toml index 7ef1e93b..32328a50 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,4 +15,4 @@ io-trait = "0.10.0" io-impl = "0.10.0" io-test = "0.10.1" wasm-bindgen-test = "0.3.42" -nanvm-lib = "0.0.3" +nanvm-lib = "0.0.4" diff --git a/blockset-lib/src/app/add.rs b/blockset-lib/src/app/add.rs index f106ae2f..17b50a87 100644 --- a/blockset-lib/src/app/add.rs +++ b/blockset-lib/src/app/add.rs @@ -74,16 +74,16 @@ fn dir_to_json( fn calculate_len(files: &[(String, u64)], state: &mut State) { // JSON size: - if files.is_empty() { + state.total = if files.is_empty() { // `{}` - state.total = 2; - return; - } - // `{` + - // `"` + path + `":"` + 45 + `",` = path.len() + 51 - state.total = files.iter().fold(1, |total, (path, len)| { - total + len + (path.len() as u64) + 51 - }); + 2 + } else { + // `{` + + // `"` + path + `":"` + 45 + `",` = path.len() + 51 + files.iter().fold(1, |total, (path, len)| { + total + len + (path.len() as u64) + 51 + }) + }; } fn normalize_path(path: &str) -> &str { diff --git a/blockset-lib/src/app/get.rs b/blockset-lib/src/app/get.rs new file mode 100644 index 00000000..f412fa10 --- /dev/null +++ b/blockset-lib/src/app/get.rs @@ -0,0 +1,51 @@ +use std::io::{self, Write}; + +use io_trait::Io; +use nanvm_lib::{ + js::any::Any, + mem::manager::Manager, + parser::{parse_with_tokens, Context, ParseError, ParseResult}, + tokenizer::tokenize, +}; + +use crate::{ + cdt::node_type::NodeType, + forest::{file::FileForest, node_id::ForestNodeId, Forest}, + uint::u224::U224, +}; + +use super::invalid_input; + +pub fn restore(io: &impl Io, hash: &U224, w: &mut impl Write) -> io::Result<()> { + FileForest(io).restore(&ForestNodeId::new(NodeType::Root, hash), w, io) +} + +fn tokenize_and_parse( + io: &impl Io, + manager: M, + s: String, +) -> Result, ParseError> { + parse_with_tokens( + &Context::new(manager, io, String::default()), + tokenize(s).into_iter(), + ) +} + +pub fn parse_json(io: &impl Io, manager: M, v: Vec) -> io::Result> { + let s = String::from_utf8(v).map_err(|_| invalid_input("Invalid UTF-8"))?; + let result = tokenize_and_parse(io, manager, s).map_err(|_| invalid_input("Invalid JSON"))?; + Ok(result.any) +} + +fn dir(path: &str) -> Option<&str> { + path.rsplit_once('/').map(|(d, _)| d) +} + +fn create_file_path_recursively(io: &T, path: &str) -> io::Result<()> { + dir(path).map_or(Ok(()), |d| io.create_dir_recursively(d)) +} + +pub fn create_file_recursively(io: &T, path: &str) -> io::Result { + create_file_path_recursively(io, path)?; + io.create(path) +} diff --git a/blockset-lib/src/app/mod.rs b/blockset-lib/src/app/mod.rs index b38f547b..85db47ac 100644 --- a/blockset-lib/src/app/mod.rs +++ b/blockset-lib/src/app/mod.rs @@ -1,14 +1,19 @@ mod add; mod add_entry; +mod get; -use std::io::{self, ErrorKind, Read, Write}; +use std::io::{self, Cursor, ErrorKind, Read, Write}; use add_entry::add_entry; use io_trait::Io; +use nanvm_lib::{ + js::{any::Any, any_cast::AnyCast, js_object::JsObjectRef, js_string::JsStringRef}, + mem::{global::GLOBAL, manager::Dealloc}, +}; use crate::{ - cdt::{main_tree::MainTreeAdd, node_type::NodeType, tree_add::TreeAdd}, + cdt::{main_tree::MainTreeAdd, tree_add::TreeAdd}, common::{ base32::{StrEx, ToBase32}, eol::ToPosixEol, @@ -16,12 +21,15 @@ use crate::{ progress::{self, Progress, State}, status_line::{mb, StatusLine}, }, - forest::{file::FileForest, node_id::ForestNodeId, tree_add::ForestTreeAdd, Forest}, + forest::{file::FileForest, tree_add::ForestTreeAdd}, info::calculate_total, uint::u224::U224, }; -use self::add::posix_path; +use self::{ + add::posix_path, + get::{create_file_recursively, parse_json, restore}, +}; fn set_progress( state: &mut StatusLine<'_, impl Io>, @@ -131,6 +139,15 @@ fn validate(a: &mut impl Iterator, stdout: &mut impl Write) -> io stdout.println(["valid: ", d.as_str()]) } +fn js_string_to_string(s: &JsStringRef) -> io::Result { + String::from_utf16(s.items()) + .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Invalid UTF-16")) +} + +fn try_move>(o: Any) -> io::Result { + o.try_move().map_err(|_| invalid_input("invalid JSON")) +} + pub fn run(io: &impl Io) -> io::Result<()> { let stdout = &mut io.stdout(); let mut a = io.args(); @@ -143,8 +160,22 @@ pub fn run(io: &impl Io) -> io::Result<()> { "get" => { let d = get_hash(&mut a)?; let path = posix_path(a.next().ok_or(invalid_input("missing file name"))?.as_str()); - let w = &mut io.create(&path)?; - FileForest(io).restore(&ForestNodeId::new(NodeType::Root, &d), w, io) + if path.ends_with('/') { + let mut buffer = Vec::default(); + let mut w = Cursor::new(&mut buffer); + restore(io, &d, &mut w)?; + let json: JsObjectRef<_> = try_move(parse_json(io, GLOBAL, buffer)?)?; + for (k, v) in json.items() { + stdout.println([ + js_string_to_string(k)?.as_str(), + ": ", + js_string_to_string(&try_move(v.clone())?)?.as_str(), + ])?; + } + Ok(()) + } else { + restore(io, &d, &mut create_file_recursively(io, &path)?) + } } "info" => stdout.println(["size: ", calculate_total(io)?.to_string().as_str(), " B."]), _ => Err(invalid_input("unknown command")), @@ -396,11 +427,46 @@ mod test { let mut a = io.args(); a.next().unwrap(); run(&mut io).unwrap(); - io.stdout.to_stdout() + let x = io.stdout.to_stdout()[..45].to_owned(); + (io, x) }; - let a = f("a"); - let b = f("a/"); + let (mut io, a) = f("a"); + let (_, b) = f("a/"); assert_eq!(a, b); f("b"); + // as a file + { + io.args = ["blockset", "get", &a, "c.json"] + .iter() + .map(|s| s.to_string()) + .collect(); + run(&mut io).unwrap(); + io.read("c.json").unwrap(); + } + // as a file to a new folder + { + io.args = ["blockset", "get", &a, "d/c.json"] + .iter() + .map(|s| s.to_string()) + .collect(); + run(&mut io).unwrap(); + io.read("c.json").unwrap(); + } + // invalid directory + { + io.args = ["blockset", "get", &a, "?/v.json"] + .iter() + .map(|s| s.to_string()) + .collect(); + run(&mut io).unwrap_err(); + } + // as a directory + { + io.args = ["blockset", "get", &a, "c/"] + .iter() + .map(|s| s.to_string()) + .collect(); + run(&mut io).unwrap(); + } } }