You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-11-23 22:36:32 +02:00
Chore: OneNote converter: Refactor to allow debugging the import process, reduce use of "unsafe" (#13300)
This commit is contained in:
1
packages/onenote-converter/.gitignore
vendored
1
packages/onenote-converter/.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
/target
|
||||
/output
|
||||
/test-output
|
||||
|
||||
/.idea
|
||||
*.iml
|
||||
|
||||
@@ -39,4 +39,4 @@ features = [
|
||||
]
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
@@ -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!()`)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
///
|
||||
|
||||
@@ -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<Notebook> {
|
||||
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<Section> {
|
||||
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<SectionGroup> {
|
||||
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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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<bool, JsValue>;
|
||||
|
||||
#[wasm_bindgen(js_name = readDir, catch)]
|
||||
unsafe fn read_dir_js(path: &str) -> std::result::Result<JsValue, JsValue>;
|
||||
}
|
||||
|
||||
pub unsafe fn read_dir(path: &str) -> Option<Vec<String>> {
|
||||
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<String> = 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<JsValue, JsValue>;
|
||||
|
||||
#[wasm_bindgen(js_name = existsSync, catch)]
|
||||
pub unsafe fn exists(path: &str) -> std::result::Result<bool, JsValue>;
|
||||
}
|
||||
|
||||
pub(crate) trait Utf16ToString {
|
||||
fn utf16_to_string(&self) -> Result<String>;
|
||||
}
|
||||
|
||||
@@ -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<String> {
|
||||
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)
|
||||
}
|
||||
|
||||
45
packages/onenote-converter/src/utils/file_api/api.rs
Normal file
45
packages/onenote-converter/src/utils/file_api/api.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
pub type ApiResult<T> = std::result::Result<T, std::io::Error>;
|
||||
|
||||
pub trait FileApiDriver: Send + Sync {
|
||||
fn is_directory(&self, path: &str) -> ApiResult<bool>;
|
||||
fn read_dir(&self, path: &str) -> ApiResult<Vec<String>>;
|
||||
fn read_file(&self, path: &str) -> ApiResult<Vec<u8>>;
|
||||
fn write_file(&self, path: &str, data: &[u8]) -> ApiResult<()>;
|
||||
fn make_dir(&self, path: &str) -> ApiResult<()>;
|
||||
fn exists(&self, path: &str) -> ApiResult<bool>;
|
||||
|
||||
// These functions correspond to the similarly-named
|
||||
// path functions.
|
||||
fn get_file_name(&self, path: &str) -> Option<String>;
|
||||
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<String> {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
23
packages/onenote-converter/src/utils/file_api/mod.rs
Normal file
23
packages/onenote-converter/src/utils/file_api/mod.rs
Normal file
@@ -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<dyn FileApiDriver> = Arc::new(FileApiDriverImpl {});
|
||||
}
|
||||
|
||||
pub fn fs_driver() -> Arc<dyn FileApiDriver> {
|
||||
FS_DRIVER.clone()
|
||||
}
|
||||
@@ -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<bool> {
|
||||
let metadata = fs::metadata(path)?;
|
||||
let file_type = metadata.file_type();
|
||||
Ok(file_type.is_dir())
|
||||
}
|
||||
|
||||
fn read_dir(&self, path: &str) -> ApiResult<Vec<String>> {
|
||||
let mut result: Vec<String> = 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<Vec<u8>> {
|
||||
Ok(fs::read(path)?)
|
||||
}
|
||||
|
||||
fn write_file(&self, path: &str, data: &[u8]) -> ApiResult<()> {
|
||||
Ok(fs::write(path, data)?)
|
||||
}
|
||||
|
||||
fn exists(&self, path: &str) -> ApiResult<bool> {
|
||||
Ok(fs::exists(path)?)
|
||||
}
|
||||
|
||||
fn make_dir(&self, path: &str) -> ApiResult<()> {
|
||||
Ok(fs::create_dir(path)?)
|
||||
}
|
||||
|
||||
fn get_file_name(&self, path: &str) -> Option<String> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
140
packages/onenote-converter/src/utils/file_api/wasm_driver.rs
Normal file
140
packages/onenote-converter/src/utils/file_api/wasm_driver.rs
Normal file
@@ -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<JsValue, JsValue>;
|
||||
|
||||
#[wasm_bindgen(js_name = pathSep, catch)]
|
||||
fn path_sep() -> std::result::Result<JsValue, JsValue>;
|
||||
|
||||
#[wasm_bindgen(js_name = removePrefix, catch)]
|
||||
fn remove_prefix(base_path: String, prefix: &str) -> std::result::Result<JsValue, JsValue>;
|
||||
|
||||
#[wasm_bindgen(js_name = getOutputPath, catch)]
|
||||
fn get_output_path(
|
||||
input_dir: &str,
|
||||
output_dir: &str,
|
||||
file_path: &str,
|
||||
) -> std::result::Result<JsValue, JsValue>;
|
||||
|
||||
#[wasm_bindgen(js_name = normalizeAndWriteFile, catch)]
|
||||
fn write_file(path: &str, data: &[u8]) -> std::result::Result<JsValue, JsValue>;
|
||||
|
||||
#[wasm_bindgen(js_name = isDirectory, catch)]
|
||||
fn is_directory(path: &str) -> std::result::Result<bool, JsValue>;
|
||||
|
||||
#[wasm_bindgen(js_name = readDir, catch)]
|
||||
fn read_dir_js(path: &str) -> std::result::Result<JsValue, JsValue>;
|
||||
}
|
||||
|
||||
#[wasm_bindgen(module = "fs")]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_name = readFileSync, catch)]
|
||||
fn read_file(path: &str) -> std::result::Result<JsValue, JsValue>;
|
||||
|
||||
#[wasm_bindgen(js_name = existsSync, catch)]
|
||||
fn exists(path: &str) -> std::result::Result<bool, JsValue>;
|
||||
}
|
||||
|
||||
#[wasm_bindgen(module = "path")]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_name = basename, catch)]
|
||||
fn get_file_name(path: &str) -> std::result::Result<JsValue, JsValue>;
|
||||
|
||||
#[wasm_bindgen(js_name = extname, catch)]
|
||||
fn get_file_extension(path: &str) -> std::result::Result<JsValue, JsValue>;
|
||||
|
||||
#[wasm_bindgen(js_name = dirname, catch)]
|
||||
fn get_dir_name(path: &str) -> std::result::Result<JsValue, JsValue>;
|
||||
|
||||
#[wasm_bindgen(js_name = join, catch)]
|
||||
fn join_path(path_1: &str, path_2: &str) -> std::result::Result<JsValue, JsValue>;
|
||||
}
|
||||
|
||||
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<bool> {
|
||||
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<Vec<String>> {
|
||||
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<Vec<u8>> {
|
||||
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<bool> {
|
||||
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<String> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -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<JsValue, JsValue>;
|
||||
|
||||
#[wasm_bindgen(js_name = pathSep, catch)]
|
||||
pub unsafe fn path_sep() -> std::result::Result<JsValue, JsValue>;
|
||||
|
||||
#[wasm_bindgen(js_name = removePrefix, catch)]
|
||||
pub unsafe fn remove_prefix(
|
||||
base_path: String,
|
||||
prefix: &str,
|
||||
) -> std::result::Result<JsValue, JsValue>;
|
||||
|
||||
#[wasm_bindgen(js_name = getOutputPath, catch)]
|
||||
pub unsafe fn get_output_path(
|
||||
input_dir: &str,
|
||||
output_dir: &str,
|
||||
file_path: &str,
|
||||
) -> std::result::Result<JsValue, JsValue>;
|
||||
|
||||
#[wasm_bindgen(js_name = getParentDir, catch)]
|
||||
pub unsafe fn get_parent_dir(input_dir: &str) -> std::result::Result<JsValue, JsValue>;
|
||||
|
||||
#[wasm_bindgen(js_name = normalizeAndWriteFile, catch)]
|
||||
pub unsafe fn write_file(path: &str, data: &[u8]) -> std::result::Result<JsValue, JsValue>;
|
||||
}
|
||||
|
||||
#[wasm_bindgen(module = "fs")]
|
||||
extern "C" {
|
||||
// #[wasm_bindgen(js_name = writeFileSync, catch)]
|
||||
// pub unsafe fn write_file(path: &str, data: &[u8]) -> std::result::Result<JsValue, JsValue>;
|
||||
|
||||
#[wasm_bindgen(js_name = readFileSync, catch)]
|
||||
pub unsafe fn read_file(path: &str) -> std::result::Result<JsValue, JsValue>;
|
||||
|
||||
#[wasm_bindgen(js_name = existsSync, catch)]
|
||||
pub unsafe fn exists(path: &str) -> std::result::Result<bool, JsValue>;
|
||||
}
|
||||
|
||||
#[wasm_bindgen(module = "path")]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_name = basename, catch)]
|
||||
pub unsafe fn get_file_name(path: &str) -> std::result::Result<JsValue, JsValue>;
|
||||
|
||||
#[wasm_bindgen(js_name = extname, catch)]
|
||||
pub unsafe fn get_file_extension(path: &str) -> std::result::Result<JsValue, JsValue>;
|
||||
|
||||
#[wasm_bindgen(js_name = dirname, catch)]
|
||||
pub unsafe fn get_dir_name(path: &str) -> std::result::Result<JsValue, JsValue>;
|
||||
|
||||
#[wasm_bindgen(js_name = join, catch)]
|
||||
pub unsafe fn join_path(path_1: &str, path_2: &str) -> std::result::Result<JsValue, JsValue>;
|
||||
}
|
||||
|
||||
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<String> {
|
||||
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]"))
|
||||
}
|
||||
37
packages/onenote-converter/tests/convert.rs
Normal file
37
packages/onenote-converter/tests/convert.rs
Normal file
@@ -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());
|
||||
}
|
||||
Reference in New Issue
Block a user