summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2020-05-24 08:13:02 +0000
committerLars Wirzenius <liw@liw.fi>2020-05-24 08:13:02 +0000
commit738c90cec0fd257334ec9e6e61314277286ea4e9 (patch)
tree0f09e6d77880b3673d6760e78642280d5de0c662
parent576aa3bcbb6615f4fef73264029fc22af78fdfdc (diff)
parentf039dc7fcb3e9dd20166ecfe64d5428a812d5990 (diff)
downloadsubplot-738c90cec0fd257334ec9e6e61314277286ea4e9.tar.gz
Merge branch 'kinnison/fix-52' into 'master'
Support JSON output in `sp-meta` Closes #52 See merge request larswirzenius/subplot!45
-rw-r--r--src/bin/sp-meta.rs114
-rw-r--r--subplot.md34
-rw-r--r--subplot.py13
-rw-r--r--subplot.yaml6
-rw-r--r--templates/python/template.py14
5 files changed, 163 insertions, 18 deletions
diff --git a/src/bin/sp-meta.rs b/src/bin/sp-meta.rs
index cfa3b3d..49f6173 100644
--- a/src/bin/sp-meta.rs
+++ b/src/bin/sp-meta.rs
@@ -1,43 +1,121 @@
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<Self, Self::Err> {
+ 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,
}
-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)?;
+#[derive(Serialize)]
+struct Metadata {
+ sources: Vec<String>,
+ title: String,
+ binding_files: Vec<String>,
+ function_files: Vec<String>,
+ bibliographies: Vec<String>,
+ scenarios: Vec<String>,
+}
- 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<Self, Self::Error> {
+ 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)?;
- for scen in doc.scenarios()? {
- println!("scenario {}", scen.title());
+ match opt.output_format {
+ OutputFormat::Plain => meta.write_out(),
+ OutputFormat::Json => println!("{}", serde_json::to_string_pretty(&meta)?),
}
+
Ok(())
}
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 "(?P<keyword>given|when|then) (?P<name>.+)" 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()