diff --git a/packages/onenote-converter/.gitignore b/packages/onenote-converter/.gitignore index e32c5c7723..21d9049d54 100644 --- a/packages/onenote-converter/.gitignore +++ b/packages/onenote-converter/.gitignore @@ -1,5 +1,6 @@ /target /output +/test-output /.idea *.iml diff --git a/packages/onenote-converter/Cargo.toml b/packages/onenote-converter/Cargo.toml index 1996fdced3..23a8a1d613 100644 --- a/packages/onenote-converter/Cargo.toml +++ b/packages/onenote-converter/Cargo.toml @@ -39,4 +39,4 @@ features = [ ] [lib] -crate-type = ["cdylib"] \ No newline at end of file +crate-type = ["cdylib", "lib"] \ No newline at end of file diff --git a/packages/onenote-converter/README.md b/packages/onenote-converter/README.md index 2ed57c6175..7fa28a02ba 100644 --- a/packages/onenote-converter/README.md +++ b/packages/onenote-converter/README.md @@ -43,6 +43,8 @@ After this, the HTML should look the same and is ready to be imported by the Imp - package.json -> where the project is built - node_functions.js -> where the custom-made functions used inside rust goes ... + - tests -> Integration tests + ... - pkg -> artifact folder generated in the build step - onenote_converter.js -> main file ... @@ -58,7 +60,7 @@ To work with the project you will need: ### Running tests -Tests for the project are located in the `lib` packages, but to make it work it is necessary to build this project first: +Most tests for the project are located in the `lib` packages, but to make it work it is necessary to build this project first: `IS_CONTINUOUS_INTEGRATION=1 yarn build # for production build` or @@ -71,6 +73,21 @@ cd ../lib IS_CONTINUOUS_INTEGRATION=1 yarn test services/interop/InteropService_Importer_OneNote.test. ``` +Other tests are written in Rust. To run these tests, use `cargo test`: +``` +cd packages/onenote-converter +cargo test +``` + +### Debugging tests + +Suppose that the importer's Rust code is failing to parse a specific `example.one` file. In this case, it may be useful to step through part of the import process in a debugger. If using VSCode, this can be done by: +1. Adding a new test to `tests/convert.rs` that runs `convert()` on the `example.one` file. +2. Setting up Rust and Rust debugging. See [the relevant VSCode documentation](https://code.visualstudio.com/docs/languages/rust#_debugging) for details. +3. Clicking the "Debug" button for the test added in step 1. This button should be provided by extensions set up in step 2. + + + ### Developing When working with the Rust code you will probably rather run `yarn buildDev` since it is faster and it has more logging messages (they can be disabled in the macro `log!()`) diff --git a/packages/onenote-converter/assets/test-data/single-page/Open Notebook.onetoc2 b/packages/onenote-converter/assets/test-data/single-page/Open Notebook.onetoc2 new file mode 100644 index 0000000000..e541aea5fc Binary files /dev/null and b/packages/onenote-converter/assets/test-data/single-page/Open Notebook.onetoc2 differ diff --git a/packages/onenote-converter/assets/test-data/single-page/Untitled Section.one b/packages/onenote-converter/assets/test-data/single-page/Untitled Section.one new file mode 100644 index 0000000000..88fcb9732f Binary files /dev/null and b/packages/onenote-converter/assets/test-data/single-page/Untitled Section.one differ diff --git a/packages/onenote-converter/node_functions.js b/packages/onenote-converter/node_functions.js index b0bb47914c..b7df809650 100644 --- a/packages/onenote-converter/node_functions.js +++ b/packages/onenote-converter/node_functions.js @@ -23,16 +23,6 @@ function removePrefix(basePath, prefix) { return basePath.replace(prefix, ''); } -function getOutputPath(inputDir, outputDir, filePath) { - const basePathFromInputFolder = filePath.replace(inputDir, ''); - const newOutput = path.join(outputDir, basePathFromInputFolder); - return path.dirname(newOutput); -} - -function getParentDir(filePath) { - return path.basename(path.dirname(filePath)); -} - function normalizeAndWriteFile(filePath, data) { filePath = path.normalize(filePath); fs.writeFileSync(filePath, data); @@ -43,7 +33,5 @@ module.exports = { isDirectory, readDir, removePrefix, - getOutputPath, - getParentDir, normalizeAndWriteFile, }; diff --git a/packages/onenote-converter/src/lib.rs b/packages/onenote-converter/src/lib.rs index 3dc7a51211..48addbc7b1 100644 --- a/packages/onenote-converter/src/lib.rs +++ b/packages/onenote-converter/src/lib.rs @@ -1,11 +1,9 @@ pub use crate::parser::Parser; -use color_eyre::eyre::eyre; -use color_eyre::eyre::Result; +use color_eyre::eyre::{eyre, Result}; use std::panic; use wasm_bindgen::prelude::wasm_bindgen; -use crate::utils::utils::{log, log_warn}; -use crate::utils::{get_file_extension, get_file_name, get_output_path, get_parent_dir}; +use crate::utils::{fs_driver, utils::{log, log_warn}}; mod notebook; mod page; @@ -37,14 +35,11 @@ fn _main(input_path: &str, output_dir: &str, base_path: &str) -> Result<()> { pub fn convert(path: &str, output_dir: &str, base_path: &str) -> Result<()> { let mut parser = Parser::new(); - let extension: String = unsafe { get_file_extension(path) } - .unwrap() - .as_string() - .unwrap(); + let extension: String = fs_driver().get_file_extension(path); match extension.as_str() { ".one" => { - let _name: String = unsafe { get_file_name(path) }.unwrap().as_string().unwrap(); + let _name: String = fs_driver().get_file_name(path).expect("Missing file name"); log!("Parsing .one file: {}", _name); if path.contains("OneNote_RecycleBin") { @@ -53,29 +48,24 @@ pub fn convert(path: &str, output_dir: &str, base_path: &str) -> Result<()> { let section = parser.parse_section(path.to_owned())?; - let section_output_dir = unsafe { get_output_path(base_path, output_dir, path) } - .unwrap() - .as_string() - .unwrap(); - + let section_output_dir = fs_driver().get_output_path(base_path, output_dir, path); section::Renderer::new().render(§ion, section_output_dir.to_owned())?; } ".onetoc2" => { - let _name: String = unsafe { get_file_name(path) }.unwrap().as_string().unwrap(); + let _name: String = fs_driver().get_file_name(path).expect("Missing file name"); log!("Parsing .onetoc2 file: {}", _name); let notebook = parser.parse_notebook(path.to_owned())?; - let notebook_name = unsafe { get_parent_dir(path) } - .expect("Input file has no parent folder") - .as_string() - .expect("Parent folder has no name"); + let notebook_name = fs_driver() + .get_parent_dir(path) + .expect("Input file has no parent folder"); + if notebook_name == "" { + panic!("Parent directory has no name"); + } log!("notebook name: {:?}", notebook_name); - let notebook_output_dir = unsafe { get_output_path(base_path, output_dir, path) } - .unwrap() - .as_string() - .unwrap(); + let notebook_output_dir = fs_driver().get_output_path(base_path, output_dir, path); log!("Notebok directory: {:?}", notebook_output_dir); notebook::Renderer::new().render(¬ebook, ¬ebook_name, ¬ebook_output_dir)?; @@ -85,3 +75,4 @@ pub fn convert(path: &str, output_dir: &str, base_path: &str) -> Result<()> { Ok(()) } + diff --git a/packages/onenote-converter/src/notebook.rs b/packages/onenote-converter/src/notebook.rs index 460d690460..1b3251491a 100644 --- a/packages/onenote-converter/src/notebook.rs +++ b/packages/onenote-converter/src/notebook.rs @@ -2,8 +2,8 @@ use crate::parser::notebook::Notebook; use crate::parser::property::common::Color; use crate::parser::section::{Section, SectionEntry}; use crate::templates::notebook::Toc; +use crate::utils::fs_driver; use crate::utils::utils::log; -use crate::utils::{join_path, make_dir, remove_prefix}; use crate::{section, templates}; use color_eyre::eyre::Result; use palette::rgb::Rgb; @@ -20,12 +20,12 @@ impl Renderer { pub fn render(&mut self, notebook: &Notebook, name: &str, output_dir: &str) -> Result<()> { log!("Notebook name: {:?} {:?}", name, output_dir); - let _ = unsafe { make_dir(output_dir) }; + fs_driver().make_dir(output_dir)?; // let notebook_dir = unsafe { join_path(output_dir, sanitize_filename::sanitize(name).as_str()) }.unwrap().as_string().unwrap(); let notebook_dir = output_dir.to_owned(); - let _ = unsafe { make_dir(¬ebook_dir) }; + fs_driver().make_dir(¬ebook_dir)?; let mut toc = Vec::new(); @@ -41,13 +41,10 @@ impl Renderer { SectionEntry::SectionGroup(group) => { let dir_name = sanitize_filename::sanitize(group.display_name()); let section_group_dir = - unsafe { join_path(notebook_dir.as_str(), dir_name.as_str()) } - .unwrap() - .as_string() - .unwrap(); + fs_driver().join(notebook_dir.as_str(), dir_name.as_str()); log!("Section group directory: {:?}", section_group_dir); - let _ = unsafe { make_dir(section_group_dir.as_str()) }; + fs_driver().make_dir(section_group_dir.as_str())?; let mut entries = Vec::new(); @@ -84,10 +81,7 @@ impl Renderer { let section_path = renderer.render(section, notebook_dir)?; log!("section_path: {:?}", section_path); - let path_from_base_dir = unsafe { remove_prefix(section_path, base_dir.as_str()) } - .unwrap() - .as_string() - .unwrap(); + let path_from_base_dir = String::from(fs_driver().remove_prefix(§ion_path, &base_dir)); log!("path_from_base_dir: {:?}", path_from_base_dir); Ok(templates::notebook::Section { name: section.display_name().to_string(), diff --git a/packages/onenote-converter/src/page/embedded_file.rs b/packages/onenote-converter/src/page/embedded_file.rs index 1245a30e1e..1ebeede0ed 100644 --- a/packages/onenote-converter/src/page/embedded_file.rs +++ b/packages/onenote-converter/src/page/embedded_file.rs @@ -1,8 +1,8 @@ use crate::page::Renderer; use crate::parser::contents::EmbeddedFile; use crate::parser::property::embedded_file::FileType; +use crate::utils::fs_driver; use crate::utils::utils::log; -use crate::utils::{join_path, write_file}; use color_eyre::eyre::ContextCompat; use color_eyre::Result; use std::path::PathBuf; @@ -12,12 +12,9 @@ impl<'a> Renderer<'a> { let content; let filename = self.determine_filename(file.filename())?; - let path = unsafe { join_path(self.output.as_str(), filename.as_str()) } - .unwrap() - .as_string() - .unwrap(); + let path = fs_driver().join(self.output.as_str(), filename.as_str()); log!("Rendering embedded file: {:?}", path); - let _ = unsafe { write_file(path.as_str(), file.data()) }; + fs_driver().write_file(&path, file.data())?; let file_type = Self::guess_type(file); diff --git a/packages/onenote-converter/src/page/image.rs b/packages/onenote-converter/src/page/image.rs index 03cf7b4d91..9fe641428e 100644 --- a/packages/onenote-converter/src/page/image.rs +++ b/packages/onenote-converter/src/page/image.rs @@ -1,7 +1,7 @@ use crate::page::Renderer; use crate::parser::contents::Image; use crate::utils::utils::log; -use crate::utils::{join_path, px, write_file, AttributeSet, StyleSet}; +use crate::utils::{fs_driver, px, AttributeSet, StyleSet}; use color_eyre::Result; impl<'a> Renderer<'a> { @@ -10,12 +10,9 @@ impl<'a> Renderer<'a> { if let Some(data) = image.data() { let filename = self.determine_image_filename(image)?; - let path = unsafe { join_path(self.output.as_str(), filename.as_str()) } - .unwrap() - .as_string() - .unwrap(); + let path = fs_driver().join(self.output.as_str(), filename.as_str()); log!("Rendering image: {:?}", path); - let _ = unsafe { write_file(path.as_str(), data) }; + fs_driver().write_file(path.as_str(), data)?; let mut attrs = AttributeSet::new(); let mut styles = StyleSet::new(); diff --git a/packages/onenote-converter/src/parser/one/property_set/page_series_node.rs b/packages/onenote-converter/src/parser/one/property_set/page_series_node.rs index d4796e15ef..ae9db72113 100644 --- a/packages/onenote-converter/src/parser/one/property_set/page_series_node.rs +++ b/packages/onenote-converter/src/parser/one/property_set/page_series_node.rs @@ -1,4 +1,3 @@ -use crate::log_warn; use crate::parser::errors::{ErrorKind, Result}; use crate::parser::fsshttpb::data::cell_id::CellId; use crate::parser::fsshttpb::data::exguid::ExGuid; @@ -9,6 +8,7 @@ use crate::parser::one::property::{simple, PropertyType}; use crate::parser::one::property_set::PropertySetId; use crate::parser::onestore::object::Object; use crate::parser::shared::guid::Guid; +use crate::utils::utils::log_warn; /// A page series. /// diff --git a/packages/onenote-converter/src/parser/onenote/mod.rs b/packages/onenote-converter/src/parser/onenote/mod.rs index a8c9092c1f..a14618e592 100644 --- a/packages/onenote-converter/src/parser/onenote/mod.rs +++ b/packages/onenote-converter/src/parser/onenote/mod.rs @@ -4,11 +4,8 @@ use crate::parser::onenote::notebook::Notebook; use crate::parser::onenote::section::{Section, SectionEntry, SectionGroup}; use crate::parser::onestore::parse_store; use crate::parser::reader::Reader; -use crate::parser::utils::{exists, is_directory, read_dir, read_file}; +use crate::utils::fs_driver; use crate::utils::utils::log; -use crate::utils::{get_dir_name, get_file_extension, get_file_name, join_path}; -use std::panic; -use web_sys::js_sys::Uint8Array; pub(crate) mod content; pub(crate) mod embedded_file; @@ -26,7 +23,6 @@ pub(crate) mod rich_text; pub(crate) mod section; pub(crate) mod table; -extern crate console_error_panic_hook; extern crate lazy_static; /// The OneNote file parser. @@ -35,7 +31,6 @@ pub struct Parser; impl Parser { /// Create a new OneNote file parser. pub fn new() -> Parser { - panic::set_hook(Box::new(console_error_panic_hook::hook)); Parser {} } @@ -46,9 +41,7 @@ impl Parser { /// sections from the folder that the table of contents file is in. pub fn parse_notebook(&mut self, path: String) -> Result { log!("Parsing notebook: {:?}", path); - let file_content = unsafe { read_file(path.as_str()) }.unwrap(); - let array = Uint8Array::new(&file_content); - let data = array.to_vec(); + let data = fs_driver().read_file(&path)?; let packaging = OneStorePackaging::parse(&mut Reader::new(&data))?; let store = parse_store(&packaging)?; @@ -56,29 +49,20 @@ impl Parser { return Err(ErrorKind::NotATocFile { file: path }.into()); } - let base_dir = unsafe { get_dir_name(path.as_str()) } - .expect("base dir not found") - .as_string() - .unwrap(); + let base_dir = fs_driver().get_dir_name(&path); let sections = notebook::parse_toc(store.data_root())? .iter() - .map(|name| { - let result = unsafe { join_path(base_dir.as_str(), name) } - .unwrap() - .as_string() - .unwrap(); - return result; - }) + .map(|name| fs_driver().join(&base_dir, name)) .filter(|p| !p.contains("OneNote_RecycleBin")) .filter(|p| { - let is_file = match unsafe { exists(p.as_str()) } { + let is_file = match fs_driver().exists(p) { Ok(is_file) => is_file, Err(_err) => false, }; return is_file; }) .map(|p| { - let is_dir = unsafe { is_directory(p.as_str()) }.unwrap(); + let is_dir = fs_driver().is_directory(&p)?; if !is_dir { self.parse_section(p).map(SectionEntry::Section) } else { @@ -96,9 +80,7 @@ impl Parser { /// OneNote section. pub fn parse_section(&mut self, path: String) -> Result
{ log!("Parsing section: {:?}", path); - let file_content = unsafe { read_file(path.as_str()) }.unwrap(); - let array = Uint8Array::new(&file_content); - let data = array.to_vec(); + let data = fs_driver().read_file(path.as_str())?; let packaging = OneStorePackaging::parse(&mut Reader::new(&data))?; let store = parse_store(&packaging)?; @@ -106,25 +88,20 @@ impl Parser { return Err(ErrorKind::NotASectionFile { file: path }.into()); } - let filename = unsafe { get_file_name(path.as_str()) } - .expect("file without file name") - .as_string() - .unwrap(); + let filename = fs_driver() + .get_file_name(&path) + .expect("file without file name"); section::parse_section(store, filename) } fn parse_section_group(&mut self, path: String) -> Result { - let display_name = unsafe { get_file_name(path.as_str()) } - .expect("file without file name") - .as_string() - .unwrap(); + let display_name = fs_driver() + .get_file_name(path.as_str()) + .expect("file without file name"); - if let Some(entries) = unsafe { read_dir(path.as_str()) } { + if let Ok(entries) = fs_driver().read_dir(&path) { for entry in entries { - let ext = unsafe { get_file_extension(entry.as_str()) } - .unwrap() - .as_string() - .unwrap(); + let ext = fs_driver().get_file_extension(&entry); if ext == ".onetoc2" { return self.parse_notebook(entry).map(|group| SectionGroup { display_name, diff --git a/packages/onenote-converter/src/parser/onestore/mod.rs b/packages/onenote-converter/src/parser/onestore/mod.rs index 8a8e747099..e1830ba9bb 100644 --- a/packages/onenote-converter/src/parser/onestore/mod.rs +++ b/packages/onenote-converter/src/parser/onestore/mod.rs @@ -31,7 +31,7 @@ impl<'a> OneStore<'a> { self.schema } - pub(crate) fn data_root(&'a self) -> &'a ObjectSpace { + pub(crate) fn data_root(&self) -> &ObjectSpace { &self.data_root } diff --git a/packages/onenote-converter/src/parser/utils.rs b/packages/onenote-converter/src/parser/utils.rs index 372de64384..498321b985 100644 --- a/packages/onenote-converter/src/parser/utils.rs +++ b/packages/onenote-converter/src/parser/utils.rs @@ -3,8 +3,6 @@ use itertools::Itertools; use std::collections::HashMap; use std::fmt; use std::fmt::Display; -use wasm_bindgen::prelude::wasm_bindgen; -use wasm_bindgen::JsValue; use widestring::U16CString; pub(crate) struct AttributeSet(HashMap<&'static str, String>); @@ -69,35 +67,6 @@ impl Display for StyleSet { } } -#[wasm_bindgen(module = "/node_functions.js")] -extern "C" { - #[wasm_bindgen(js_name = isDirectory, catch)] - pub unsafe fn is_directory(path: &str) -> std::result::Result; - - #[wasm_bindgen(js_name = readDir, catch)] - unsafe fn read_dir_js(path: &str) -> std::result::Result; -} - -pub unsafe fn read_dir(path: &str) -> Option> { - let result_ptr = read_dir_js(path).unwrap(); - - let result_str: String = match result_ptr.as_string() { - Some(x) => x, - _ => String::new(), - }; - let names: Vec = result_str.split('\n').map(|s| s.to_string()).collect_vec(); - Some(names) -} - -#[wasm_bindgen(module = "fs")] -extern "C" { - #[wasm_bindgen(js_name = readFileSync, catch)] - pub unsafe fn read_file(path: &str) -> std::result::Result; - - #[wasm_bindgen(js_name = existsSync, catch)] - pub unsafe fn exists(path: &str) -> std::result::Result; -} - pub(crate) trait Utf16ToString { fn utf16_to_string(&self) -> Result; } diff --git a/packages/onenote-converter/src/section.rs b/packages/onenote-converter/src/section.rs index 8c3dc22fc5..a0a71a53b0 100644 --- a/packages/onenote-converter/src/section.rs +++ b/packages/onenote-converter/src/section.rs @@ -1,6 +1,6 @@ use crate::parser::section::Section; +use crate::utils::fs_driver; use crate::utils::utils::log; -use crate::utils::{join_path, make_dir, remove_prefix, write_file}; use crate::{page, templates}; use color_eyre::eyre::Result; use std::collections::HashSet; @@ -19,15 +19,10 @@ impl Renderer { } pub fn render(&mut self, section: &Section, output_dir: String) -> Result { - let section_dir = unsafe { - join_path( - output_dir.as_str(), - sanitize_filename::sanitize(section.display_name()).as_str(), - ) - } - .unwrap() - .as_string() - .unwrap(); + let section_dir = fs_driver().join( + output_dir.as_str(), + sanitize_filename::sanitize(section.display_name()).as_str(), + ); log!( "section_dir: {:?} \n output_dir: {:?}", section_dir, @@ -35,7 +30,7 @@ impl Renderer { ); log!("Rendering section: {:?}", section_dir); - let _ = unsafe { make_dir(section_dir.as_str()) }; + fs_driver().make_dir(section_dir.as_str())?; let mut toc = Vec::new(); let mut fallback_title_index = 0; @@ -52,38 +47,27 @@ impl Renderer { let file_name = self.determine_page_filename(&file_name)?; let file_name = sanitize_filename::sanitize(file_name + ".html"); - let page_path = unsafe { join_path(section_dir.as_str(), file_name.as_str()) } - .unwrap() - .as_string() - .unwrap(); + let page_path = fs_driver().join(section_dir.as_str(), file_name.as_str()); let mut renderer = page::Renderer::new(section_dir.clone(), self); let page_html = renderer.render_page(page)?; log!("Creating page file: {:?}", page_path); - let _ = unsafe { write_file(&page_path, page_html.as_bytes()) }; + fs_driver().write_file(&page_path, page_html.as_bytes())?; let page_path_without_basedir = - unsafe { remove_prefix(page_path, output_dir.as_str()) } - .unwrap() - .as_string() - .unwrap(); + String::from(fs_driver().remove_prefix(&page_path, output_dir.as_str())); toc.push((title, page_path_without_basedir, page.level())) } } let toc_html = templates::section::render(section.display_name(), toc)?; - let toc_file = unsafe { - join_path( - output_dir.as_str(), - format!("{}.html", section.display_name()).as_str(), - ) - } - .unwrap() - .as_string() - .unwrap(); + let toc_file = fs_driver().join( + output_dir.as_str(), + format!("{}.html", section.display_name()).as_str(), + ); log!("ToC: {:?}", toc_file); - let _ = unsafe { write_file(toc_file.as_str(), toc_html.as_bytes()) }; + fs_driver().write_file(toc_file.as_str(), toc_html.as_bytes())?; Ok(section_dir) } diff --git a/packages/onenote-converter/src/utils/file_api/api.rs b/packages/onenote-converter/src/utils/file_api/api.rs new file mode 100644 index 0000000000..affe47d17c --- /dev/null +++ b/packages/onenote-converter/src/utils/file_api/api.rs @@ -0,0 +1,45 @@ +pub type ApiResult = std::result::Result; + +pub trait FileApiDriver: Send + Sync { + fn is_directory(&self, path: &str) -> ApiResult; + fn read_dir(&self, path: &str) -> ApiResult>; + fn read_file(&self, path: &str) -> ApiResult>; + fn write_file(&self, path: &str, data: &[u8]) -> ApiResult<()>; + fn make_dir(&self, path: &str) -> ApiResult<()>; + fn exists(&self, path: &str) -> ApiResult; + + // These functions correspond to the similarly-named + // path functions. + fn get_file_name(&self, path: &str) -> Option; + fn get_file_extension(&self, path: &str) -> String; + fn get_dir_name(&self, path: &str) -> String; + fn join(&self, path_1: &str, path_2: &str) -> String; + + fn remove_prefix<'a>(&self, full_path: &'a str, prefix: &str) -> &'a str { + if full_path.starts_with(prefix) { + &full_path[prefix.len()..] + } else { + full_path + } + } + fn get_output_path(&self, input_dir: &str, output_dir: &str, file_path: &str) -> String { + let base_path = self.remove_prefix(file_path, input_dir); + let rebased_output = self.join(output_dir, base_path); + self.get_dir_name(&rebased_output) + } + + fn get_parent_dir(&self, path: &str) -> Option { + let dir_name = self.get_dir_name(path); + let result = self.get_file_name(&dir_name); + + if let Some(value) = result { + if value == "" { + None + } else { + Some(value) + } + } else { + None + } + } +} diff --git a/packages/onenote-converter/src/utils/file_api/mod.rs b/packages/onenote-converter/src/utils/file_api/mod.rs new file mode 100644 index 0000000000..1877b09699 --- /dev/null +++ b/packages/onenote-converter/src/utils/file_api/mod.rs @@ -0,0 +1,23 @@ +pub mod api; +pub use api::ApiResult; +pub use api::FileApiDriver; +use lazy_static::lazy_static; +use std::sync::Arc; + +#[cfg(target_arch = "wasm32")] +mod wasm_driver; +#[cfg(target_arch = "wasm32")] +use wasm_driver::FileApiDriverImpl; + +#[cfg(not(target_arch = "wasm32"))] +mod native_driver; +#[cfg(not(target_arch = "wasm32"))] +use native_driver::FileApiDriverImpl; + +lazy_static! { + static ref FS_DRIVER: Arc = Arc::new(FileApiDriverImpl {}); +} + +pub fn fs_driver() -> Arc { + FS_DRIVER.clone() +} diff --git a/packages/onenote-converter/src/utils/file_api/native_driver.rs b/packages/onenote-converter/src/utils/file_api/native_driver.rs new file mode 100644 index 0000000000..a86bdb9729 --- /dev/null +++ b/packages/onenote-converter/src/utils/file_api/native_driver.rs @@ -0,0 +1,64 @@ +use super::ApiResult; +use super::FileApiDriver; +use std::fs; +use std::path::Path; + +pub struct FileApiDriverImpl {} + +impl FileApiDriver for FileApiDriverImpl { + fn is_directory(&self, path: &str) -> ApiResult { + let metadata = fs::metadata(path)?; + let file_type = metadata.file_type(); + Ok(file_type.is_dir()) + } + + fn read_dir(&self, path: &str) -> ApiResult> { + let mut result: Vec = Vec::new(); + for item in fs::read_dir(path)? { + let item = item?.path(); + result.push(item.to_string_lossy().into()) + } + Ok(result) + } + + fn read_file(&self, path: &str) -> ApiResult> { + Ok(fs::read(path)?) + } + + fn write_file(&self, path: &str, data: &[u8]) -> ApiResult<()> { + Ok(fs::write(path, data)?) + } + + fn exists(&self, path: &str) -> ApiResult { + Ok(fs::exists(path)?) + } + + fn make_dir(&self, path: &str) -> ApiResult<()> { + Ok(fs::create_dir(path)?) + } + + fn get_file_name(&self, path: &str) -> Option { + match Path::new(path).file_name() { + Some(name) => Some(name.to_string_lossy().into()), + None => None, + } + } + fn get_file_extension(&self, path: &str) -> String { + match Path::new(path).extension() { + Some(ext) => { + let extension = String::from(ext.to_string_lossy()); + String::from(".") + &extension + } + None => String::from(""), + } + } + fn get_dir_name(&self, path: &str) -> String { + match Path::new(path).parent() { + Some(parent) => parent.to_string_lossy().into(), + None => String::from(""), + } + } + fn join(&self, path_1: &str, path_2: &str) -> String { + Path::new(path_1).join(path_2).to_string_lossy().into() + } +} diff --git a/packages/onenote-converter/src/utils/file_api/wasm_driver.rs b/packages/onenote-converter/src/utils/file_api/wasm_driver.rs new file mode 100644 index 0000000000..da79c942a3 --- /dev/null +++ b/packages/onenote-converter/src/utils/file_api/wasm_driver.rs @@ -0,0 +1,140 @@ +use super::ApiResult; +use super::FileApiDriver; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::JsValue; +use web_sys::js_sys; +use web_sys::js_sys::Uint8Array; + +#[wasm_bindgen(module = "/node_functions.js")] +extern "C" { + #[wasm_bindgen(js_name = mkdirSyncRecursive, catch)] + fn make_dir(path: &str) -> std::result::Result; + + #[wasm_bindgen(js_name = pathSep, catch)] + fn path_sep() -> std::result::Result; + + #[wasm_bindgen(js_name = removePrefix, catch)] + fn remove_prefix(base_path: String, prefix: &str) -> std::result::Result; + + #[wasm_bindgen(js_name = getOutputPath, catch)] + fn get_output_path( + input_dir: &str, + output_dir: &str, + file_path: &str, + ) -> std::result::Result; + + #[wasm_bindgen(js_name = normalizeAndWriteFile, catch)] + fn write_file(path: &str, data: &[u8]) -> std::result::Result; + + #[wasm_bindgen(js_name = isDirectory, catch)] + fn is_directory(path: &str) -> std::result::Result; + + #[wasm_bindgen(js_name = readDir, catch)] + fn read_dir_js(path: &str) -> std::result::Result; +} + +#[wasm_bindgen(module = "fs")] +extern "C" { + #[wasm_bindgen(js_name = readFileSync, catch)] + fn read_file(path: &str) -> std::result::Result; + + #[wasm_bindgen(js_name = existsSync, catch)] + fn exists(path: &str) -> std::result::Result; +} + +#[wasm_bindgen(module = "path")] +extern "C" { + #[wasm_bindgen(js_name = basename, catch)] + fn get_file_name(path: &str) -> std::result::Result; + + #[wasm_bindgen(js_name = extname, catch)] + fn get_file_extension(path: &str) -> std::result::Result; + + #[wasm_bindgen(js_name = dirname, catch)] + fn get_dir_name(path: &str) -> std::result::Result; + + #[wasm_bindgen(js_name = join, catch)] + fn join_path(path_1: &str, path_2: &str) -> std::result::Result; +} + +fn handle_error(error: JsValue, source: &str) -> std::io::Error { + use std::io::Error; + use std::io::ErrorKind; + + let error = js_sys::Error::from(error); + match error.name().to_string() { + _ => Error::new( + ErrorKind::Other, + String::from(format!("Err({}): {:?}", source, error)), + ), + } +} + +pub struct FileApiDriverImpl {} + +impl FileApiDriver for FileApiDriverImpl { + fn is_directory(&self, path: &str) -> ApiResult { + match is_directory(path) { + Ok(is_dir) => Ok(is_dir), + Err(e) => Err(handle_error(e, "checking is_directory")), + } + } + + fn read_dir(&self, path: &str) -> ApiResult> { + let result_ptr = read_dir_js(path).unwrap(); + + let result_str: String = match result_ptr.as_string() { + Some(x) => x, + _ => String::new(), + }; + Ok(result_str.split('\n').map(|s| s.to_string()).collect()) + } + + fn read_file(&self, path: &str) -> ApiResult> { + match read_file(path) { + Ok(file) => Ok(Uint8Array::new(&file).to_vec()), + Err(e) => Err(handle_error(e, &format!("reading file {}", path))), + } + } + + fn write_file(&self, path: &str, data: &[u8]) -> ApiResult<()> { + if let Err(error) = write_file(path, data) { + Err(handle_error(error, &format!("writing file {}", path))) + } else { + Ok(()) + } + } + + fn exists(&self, path: &str) -> ApiResult { + match exists(path) { + Ok(exists) => Ok(exists), + Err(e) => Err(handle_error(e, &format!("checking exists {}", path))), + } + } + + fn make_dir(&self, path: &str) -> ApiResult<()> { + if let Err(error) = make_dir(path) { + Err(handle_error(error, &format!("mkdir {}", path))) + } else { + Ok(()) + } + } + + fn get_file_name(&self, path: &str) -> Option { + let file_name = get_file_name(path).unwrap().as_string().unwrap(); + if file_name == "" { + None + } else { + Some(file_name) + } + } + fn get_file_extension(&self, path: &str) -> String { + get_file_extension(path).unwrap().as_string().unwrap() + } + fn get_dir_name(&self, path: &str) -> String { + get_dir_name(path).unwrap().as_string().unwrap() + } + fn join(&self, path_1: &str, path_2: &str) -> String { + join_path(path_1, path_2).unwrap().as_string().unwrap() + } +} diff --git a/packages/onenote-converter/src/utils.rs b/packages/onenote-converter/src/utils/mod.rs similarity index 57% rename from packages/onenote-converter/src/utils.rs rename to packages/onenote-converter/src/utils/mod.rs index 23c797cac9..646cc99a46 100644 --- a/packages/onenote-converter/src/utils.rs +++ b/packages/onenote-converter/src/utils/mod.rs @@ -5,10 +5,12 @@ use std::collections::HashMap; use std::fmt; use std::fmt::Display; use std::sync::Mutex; -use wasm_bindgen::prelude::wasm_bindgen; -use wasm_bindgen::JsValue; use widestring::U16CString; +mod file_api; +pub use file_api::fs_driver; +pub use file_api::FileApiDriver; + pub(crate) fn px(inches: f32) -> String { format!("{}px", (inches * 48.0).round()) } @@ -74,63 +76,8 @@ impl Display for StyleSet { } } -#[wasm_bindgen(module = "/node_functions.js")] -extern "C" { - #[wasm_bindgen(js_name = mkdirSyncRecursive, catch)] - pub unsafe fn make_dir(path: &str) -> std::result::Result; - - #[wasm_bindgen(js_name = pathSep, catch)] - pub unsafe fn path_sep() -> std::result::Result; - - #[wasm_bindgen(js_name = removePrefix, catch)] - pub unsafe fn remove_prefix( - base_path: String, - prefix: &str, - ) -> std::result::Result; - - #[wasm_bindgen(js_name = getOutputPath, catch)] - pub unsafe fn get_output_path( - input_dir: &str, - output_dir: &str, - file_path: &str, - ) -> std::result::Result; - - #[wasm_bindgen(js_name = getParentDir, catch)] - pub unsafe fn get_parent_dir(input_dir: &str) -> std::result::Result; - - #[wasm_bindgen(js_name = normalizeAndWriteFile, catch)] - pub unsafe fn write_file(path: &str, data: &[u8]) -> std::result::Result; -} - -#[wasm_bindgen(module = "fs")] -extern "C" { - // #[wasm_bindgen(js_name = writeFileSync, catch)] - // pub unsafe fn write_file(path: &str, data: &[u8]) -> std::result::Result; - - #[wasm_bindgen(js_name = readFileSync, catch)] - pub unsafe fn read_file(path: &str) -> std::result::Result; - - #[wasm_bindgen(js_name = existsSync, catch)] - pub unsafe fn exists(path: &str) -> std::result::Result; -} - -#[wasm_bindgen(module = "path")] -extern "C" { - #[wasm_bindgen(js_name = basename, catch)] - pub unsafe fn get_file_name(path: &str) -> std::result::Result; - - #[wasm_bindgen(js_name = extname, catch)] - pub unsafe fn get_file_extension(path: &str) -> std::result::Result; - - #[wasm_bindgen(js_name = dirname, catch)] - pub unsafe fn get_dir_name(path: &str) -> std::result::Result; - - #[wasm_bindgen(js_name = join, catch)] - pub unsafe fn join_path(path_1: &str, path_2: &str) -> std::result::Result; -} - pub mod utils { - + #[cfg(target_arch = "wasm32")] macro_rules! log { ( $( $t:tt )* ) => { #[cfg(debug_assertions)] @@ -138,15 +85,32 @@ pub mod utils { } } + #[cfg(target_arch = "wasm32")] macro_rules! log_warn { ( $( $t:tt )* ) => { use crate::utils::get_current_page; - web_sys::console::warn_1(&format!("OneNoteConverter: Warning around the following page: {}", get_current_page().unwrap()).into()); + web_sys::console::warn_1(&format!("OneNoteConverter: Warning around the following page: {}", get_current_page()).into()); web_sys::console::warn_2(&format!("OneNoteConverter: ").into(), &format!( $( $t )* ).into()); } } + #[cfg(not(target_arch = "wasm32"))] + macro_rules! log { + ( $( $t:tt )* ) => { + #[cfg(debug_assertions)] + println!( $( $t )* ); + } + } + + #[cfg(not(target_arch = "wasm32"))] + macro_rules! log_warn { + ( $( $t:tt )* ) => { + use crate::utils::get_current_page; + println!("Warning: {}, near {}", &format!( $( $t )* ), get_current_page()); + } + } + pub(crate) use log; pub(crate) use log_warn; } @@ -177,7 +141,9 @@ pub fn set_current_page(page_name: String) { *current_page = Some(page_name.to_string()); } -pub fn get_current_page() -> Option { +pub fn get_current_page() -> String { let current_page = CURRENT_PAGE.lock().unwrap(); - current_page.clone() + current_page + .clone() + .unwrap_or_else(|| String::from("[None]")) } diff --git a/packages/onenote-converter/tests/convert.rs b/packages/onenote-converter/tests/convert.rs new file mode 100644 index 0000000000..1233e411f5 --- /dev/null +++ b/packages/onenote-converter/tests/convert.rs @@ -0,0 +1,37 @@ +use onenote_converter::convert; +use std::fs; +use std::path::PathBuf; + +fn get_output_dir() -> PathBuf { + PathBuf::from("./test-output") +} + +fn setup() { + let output_dir = get_output_dir(); + + if output_dir.exists() { + fs::remove_dir_all(&output_dir).unwrap(); + } + fs::create_dir(&output_dir).unwrap(); +} + +#[test] +fn convert_simple() { + setup(); + + let output_dir = get_output_dir(); + convert( + "./assets/test-data/single-page/Untitled Section.one", + &output_dir.to_string_lossy(), + "./assets/test-data/single-page/", + ) + .unwrap(); + + // Should create a table of contents file + assert!(output_dir.join("Untitled Section.html").exists()); + // Should convert the input page to an HTML file + assert!(output_dir + .join("Untitled Section") + .join("test.html") + .exists()); +}