1
0
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:
Henry Heino
2025-09-30 09:03:38 -07:00
committed by GitHub
parent 0b082a985b
commit 14b56f19df
21 changed files with 413 additions and 223 deletions

View File

@@ -1,5 +1,6 @@
/target
/output
/test-output
/.idea
*.iml

View File

@@ -39,4 +39,4 @@ features = [
]
[lib]
crate-type = ["cdylib"]
crate-type = ["cdylib", "lib"]

View File

@@ -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!()`)

View File

@@ -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,
};

View File

@@ -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(&section, 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(&notebook, &notebook_name, &notebook_output_dir)?;
@@ -85,3 +75,4 @@ pub fn convert(path: &str, output_dir: &str, base_path: &str) -> Result<()> {
Ok(())
}

View File

@@ -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(&notebook_dir) };
fs_driver().make_dir(&notebook_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(&section_path, &base_dir));
log!("path_from_base_dir: {:?}", path_from_base_dir);
Ok(templates::notebook::Section {
name: section.display_name().to_string(),

View File

@@ -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);

View 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();

View File

@@ -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.
///

View File

@@ -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,

View File

@@ -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
}

View File

@@ -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>;
}

View File

@@ -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)
}

View 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
}
}
}

View 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()
}

View File

@@ -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()
}
}

View 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()
}
}

View File

@@ -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]"))
}

View 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());
}