From 6959f556ccfa0ccfd409d36bfa4d40e004de0040 Mon Sep 17 00:00:00 2001 From: Daniel Silverstone Date: Thu, 21 May 2020 17:18:54 +0100 Subject: refactor: make sp-meta structure based ready for json Signed-off-by: Daniel Silverstone --- src/bin/sp-meta.rs | 87 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 69 insertions(+), 18 deletions(-) diff --git a/src/bin/sp-meta.rs b/src/bin/sp-meta.rs index cfa3b3d..cc4a7bd 100644 --- a/src/bin/sp-meta.rs +++ b/src/bin/sp-meta.rs @@ -1,5 +1,7 @@ use anyhow::Result; +use std::convert::TryFrom; use std::path::{Path, PathBuf}; + use structopt::StructOpt; use subplot::Document; @@ -12,32 +14,81 @@ struct Opt { filename: PathBuf, } -fn main() -> Result<()> { - let opt = Opt::from_args(); - let basedir = subplot::get_basedir_from(&opt.filename)?; - let mut doc = Document::from_file(&basedir, &opt.filename)?; +struct Metadata { + sources: Vec, + title: String, + binding_files: Vec, + function_files: Vec, + bibliographies: Vec, + scenarios: Vec, +} - for filename in doc.sources() { - println!("source: {}", filename.display()); +impl TryFrom<&mut Document> for Metadata { + type Error = subplot::SubplotError; + fn try_from(doc: &mut Document) -> std::result::Result { + let sources: Vec<_> = doc + .sources() + .into_iter() + .map(|p| filename(Some(&p))) + .collect(); + let title = doc.meta().title().to_owned(); + let binding_files = doc + .meta() + .bindings_filenames() + .into_iter() + .map(|p| filename(Some(&p))) + .collect(); + let function_files = doc + .meta() + .functions_filenames() + .into_iter() + .map(|p| filename(Some(&p))) + .collect(); + let bibliographies = doc + .meta() + .bibliographies() + .into_iter() + .map(|p| filename(Some(&p))) + .collect(); + let scenarios = doc + .scenarios()? + .into_iter() + .map(|s| s.title().to_owned()) + .collect(); + Ok(Self { + sources, + title, + binding_files, + function_files, + bibliographies, + scenarios, + }) } +} - println!("title: {}", doc.meta().title()); - - for filename in doc.meta().bindings_filenames() { - println!("bindings: {}", filename.display()); +impl Metadata { + fn write_list(v: &[String], prefix: &str) { + v.iter().for_each(|entry| println!("{}: {}", prefix, entry)) } - for filename in doc.meta().functions_filenames() { - println!("functions: {}", filename.display()); + fn write_out(&self) { + Self::write_list(&self.sources, "source"); + println!("title: {}", self.title); + Self::write_list(&self.binding_files, "bindings"); + Self::write_list(&self.function_files, "functions"); + Self::write_list(&self.bibliographies, "bibliography"); + Self::write_list(&self.scenarios, "scenario"); } +} - for bib in doc.meta().bibliographies().iter() { - println!("bibliography: {}", filename(Some(bib))); - } +fn main() -> Result<()> { + let opt = Opt::from_args(); + let basedir = subplot::get_basedir_from(&opt.filename)?; + let mut doc = Document::from_file(&basedir, &opt.filename)?; + let meta = Metadata::try_from(&mut doc)?; + + meta.write_out(); - for scen in doc.scenarios()? { - println!("scenario {}", scen.title()); - } Ok(()) } -- cgit v1.2.1 From bbdb40ac56d67ffb0d5a1cf582fe15740f1283ce Mon Sep 17 00:00:00 2001 From: Daniel Silverstone Date: Thu, 21 May 2020 17:26:25 +0100 Subject: feat: sp-meta now supports json output Signed-off-by: Daniel Silverstone --- src/bin/sp-meta.rs | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/bin/sp-meta.rs b/src/bin/sp-meta.rs index cc4a7bd..49f6173 100644 --- a/src/bin/sp-meta.rs +++ b/src/bin/sp-meta.rs @@ -1,19 +1,43 @@ use anyhow::Result; use std::convert::TryFrom; use std::path::{Path, PathBuf}; +use std::str::FromStr; +use serde::Serialize; use structopt::StructOpt; use subplot::Document; +#[derive(Debug)] +enum OutputFormat { + Plain, + Json, +} + +impl FromStr for OutputFormat { + type Err = String; + + fn from_str(s: &str) -> std::result::Result { + match s.to_ascii_lowercase().as_ref() { + "plain" => Ok(OutputFormat::Plain), + "json" => Ok(OutputFormat::Json), + _ => Err(format!("Unknown output format: `{}`", s)), + } + } +} + #[derive(Debug, StructOpt)] #[structopt(name = "sp-meta", about = "Show Subplot document metadata.")] struct Opt { - // Input filename. + /// Form that you want the output to take + #[structopt(short = "o", default_value = "plain", possible_values=&["plain", "json"])] + output_format: OutputFormat, + /// Input subplot document filename #[structopt(parse(from_os_str))] filename: PathBuf, } +#[derive(Serialize)] struct Metadata { sources: Vec, title: String, @@ -87,7 +111,10 @@ fn main() -> Result<()> { let mut doc = Document::from_file(&basedir, &opt.filename)?; let meta = Metadata::try_from(&mut doc)?; - meta.write_out(); + match opt.output_format { + OutputFormat::Plain => meta.write_out(), + OutputFormat::Json => println!("{}", serde_json::to_string_pretty(&meta)?), + } Ok(()) } -- cgit v1.2.1 From f039dc7fcb3e9dd20166ecfe64d5428a812d5990 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Sun, 24 May 2020 10:49:37 +0300 Subject: test(sp-meta): amend scenario to also test JSON output --- subplot.md | 34 ++++++++++++++++++++++++++++++++++ subplot.py | 13 +++++++++++++ subplot.yaml | 6 ++++++ templates/python/template.py | 14 ++++++++++++++ 4 files changed, 67 insertions(+) diff --git a/subplot.md b/subplot.md index ff1d702..74e23cf 100644 --- a/subplot.md +++ b/subplot.md @@ -1461,6 +1461,7 @@ and file f.py and file other.py and file foo.bib and file bar.bib +and file expected.json when I run sp-meta images.md then output matches /source: images.md/ and output matches /source: b.yaml/ @@ -1473,6 +1474,8 @@ and output matches /source: image.gif/ and output matches /bindings: b.yaml/ and output matches /bindings: other.yaml/ and output matches /functions: f.py/ +when I run sp-meta images.md -o json +then JSON output matches expected.json ~~~ @@ -1518,6 +1521,35 @@ bibliography: [foo.bib, bar.bib] } ~~~ +~~~{#expected.json .file .json} +{ + "title": "Document refers to external images", + "sources": [ + "images.md", + "b.yaml", + "other.yaml", + "f.py", + "other.py", + "foo.bib", + "bar.bib", + "image.gif" + ], + "binding_files": [ + "b.yaml", + "other.yaml" + ], + "function_files": [ + "f.py", + "other.py" + ], + "bibliographies": [ + "foo.bib", + "bar.bib" + ], + "scenarios": [] +} +~~~ + ## Embedded files @@ -1851,4 +1883,6 @@ functions: - subplot.py - runcmd.py - files.py +classes: +- json ... diff --git a/subplot.py b/subplot.py index 9e16168..bf6415f 100644 --- a/subplot.py +++ b/subplot.py @@ -1,3 +1,4 @@ +import json import os import re import subprocess @@ -54,6 +55,12 @@ def run_meta(ctx, filename=None): exit_code_is(ctx, 0) +def run_meta_json(ctx, filename=None): + meta = binary("sp-meta") + runcmd(ctx, [meta, filename, "-o", "json"]) + exit_code_is(ctx, 0) + + def run_pandoc_with_filter(ctx, filename=None, output=None): sp_filter = binary("sp-filter") runcmd(ctx, ["pandoc", "--filter", sp_filter, filename, "-o", output]) @@ -94,5 +101,11 @@ def cleanup_was_not_run(ctx, keyword=None, name=None): stdout_does_not_contain(ctx, pattern="\n cleanup: {} {}\n".format(keyword, name)) +def json_output_matches_file(ctx, filename=None): + actual = json.loads(ctx["stdout"]) + expected = json.load(open(filename)) + assert_dict_eq(actual, expected) + + def binary(basename): return os.path.join(srcdir, "target", "debug", basename) diff --git a/subplot.yaml b/subplot.yaml index f35cdef..da84a7d 100644 --- a/subplot.yaml +++ b/subplot.yaml @@ -25,6 +25,9 @@ - when: I run sp-meta {filename} function: run_meta +- when: I run sp-meta {filename} -o json + function: run_meta_json + - when: I run pandoc --filter sp-filter {filename} -o {output} function: run_pandoc_with_filter @@ -51,3 +54,6 @@ - then: cleanup for "(?Pgiven|when|then) (?P.+)" was not run function: cleanup_was_not_run regex: true + +- then: JSON output matches {filename} + function: json_output_matches_file diff --git a/templates/python/template.py b/templates/python/template.py index e430b1b..a1fa3ee 100644 --- a/templates/python/template.py +++ b/templates/python/template.py @@ -67,6 +67,20 @@ def assert_eq(a, b): def assert_ne(a, b): assert a != b, 'expected %r != %r' % (a, b) +# Check that two dict values are equal. +def assert_dict_eq(a, b): + assert isinstance(a, dict) + assert isinstance(b, dict) + for key in a: + assert key in b, f"exected {key} in both dicts" + av = a[key] + bv = b[key] + assert_eq(type(av), type(bv)) + if isinstance(av, list): + assert_eq(list(sorted(av)), list(sorted(bv))) + for key in b: + assert key in a, f"exected {key} in both dicts" + # Remember where we started from. The step functions may need to refer # to files there. srcdir = os.getcwd() -- cgit v1.2.1