Browse Source

Automatically add examples to the documentation

Bzero 1 year ago
parent
commit
0210b9cc52
5 changed files with 128 additions and 18 deletions
  1. 1 0
      Cargo.lock
  2. 1 0
      numbat/Cargo.toml
  3. 121 17
      numbat/examples/inspect.rs
  4. 4 0
      numbat/src/markup.rs
  5. 1 1
      numbat/src/resolver.rs

+ 1 - 0
Cargo.lock

@@ -1089,6 +1089,7 @@ dependencies = [
  "num-traits",
  "numbat-exchange-rates",
  "once_cell",
+ "percent-encoding",
  "plotly",
  "pretty_dtoa",
  "rand",

+ 1 - 0
numbat/Cargo.toml

@@ -49,6 +49,7 @@ glob = "0.3"
 insta = "1.34.0"
 once_cell = "1.19.0"
 criterion = { version = "0.5", features = ["html_reports"] }
+percent-encoding = "2.3.1"
 
 [[bench]]
 name = "prelude"

+ 121 - 17
numbat/examples/inspect.rs

@@ -1,5 +1,10 @@
 use itertools::Itertools;
-use numbat::{module_importer::FileSystemImporter, resolver::CodeSource, Context};
+use numbat::markup::plain_text_format;
+use numbat::module_importer::FileSystemImporter;
+use numbat::pretty_print::PrettyPrint;
+use numbat::resolver::CodeSource;
+use numbat::Context;
+use percent_encoding;
 use std::path::Path;
 
 const AUTO_GENERATED_HINT: &str = "<!-- NOTE! This file is auto-generated -->";
@@ -57,19 +62,7 @@ fn inspect_functions_in_module(ctx: &Context, module: String) {
         }
 
         if let Some(ref description_raw) = description {
-            let description_raw = description_raw.trim().to_string();
-
-            // Replace $..$ with \\( .. \\) for mdbook.
-            let mut description = String::new();
-            for (i, part) in description_raw.split('$').enumerate() {
-                if i % 2 == 0 {
-                    description.push_str(part);
-                } else {
-                    description.push_str("\\\\( ");
-                    description.push_str(part);
-                    description.push_str(" \\\\)");
-                }
-            }
+            let description = replace_equation_delimiters(description_raw.trim().to_string());
 
             if description.ends_with('.') {
                 println!("{description}");
@@ -86,15 +79,126 @@ fn inspect_functions_in_module(ctx: &Context, module: String) {
         println!("{signature}");
         println!("```");
         println!();
+
+        if !examples.is_empty() {
+            println!("<details>");
+            println!("<summary>Examples</summary>");
+            println!();
+        }
+
+        for (example_code, example_description) in examples {
+            let mut example_ctx = prepare_context();
+            let _result = example_ctx
+                .interpret("use prelude", CodeSource::Internal)
+                .unwrap();
+
+            let extra_import = if !example_ctx
+                .resolver()
+                .imported_modules
+                .contains(&module_path)
+            {
+                format!("use {}\n", module)
+            } else {
+                "".into()
+            };
+            let _result = example_ctx
+                .interpret(&extra_import, CodeSource::Internal)
+                .unwrap();
+
+            if let Ok((statements, results)) =
+                example_ctx.interpret(&example_code, CodeSource::Internal)
+            {
+                //Format the example input
+                let example_input = format!(">>> {}", example_code);
+
+                //Encode the example url
+                let url_code = extra_import + &example_code;
+                let example_url = format!(
+                    "https://numbat.dev/?q={}",
+                    percent_encoding::utf8_percent_encode(
+                        &url_code,
+                        percent_encoding::NON_ALPHANUMERIC
+                    )
+                );
+
+                //Assemble the example output
+                let mut example_output = String::new();
+                example_output += "\n";
+
+                for statement in &statements {
+                    example_output += &plain_text_format(&statement.pretty_print(), true);
+                    example_output += "\n\n";
+                }
+
+                let result_markup = results.to_markup(
+                    statements.last(),
+                    &example_ctx.dimension_registry(),
+                    true,
+                    true,
+                );
+                example_output += &plain_text_format(&result_markup, false);
+
+                if results.is_value() {
+                    example_output += "\n";
+                }
+
+                //Print the example
+                if let Some(example_description) = example_description {
+                    println!(
+                        "* {}\n\n  <a href=\"{}\"><i class=\"fa fa-play\"></i> Run this example</a>",
+                        replace_equation_delimiters(example_description),
+                        example_url
+                    );
+                } else {
+                    println!(
+                        "* <a href=\"{}\"><i class=\"fa fa-play\"></i> Run this example</a>\n",
+                        example_url
+                    );
+                }
+
+                println!("  ```nbt");
+                for l in example_input.lines() {
+                    println!("    {}", l);
+                }
+                for l in example_output.lines() {
+                    println!("    {}", l);
+                }
+                println!("  ```");
+            } else {
+                eprintln!(
+                        "Warning: Example \"{example_code}\" of function {fn_name} did not run successfully."
+                    );
+            }
+        }
+        println!("</details>");
+        println!();
     }
 }
 
-fn main() {
-    let module_path = Path::new(&std::env::var_os("CARGO_MANIFEST_DIR").unwrap()).join("modules");
+// Replace $..$ with \\( .. \\) for mdbook.
+fn replace_equation_delimiters(text_in: String) -> String {
+    let mut text_out = String::new();
+    for (i, part) in text_in.split('$').enumerate() {
+        if i % 2 == 0 {
+            text_out.push_str(part);
+        } else {
+            text_out.push_str("\\\\( ");
+            text_out.push_str(part);
+            text_out.push_str(" \\\\)");
+        }
+    }
+    return text_out;
+}
 
+fn prepare_context() -> Context {
+    let module_path = Path::new(&std::env::var_os("CARGO_MANIFEST_DIR").unwrap()).join("modules");
     let mut importer = FileSystemImporter::default();
     importer.add_path(module_path);
-    let mut ctx = Context::new(importer);
+    return Context::new(importer);
+}
+
+fn main() {
+    let mut ctx = prepare_context();
     let _result = ctx.interpret("use all", CodeSource::Internal).unwrap();
 
     let mut args = std::env::args();

+ 4 - 0
numbat/src/markup.rs

@@ -209,3 +209,7 @@ impl Formatter for PlainTextFormatter {
         text.clone()
     }
 }
+
+pub fn plain_text_format(m: &Markup, indent: bool) -> String {
+    PlainTextFormatter {}.format(m, indent)
+}

+ 1 - 1
numbat/src/resolver.rs

@@ -49,7 +49,7 @@ pub struct Resolver {
     pub files: SimpleFiles<String, String>,
     text_code_source_count: usize,
     internal_code_source_count: usize,
-    imported_modules: Vec<ModulePath>,
+    pub imported_modules: Vec<ModulePath>,
     codesources: HashMap<usize, CodeSource>,
 }