diff --git a/.github/workflows/publish-wiki.yml b/.github/workflows/publish-wiki.yml new file mode 100644 index 0000000..c420529 --- /dev/null +++ b/.github/workflows/publish-wiki.yml @@ -0,0 +1,18 @@ +name: Publish wiki +on: + push: + branches: [main] + paths: + - wiki/** + - .github/workflows/publish-wiki.yml +concurrency: + group: publish-wiki + cancel-in-progress: true +permissions: + contents: write +jobs: + publish-wiki: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: Andrew-Chen-Wang/github-wiki-action@v4 \ No newline at end of file diff --git a/Makefile b/Makefile index a97e613..6b06155 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,8 @@ format: tests: lint cargo test make remove-xlsx + ./target/debug/documentation + cmp functions.md wiki/functions.md || exit 1 remove-xlsx: rm -f xlsx/hello-calc.xlsx @@ -21,6 +23,7 @@ clean: remove-xlsx rm -f cargo-test-* rm -f base/cargo-test-* rm -f xlsx/cargo-test-* + rm functions.md coverage: diff --git a/base/src/functions/mod.rs b/base/src/functions/mod.rs index a7849ab..b870ec9 100644 --- a/base/src/functions/mod.rs +++ b/base/src/functions/mod.rs @@ -1,4 +1,5 @@ use core::fmt; +use std::array::IntoIter; use crate::{ calc_result::CalcResult, @@ -244,6 +245,206 @@ pub enum Function { Subtotal, } +impl Function { + pub fn into_iter() -> IntoIter { + [ + Function::And, + Function::False, + Function::If, + Function::Iferror, + Function::Ifna, + Function::Ifs, + Function::Not, + Function::Or, + Function::Switch, + Function::True, + Function::Xor, + Function::Sin, + Function::Cos, + Function::Tan, + Function::Asin, + Function::Acos, + Function::Atan, + Function::Sinh, + Function::Cosh, + Function::Tanh, + Function::Asinh, + Function::Acosh, + Function::Atanh, + Function::Abs, + Function::Pi, + Function::Sqrt, + Function::Sqrtpi, + Function::Atan2, + Function::Power, + Function::Max, + Function::Min, + Function::Product, + Function::Rand, + Function::Randbetween, + Function::Round, + Function::Rounddown, + Function::Roundup, + Function::Sum, + Function::Sumif, + Function::Sumifs, + Function::Choose, + Function::Column, + Function::Columns, + Function::Index, + Function::Indirect, + Function::Hlookup, + Function::Lookup, + Function::Match, + Function::Offset, + Function::Row, + Function::Rows, + Function::Vlookup, + Function::Xlookup, + Function::Concatenate, + Function::Exact, + Function::Value, + Function::T, + Function::Valuetotext, + Function::Concat, + Function::Find, + Function::Left, + Function::Len, + Function::Lower, + Function::Mid, + Function::Right, + Function::Search, + Function::Text, + Function::Trim, + Function::Upper, + Function::Isnumber, + Function::Isnontext, + Function::Istext, + Function::Islogical, + Function::Isblank, + Function::Iserr, + Function::Iserror, + Function::Isna, + Function::Na, + Function::Isref, + Function::Isodd, + Function::Iseven, + Function::ErrorType, + Function::Isformula, + Function::Type, + Function::Sheet, + Function::Average, + Function::Averagea, + Function::Averageif, + Function::Averageifs, + Function::Count, + Function::Counta, + Function::Countblank, + Function::Countif, + Function::Countifs, + Function::Maxifs, + Function::Minifs, + Function::Year, + Function::Day, + Function::Month, + Function::Eomonth, + Function::Date, + Function::Edate, + Function::Today, + Function::Now, + Function::Pmt, + Function::Pv, + Function::Rate, + Function::Nper, + Function::Fv, + Function::Ppmt, + Function::Ipmt, + Function::Npv, + Function::Mirr, + Function::Irr, + Function::Xirr, + Function::Xnpv, + Function::Rept, + Function::Textafter, + Function::Textbefore, + Function::Textjoin, + Function::Substitute, + Function::Ispmt, + Function::Rri, + Function::Sln, + Function::Syd, + Function::Nominal, + Function::Effect, + Function::Pduration, + Function::Tbillyield, + Function::Tbillprice, + Function::Tbilleq, + Function::Dollarde, + Function::Dollarfr, + Function::Ddb, + Function::Db, + Function::Cumprinc, + Function::Cumipmt, + Function::Besseli, + Function::Besselj, + Function::Besselk, + Function::Bessely, + Function::Erf, + Function::ErfPrecise, + Function::Erfc, + Function::ErfcPrecise, + Function::Bin2dec, + Function::Bin2hex, + Function::Bin2oct, + Function::Dec2Bin, + Function::Dec2hex, + Function::Dec2oct, + Function::Hex2bin, + Function::Hex2dec, + Function::Hex2oct, + Function::Oct2bin, + Function::Oct2dec, + Function::Oct2hex, + Function::Bitand, + Function::Bitlshift, + Function::Bitor, + Function::Bitrshift, + Function::Bitxor, + Function::Complex, + Function::Imabs, + Function::Imaginary, + Function::Imargument, + Function::Imconjugate, + Function::Imcos, + Function::Imcosh, + Function::Imcot, + Function::Imcsc, + Function::Imcsch, + Function::Imdiv, + Function::Imexp, + Function::Imln, + Function::Imlog10, + Function::Imlog2, + Function::Impower, + Function::Improduct, + Function::Imreal, + Function::Imsec, + Function::Imsech, + Function::Imsin, + Function::Imsinh, + Function::Imsqrt, + Function::Imsub, + Function::Imsum, + Function::Imtan, + Function::Convert, + Function::Delta, + Function::Gestep, + Function::Subtotal, + ] + .into_iter() + } +} + impl Function { /// Some functions in Excel like CONCAT are stringified as `_xlfn.CONCAT`. pub fn to_xlsx_string(&self) -> String { @@ -708,7 +909,23 @@ impl fmt::Display for Function { } } +/// Documentation for one function +pub struct Documentation { + pub name: String, +} + impl Model { + /// Produces documentation for all implemented functions + pub fn documentation() -> Vec { + let mut doc = Vec::new(); + for function in Function::into_iter() { + doc.push(Documentation { + name: function.to_string(), + }); + } + doc + } + pub(crate) fn evaluate_function( &mut self, kind: &Function, @@ -928,3 +1145,66 @@ impl Model { } } } + +#[cfg(test)] +mod tests { + use std::{ + fs::File, + io::{BufRead, BufReader}, + }; + + use crate::functions::Function; + + #[test] + fn function_iterator() { + // This checks that the number of functions in the enum is the same + // as the number of functions in the Iterator. + + // This is tricky. In Rust we cannot loop over all the members of an enum. + // There are alternatives like using an external crate like strum. + // But I am not in the mood for that. + + // What we do here is read this file , extract the functions in the enum + // and check they are the same as in the iterator + let file = File::open("src/functions/mod.rs").unwrap(); + let reader = BufReader::new(file); + let mut start = false; + let mut list = Vec::new(); + + for line in reader.lines() { + let text = line.unwrap(); + let text = text.trim().trim_end_matches(','); + if text == "pub enum Function {" { + start = true; + continue; + } + if start { + if text == "}" { + break; + } + if text.starts_with("//") { + // skip comments + continue; + } + if text.is_empty() { + // skip empty lines + continue; + } + list.push(text.to_owned()); + } + } + // We make a list with their functions names, but we escape ".": ERROR.TYPE => ERRORTYPE + let iter_list = Function::into_iter() + .map(|f| format!("{}", f).replace('.', "")) + .collect::>(); + + let len = iter_list.len(); + + assert_eq!(list.len(), len); + // We still need to check there are no duplicates. This will fail if a function in iter_list + // is included twice and one is missing + for function in list { + assert!(iter_list.contains(&function.to_uppercase())); + } + } +} diff --git a/wiki/functions.md b/wiki/functions.md new file mode 100644 index 0000000..507113f --- /dev/null +++ b/wiki/functions.md @@ -0,0 +1,192 @@ +AND +FALSE +IF +IFERROR +IFNA +IFS +NOT +OR +SWITCH +TRUE +XOR +SIN +COS +TAN +ASIN +ACOS +ATAN +SINH +COSH +TANH +ASINH +ACOSH +ATANH +ABS +PI +SQRT +SQRTPI +ATAN2 +POWER +MAX +MIN +PRODUCT +RAND +RANDBETWEEN +ROUND +ROUNDDOWN +ROUNDUP +SUM +SUMIF +SUMIFS +CHOOSE +COLUMN +COLUMNS +INDEX +INDIRECT +HLOOKUP +LOOKUP +MATCH +OFFSET +ROW +ROWS +VLOOKUP +XLOOKUP +CONCATENATE +EXACT +VALUE +T +VALUETOTEXT +CONCAT +FIND +LEFT +LEN +LOWER +MID +RIGHT +SEARCH +TEXT +TRIM +UPPER +ISNUMBER +ISNONTEXT +ISTEXT +ISLOGICAL +ISBLANK +ISERR +ISERROR +ISNA +NA +ISREF +ISODD +ISEVEN +ERROR.TYPE +ISFORMULA +TYPE +SHEET +AVERAGE +AVERAGEA +AVERAGEIF +AVERAGEIFS +COUNT +COUNTA +COUNTBLANK +COUNTIF +COUNTIFS +MAXIFS +MINIFS +YEAR +DAY +MONTH +EOMONTH +DATE +EDATE +TODAY +NOW +PMT +PV +RATE +NPER +FV +PPMT +IPMT +NPV +MIRR +IRR +XIRR +XNPV +REPT +TEXTAFTER +TEXTBEFORE +TEXTJOIN +SUBSTITUTE +ISPMT +RRI +SLN +SYD +NOMINAL +EFFECT +PDURATION +TBILLYIELD +TBILLPRICE +TBILLEQ +DOLLARDE +DOLLARFR +DDB +DB +CUMPRINC +CUMIPMT +BESSELI +BESSELJ +BESSELK +BESSELY +ERF +ERF.PRECISE +ERFC +ERFC.PRECISE +BIN2DEC +BIN2HEX +BIN2OCT +DEC2BIN +DEC2HEX +DEC2OCT +HEX2BIN +HEX2DEC +HEX2OCT +OCT2BIN +OCT2DEC +OCT2HEX +BITAND +BITLSHIFT +BITOR +BITRSHIFT +BITXOR +COMPLEX +IMABS +IMAGINARY +IMARGUMENT +IMCONJUGATE +IMCOS +IMCOSH +IMCOT +IMCSC +IMCSCH +IMDIV +IMEXP +IMLN +IMLOG10 +IMLOG2 +IMPOWER +IMPRODUCT +IMREAL +IMSEC +IMSECH +IMSIN +IMSINH +IMSQRT +IMSUB +IMSUM +IMTAN +CONVERT +DELTA +GESTEP +SUBTOTAL \ No newline at end of file diff --git a/xlsx/Cargo.toml b/xlsx/Cargo.toml index 5a832c0..8a47071 100644 --- a/xlsx/Cargo.toml +++ b/xlsx/Cargo.toml @@ -34,3 +34,8 @@ path = "src/lib.rs" [[bin]] name = "test" path = "src/bin/test.rs" + + +[[bin]] +name = "documentation" +path = "src/bin/documentation.rs" \ No newline at end of file diff --git a/xlsx/src/bin/documentation.rs b/xlsx/src/bin/documentation.rs new file mode 100644 index 0000000..ec19846 --- /dev/null +++ b/xlsx/src/bin/documentation.rs @@ -0,0 +1,17 @@ +//! Produces documentation of all the implemented IronCalc functions +//! and saves the result to functions.md +//! +//! Usage: documentation + +use std::fs; + +use ironcalc_base::model::Model; + +fn main() { + let mut doc = Vec::new(); + for function in Model::documentation() { + doc.push(function.name); + } + let data = doc.join("\n"); + fs::write("functions.md", data).expect("Unable to write file"); +}