Skip to main content

human_readable_map16/
lib.rs

1//! Provides a simple wrapper around the C API of [the human-readable map16 C++ library](https://github.com/Underrout/human-readable-map16/tree/main).
2//! 
3//! To convert a binary [Lunar Magic](https://fusoya.eludevisibility.org/lm/) map16 file to a folder tree of human-readable text files, use the [from_map16] function:
4//! ```
5//! # use human_readable_map16::from_map16;
6//! from_map16("input.map16", "map16_folder");
7//! ```
8//! 
9//! To convert from the folder generated by `from_map16` back to a binary map16 file, use [to_map16]:
10//! ```
11//! # use human_readable_map16::to_map16;
12//! to_map16("map16_folder", "output.map16"); 
13//! ```
14
15use std::{ffi::{CStr, CString}, path::{Path, PathBuf}};
16
17use thiserror::Error;
18
19mod ffi {
20    use std::ffi::{c_char, c_int};
21
22    unsafe extern "C" {
23        pub(crate) fn from_map16(input_file: *const c_char, output_folder: *const c_char) -> c_int;
24        pub(crate) fn to_map16(input_folder: *const c_char, output_file: *const c_char) -> c_int;
25
26        pub(crate) fn get_last_error() -> *const c_char;
27    }
28}
29
30fn last_error() -> String {
31    let cstr = unsafe {
32        CStr::from_ptr(ffi::get_last_error())
33    };
34
35    cstr.to_string_lossy().into_owned()
36}
37
38pub type Result<T> = std::result::Result<T, Error>;
39
40#[derive(Debug, Error)]
41pub enum Error {
42    #[error(transparent)]
43    Io(#[from] std::io::Error),
44
45    #[error(transparent)]
46    NulError(#[from] std::ffi::NulError),
47
48    #[error("Map16 conversion error: {0}")]
49    Conversion(String),
50
51    #[error("Failed to convert {0} to str")]
52    PathConversion(PathBuf),
53}
54
55fn convert_to_cstring(path: &Path) -> Result<CString> {
56    let s = path.to_str().ok_or(Error::PathConversion(path.to_path_buf()))?;
57
58    Ok(CString::new(s)?)
59}
60
61/// Converts from a binary map16 file to a folder tree of human-readable text files.
62/// 
63/// # Examples
64/// 
65/// ```
66/// use human_readable_map16::from_map16;
67/// 
68/// from_map16("input.map16", "map16_folder");
69/// ```
70/// 
71/// # Panics
72/// 
73/// This function does not panic.
74/// 
75/// # Errors
76/// 
77/// Returns an error if any step of the conversion fails. This could be for many 
78/// reasons, the input file not existing, invalid formatting of the input file,
79/// problems writing the output, etc.
80/// 
81/// [Error::Conversion] should contain a fairly descriptive message if it 
82/// is returned. Other errors should rarely occur.
83pub fn from_map16<I: AsRef<Path>, O: AsRef<Path>>(input_file: I, output_folder: O) -> Result<()> {
84    let input = convert_to_cstring(input_file.as_ref())?;
85    let output = convert_to_cstring(output_folder.as_ref())?;
86
87    let return_code = unsafe { ffi::from_map16(input.as_ptr(), output.as_ptr()) };
88
89    if return_code == 0 {
90        Ok(())
91    } else {
92        Err(Error::Conversion(last_error()))
93    }
94}
95
96/// Converts from a folder tree of human-readable text files, generally generated by [from_map16], back to a binary map16 file.
97/// 
98/// # Examples
99/// 
100/// ```
101/// use human_readable_map16::to_map16;
102/// 
103/// to_map16("map16_folder", "output.map16");
104/// ```
105/// 
106/// # Panics
107/// 
108/// This function does not panic.
109/// 
110/// # Errors
111/// 
112/// Returns an error if any step of the conversion fails. This could be for many 
113/// reasons, the map16 folder not existing, formatting errors in the input text files,
114/// problems writing the output file, etc.
115/// 
116/// [Error::Conversion] should contain a fairly descriptive message if it 
117/// is returned. Other errors should rarely occur.
118pub fn to_map16<I: AsRef<Path>, O: AsRef<Path>>(input_folder: I, output_file: O) -> Result<()> {
119    let input = convert_to_cstring(input_folder.as_ref())?;
120    let output = convert_to_cstring(output_file.as_ref())?;
121
122    let return_code = unsafe { ffi::to_map16(input.as_ptr(), output.as_ptr()) };
123
124    if return_code == 0 {
125        Ok(())
126    } else {
127        Err(Error::Conversion(last_error()))
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use dir_assert::assert_paths;
134
135    use crate::{from_map16, to_map16};
136
137    #[test]
138    fn from_map16_works() {
139        from_map16("test_data/all.map16", "test_data/test").unwrap();
140
141        assert_paths!("test_data/all/global_pages", "test_data/test/global_pages");
142        assert_paths!("test_data/all/pipe_tiles", "test_data/test/pipe_tiles");
143        assert_paths!("test_data/all/tileset_group_specific_tiles", "test_data/test/tileset_group_specific_tiles");
144    }
145
146    #[test]
147    fn to_map16_works() {
148        to_map16("test_data/all", "test_data/test.map16").unwrap();
149    }
150
151    #[test]
152    #[should_panic]
153    fn to_map16_detects_errors() {
154        to_map16("test_data/incorrect", "test_data/incorrect.map16").unwrap();
155    }
156}