Skip to content

Commit 5152831

Browse files
authored
--collect-as and --name-inputs (#162)
- feature: New flags `--collect-as` and `--name-inputs` have been implemented to aggregate data from arbitrary numbers of inputs (e.g.using shell wildcard syntax).
1 parent 91aabbc commit 5152831

File tree

9 files changed

+290
-28
lines changed

9 files changed

+290
-28
lines changed

docs/command-line.md

+100-5
Original file line numberDiff line numberDiff line change
@@ -145,12 +145,107 @@ When several inputs are listed, names from earlier inputs become
145145
available to later inputs, but the content that will be rendered is
146146
that of the final input.
147147

148-
The common use case is a final input which contains logic to inspect
149-
or process data provided by the previous inputs (potentially coming in
150-
from previous processing on stdin).
148+
So for instance:
151149

152-
If you want to render contents of earlier inputs verbatim, you need a
153-
named input to provide a name for that content which you can then use.
150+
a.eu
151+
```eu
152+
x: 4
153+
y: 8
154+
```
155+
156+
b.eu
157+
158+
```eu
159+
z: x + y
160+
```
161+
162+
```sh
163+
eu a.eu b.eu
164+
```
165+
166+
will output
167+
168+
```yaml
169+
z: 12
170+
```
171+
172+
The common use cases are:
173+
- a final input containing logic to inspect or process data
174+
provided by previous inputs
175+
- a final input which uses functions defined in earlier inputs to
176+
process data provided in previous inputs
177+
178+
If you want to __render_ contents of earlier inputs, you need a named
179+
input to provide a name for that content which you can then use.
180+
181+
For instance:
182+
183+
```sh
184+
eu r=a.eu b.eu -e r
185+
```
186+
187+
will render:
188+
189+
```yaml
190+
x: 4
191+
y: 8
192+
```
193+
194+
#### `--collect-as` and `--name-inputs`
195+
196+
Occasionally it is useful to aggregate data from an arbitrary number
197+
of sources files, typically specified by shell wildcards. To refer to
198+
this data we need to introduce a name for the collection of data.
199+
200+
This is what the command line switch `--collect-as` / `-C` is for.
201+
202+
```sh
203+
eu --collect-as inputs *.eu
204+
```
205+
206+
...will render:
207+
208+
```yaml
209+
inputs:
210+
- x: 4
211+
y: 8
212+
- z: 12
213+
```
214+
215+
It is common to use `-e` to select an item to render:
216+
217+
```sh
218+
eu -C *.eu -e 'inputs head'
219+
```
220+
221+
...renders:
222+
223+
```yaml
224+
x: 4
225+
y: 8
226+
```
227+
228+
If you are likely to need to refer to inputs by name, you can add
229+
`--name-inputs` / `-N` to pass inputs as a block instead of a list:
230+
231+
```sh
232+
eu --collect-as inputs *.eu
233+
```
234+
235+
...renders:
236+
237+
```yaml
238+
inputs:
239+
a.eu:
240+
x: 4
241+
y: 8
242+
b.eu:
243+
z: 12
244+
```
245+
246+
This makes it possible to easier to invoke specific functions from
247+
named inputs although you will need single-quote name syntax to use
248+
the generated names which contain '.'s.
154249

155250
## Outputs
156251

src/core/doc.rs

+13
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,16 @@ pub struct DeclarationDocumentation {
1212
/// Documentation metadata
1313
pub doc: String,
1414
}
15+
16+
impl DeclarationDocumentation {
17+
/// Construct a documentation item one level deeper under a named
18+
/// root
19+
pub fn under(&self, root: &str) -> Self {
20+
let mut path = vec![root.to_string()];
21+
path.extend(self.path.clone());
22+
Self {
23+
path,
24+
..self.clone()
25+
}
26+
}
27+
}

src/core/target.rs

+10
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,14 @@ impl Target {
6666
pub fn path(&self) -> &Vec<String> {
6767
&self.path
6868
}
69+
70+
/// Return a version of the target where the path is
71+
pub fn under(&self, root: &str) -> Self {
72+
let mut path = vec![root.to_string()];
73+
path.extend(self.path.clone());
74+
Self {
75+
path,
76+
..self.clone()
77+
}
78+
}
6979
}

src/core/unit.rs

+39
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,45 @@ impl TranslationUnit {
4242
})
4343
}
4444

45+
/// Combine the units as a unit containing a single list
46+
///
47+
/// No targets or
48+
pub fn listify<'a, I>(units: I) -> Result<Self, CoreError>
49+
where
50+
I: Iterator<Item = &'a Self>,
51+
{
52+
Ok(TranslationUnit {
53+
expr: acore::list(units.map(|u| u.expr.clone()).collect()),
54+
..Default::default()
55+
})
56+
}
57+
58+
/// Combine the units as a unit containing a single block with
59+
/// each units keys derived from existing name or generated name
60+
/// if required
61+
pub fn blockify<'a, I, S>(keys: &'a [S], units: I) -> Result<Self, CoreError>
62+
where
63+
I: Iterator<Item = &'a Self>,
64+
S: AsRef<str>,
65+
{
66+
let mut targets = HashSet::new();
67+
let mut docs = vec![];
68+
let mut entries = vec![];
69+
70+
for (k, u) in keys.iter().zip(units) {
71+
let key = k.as_ref();
72+
targets.extend(u.targets.iter().map(|t| t.under(key)));
73+
docs.extend(u.docs.iter().map(|d| d.under(key)));
74+
entries.push((key.to_string(), u.expr.clone()));
75+
}
76+
77+
Ok(TranslationUnit {
78+
expr: acore::block(entries),
79+
targets,
80+
docs,
81+
})
82+
}
83+
4584
/// Return a new unit which merges the expression and targets etc.
4685
/// of the specified units.
4786
pub fn merge<I>(units: I) -> Result<Self, CoreError>

src/driver/options.rs

+61-11
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ pub struct EucalyptOptions {
5959
#[structopt(short, long)]
6060
evaluate: Option<String>,
6161

62+
#[structopt(short = "c", long)]
63+
collect_as: Option<String>,
64+
65+
#[structopt(short = "N", long)]
66+
name_inputs: bool,
67+
6268
/// Turn on debug features
6369
#[structopt(short, long)]
6470
debug: bool,
@@ -92,8 +98,16 @@ pub struct EucalyptOptions {
9298
#[structopt(flatten)]
9399
stg_settings: StgSettings,
94100

95-
/// Source code / data inputs (in order)
96-
inputs: Vec<Input>,
101+
/// Explicit source code / data inputs (in order)
102+
explicit_inputs: Vec<Input>,
103+
104+
/// Prologue inputs (inputs added prior to explicit inputs)
105+
#[structopt(skip)]
106+
prologue_inputs: Vec<Input>,
107+
108+
/// Epilogue inputs (inputs added after explicit inputs)
109+
#[structopt(skip)]
110+
epilogue_inputs: Vec<Input>,
97111
}
98112

99113
impl EucalyptOptions {
@@ -114,8 +128,8 @@ impl EucalyptOptions {
114128
self
115129
}
116130

117-
pub fn with_inputs(mut self, inputs: Vec<Input>) -> Self {
118-
self.inputs = inputs;
131+
pub fn with_explicit_inputs(mut self, inputs: Vec<Input>) -> Self {
132+
self.explicit_inputs = inputs;
119133
self
120134
}
121135

@@ -193,7 +207,19 @@ impl EucalyptOptions {
193207

194208
// inputs
195209
explanation.push_str("Inputs:\n");
196-
for i in &self.inputs {
210+
for i in &self.prologue_inputs {
211+
explanation.push_str(&format!(" • {}\n", i));
212+
}
213+
if let Some(name) = self.collection() {
214+
explanation.push_str(&format!(" • {}=\n", name));
215+
}
216+
for i in &self.explicit_inputs {
217+
if self.collection().is_some() {
218+
explanation.push_str(" ");
219+
}
220+
explanation.push_str(&format!(" • {}\n", i));
221+
}
222+
for i in &self.epilogue_inputs {
197223
explanation.push_str(&format!(" • {}\n", i));
198224
}
199225
explanation.push('\n');
@@ -316,6 +342,14 @@ impl EucalyptOptions {
316342
self.evaluate.as_ref()
317343
}
318344

345+
pub fn collection(&self) -> Option<&String> {
346+
self.collect_as.as_ref()
347+
}
348+
349+
pub fn name_inputs(&self) -> bool {
350+
self.name_inputs
351+
}
352+
319353
pub fn explain(&self) -> bool {
320354
self.command.explain
321355
}
@@ -372,8 +406,24 @@ impl EucalyptOptions {
372406
self.debug
373407
}
374408

375-
pub fn inputs(&self) -> &[Input] {
376-
&self.inputs
409+
pub fn prologue_inputs(&self) -> &[Input] {
410+
&self.prologue_inputs
411+
}
412+
413+
pub fn explicit_inputs(&self) -> &[Input] {
414+
&self.explicit_inputs
415+
}
416+
417+
pub fn epilogue_inputs(&self) -> &[Input] {
418+
&self.epilogue_inputs
419+
}
420+
421+
pub fn inputs(&self) -> Vec<Input> {
422+
let mut inputs = vec![];
423+
inputs.extend_from_slice(&self.prologue_inputs.as_slice());
424+
inputs.extend_from_slice(&self.explicit_inputs.as_slice());
425+
inputs.extend_from_slice(&self.epilogue_inputs.as_slice());
426+
inputs
377427
}
378428

379429
pub fn lib_path(&self) -> &[PathBuf] {
@@ -406,8 +456,8 @@ impl EucalyptOptions {
406456

407457
/// Prepend an input if it is not already in the input list
408458
fn prepend_input(&mut self, input: Input) {
409-
if !self.inputs.iter().any(|i| i == &input) {
410-
self.inputs.insert(0, input);
459+
if !self.explicit_inputs.iter().any(|i| i == &input) {
460+
self.prologue_inputs.insert(0, input);
411461
}
412462
}
413463

@@ -445,7 +495,7 @@ impl EucalyptOptions {
445495
self.lib_path.insert(0, std::env::current_dir()?);
446496

447497
// For pipes, default json stdin
448-
if self.inputs.is_empty() && !atty::is(Stream::Stdin) {
498+
if self.explicit_inputs.is_empty() && !atty::is(Stream::Stdin) {
449499
self.prepend_input(Input::from_str("-").unwrap());
450500
}
451501

@@ -484,7 +534,7 @@ impl EucalyptOptions {
484534

485535
// Add CLI evaluand as final input if it exists
486536
if self.evaluate.is_some() {
487-
self.inputs.push(Input::from(Locator::Cli(
537+
self.epilogue_inputs.push(Input::from(Locator::Cli(
488538
self.evaluate.as_ref().unwrap().to_string(),
489539
)));
490540
}

src/driver/prepare.rs

+17-5
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@ pub fn prepare(
2626
loader: &mut SourceLoader,
2727
stats: &mut Timings,
2828
) -> Result<Command, EucalyptError> {
29+
let inputs = opt.inputs();
30+
2931
{
3032
let t = Instant::now();
3133

32-
for i in opt.inputs() {
34+
for i in &inputs {
3335
loader.load(i)?;
3436
}
3537

@@ -49,18 +51,28 @@ pub fn prepare(
4951
{
5052
let t = Instant::now();
5153

52-
for i in opt.inputs() {
54+
for i in &inputs {
5355
loader.translate(i)?;
5456
}
5557

5658
stats.record("translate", t.elapsed());
5759
}
5860

59-
// Merge into a single core - before or after cooking?
61+
// Optionally collect explicit inputs together
6062
{
6163
let t = Instant::now();
6264

63-
loader.merge_units(opt.inputs())?;
65+
if let Some(collection_name) = opt.collection() {
66+
loader.collect_and_merge_units(
67+
&collection_name,
68+
opt.name_inputs(),
69+
opt.prologue_inputs(),
70+
opt.explicit_inputs(),
71+
opt.epilogue_inputs(),
72+
)?;
73+
} else {
74+
loader.merge_units(&inputs)?;
75+
}
6476

6577
stats.record("merge", t.elapsed());
6678
}
@@ -86,7 +98,7 @@ pub fn prepare(
8698
}
8799

88100
println!("\nFrom inputs\n");
89-
for i in opt.inputs() {
101+
for i in &inputs {
90102
println!(" - {}", i);
91103
}
92104
return Ok(Command::Exit);

0 commit comments

Comments
 (0)