Skip to content

Commit bc053bc

Browse files
committed
Give jk transform a flag for reading from stdin
The new flag --stdin-format instructs `jk transform` to read from stdin (as well as any files that are given as arguments). The possible values are `json` meaning expect JSON values, and `yaml` meaning expect a YAML stream. An alternative would be the conventional `-` denoting stdin; however, you would still need to provide a format. Defaulting to YAML would read a JSON value equally well, but (crucially) not multiple JSON values.
1 parent 676c9a5 commit bc053bc

File tree

4 files changed

+86
-49
lines changed

4 files changed

+86
-49
lines changed

std/cmd/generate.ts

Lines changed: 2 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as std from '../index';
22
import { WriteOptions } from '../write';
3+
import { splitPath, formatFromPath } from '../read';
34
import { ValidateFn } from './validate';
45
import { normaliseResult, formatError } from '../validation';
56

@@ -105,37 +106,6 @@ const nth = (n: number): string => {
105106
return n + (s[mod(v - 20, 10)] || s[v] || s[0]);
106107
};
107108

108-
function splitExtension(path: string): [string, string] {
109-
const parts = path.split('.');
110-
const ext = parts.pop()
111-
// When there's no extension, either there will be a single part (no
112-
// dots anywhere), or a path separator in the last part (a dot
113-
// somewhere before the last path segment)
114-
if (parts.length == 0 || ext.includes('/')) {
115-
return [ext, '']
116-
}
117-
return [parts.join(''), ext]
118-
}
119-
120-
function extension(path: string): string {
121-
return splitExtension(path)[1]
122-
}
123-
124-
function formatFromPath(path: string): std.Format {
125-
switch (extension(path)) {
126-
case 'yaml':
127-
case 'yml':
128-
return std.Format.YAML;
129-
case 'json':
130-
return std.Format.JSON;
131-
case 'hcl':
132-
case 'tf':
133-
return std.Format.HCL;
134-
default:
135-
return std.Format.JSON;
136-
}
137-
}
138-
139109
const isString = (s: any): boolean => typeof s === 'string' || s instanceof String;
140110

141111
// represents a file spec that has its promise resolved, if necessary
@@ -218,7 +188,7 @@ function forceFormat(forced: OutputFormat, files: RealisedFile[]) {
218188
file.format = outputFormatToFormat[forced];
219189
break;
220190
}
221-
const [p, ext] = splitExtension(path);
191+
const [p, ext] = splitPath(path);
222192
if (ext !== '') {
223193
file.path = [p, forced].join('.');
224194
}

std/cmd/transform.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
1-
import { Format, Overwrite } from '../index';
1+
import { Format, Overwrite, read, stdin } from '../index';
22
import * as host from '@jkcfg/std/internal/host'; // magic module
33
import * as param from '../param';
44
import { generate, File, GenerateParams, maybeSetFormat } from './generate';
5-
import { valuesFormatFromPath } from '../read';
5+
import { valuesFormatFromPath, valuesFormatFromExtension } from '../read';
66

77
type TransformFn = (value: any) => any | void;
88

9-
const inputParams: GenerateParams = {
9+
const generateParams: GenerateParams = {
1010
stdout: param.Boolean('jk.transform.stdout', false),
1111
overwrite: param.Boolean('jk.transform.overwrite', false) ? Overwrite.Write : Overwrite.Err,
1212
};
13-
maybeSetFormat(inputParams, param.String('jk.generate.format', undefined)); // NB jk.generate. param
13+
maybeSetFormat(generateParams, param.String('jk.generate.format', undefined)); // NB jk.generate. param
1414

1515
// If we're told to overwrite, we need to be able to write to the
1616
// files mentioned on the command-line; but not otherwise.
17-
if (inputParams.overwrite == Overwrite.Write) {
18-
inputParams.writeFile = host.write;
17+
if (generateParams.overwrite == Overwrite.Write) {
18+
generateParams.writeFile = host.write;
1919
}
2020

2121
function transform(fn: TransformFn): void {
@@ -28,6 +28,15 @@ function transform(fn: TransformFn): void {
2828

2929
const inputFiles = param.Object('jk.transform.input', {});
3030
const outputs = [];
31+
32+
const stdinFormat = param.String('jk.transform.stdin.format', '');
33+
if (stdinFormat !== '') {
34+
const format = valuesFormatFromExtension(stdinFormat);
35+
const path = `stdin.${stdinFormat}`; // path is a stand-in
36+
const value = read(stdin, { format }).then(v => v.map(transformOne));
37+
outputs.push({ path, value, format });
38+
}
39+
3140
for (const path of Object.keys(inputFiles)) {
3241
const format = valuesFormatFromPath(path);
3342
outputs.push(host.read(path, { format }).then((obj): File => {
@@ -44,7 +53,7 @@ function transform(fn: TransformFn): void {
4453
}
4554
}));
4655
}
47-
generate(Promise.all(outputs), inputParams);
56+
generate(Promise.all(outputs), generateParams);
4857
}
4958

5059
export default transform;

std/read.ts

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,47 @@ export interface ReadOptions {
2929
module?: string;
3030
}
3131

32-
// valuesFormatFromPath guesses, for a path, the format that will
33-
// return all values in a file. In other words, it prefers YAML
34-
// streams and concatenated JSON. You may need to treat the read value
35-
// differently depending on the format you got here, since YAMLStream
36-
// and JSONStream will both result in an array of values.
37-
export function valuesFormatFromPath(path: string): Format {
38-
const ext = path.split('.').pop();
32+
// splitPath returns [all-but-extension, extension] for a path. If a
33+
// path does not end with an extension, it will be an empty string.
34+
export function splitPath(path: string): [string, string] {
35+
const parts = path.split('.');
36+
const ext = parts.pop();
37+
// When there's no extension, either there will be a single part (no
38+
// dots anywhere), or a path separator in the last part (a dot
39+
// somewhere before the last path segment)
40+
if (parts.length === 0 || ext.includes('/')) {
41+
return [ext, ''];
42+
}
43+
return [parts.join(''), ext];
44+
}
45+
46+
function extension(path: string): string {
47+
return splitPath(path)[1];
48+
}
49+
50+
// formatFromPath guesses, for a file path, the format in which to
51+
// read the file. It will assume one value per file, so if you have
52+
// files that may have multiple values (e.g., YAML streams), it's
53+
// better to use `valuesFormatFromPath` and be prepared to get
54+
// multiple values.
55+
export function formatFromPath(path: string): Format {
56+
switch (extension(path)) {
57+
case 'yaml':
58+
case 'yml':
59+
return Format.YAML;
60+
case 'json':
61+
return Format.JSON;
62+
case 'hcl':
63+
case 'tf':
64+
return Format.HCL;
65+
default:
66+
return Format.JSON;
67+
}
68+
}
69+
70+
// valuesFormatFromExtension returns the format implied by a
71+
// particular file extension.
72+
export function valuesFormatFromExtension(ext: string): Format {
3973
switch (ext) {
4074
case 'yaml':
4175
case 'yml':
@@ -47,6 +81,16 @@ export function valuesFormatFromPath(path: string): Format {
4781
}
4882
}
4983

84+
// valuesFormatFromPath guesses, for a path, the format that will
85+
// return all values in a file. In other words, it prefers YAML
86+
// streams and concatenated JSON. You may need to treat the read value
87+
// differently depending on the format you got here, since YAMLStream
88+
// and JSONStream will both result in an array of values.
89+
export function valuesFormatFromPath(path: string): Format {
90+
const ext = extension(path);
91+
return valuesFormatFromExtension(ext);
92+
}
93+
5094
type ReadPath = string | typeof stdin;
5195

5296
// read requests the path and returns a promise that will be resolved

transform.go

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,14 @@ const transformExamples = `
2727
var transformOptions struct {
2828
vmOptions
2929
scriptOptions
30-
stdout bool // print everything to stdout
31-
overwrite bool // permit the overwriting of input files
32-
format string // force the format of the output
30+
// print everything to stdout
31+
stdout bool
32+
// permit the overwriting of input files
33+
overwrite bool
34+
// force the format of the output
35+
format string
36+
// read from stdin, expecting a stream of values in the format given
37+
stdinFormat string
3338
}
3439

3540
func init() {
@@ -38,6 +43,7 @@ func init() {
3843
transformCmd.PersistentFlags().BoolVar(&transformOptions.stdout, "stdout", false, "print the resulting values to stdout")
3944
transformCmd.PersistentFlags().BoolVar(&transformOptions.overwrite, "overwrite", false, "allow input file(s) to be overwritten by output file(s); otherwise, an error will be thrown")
4045
transformCmd.PersistentFlags().StringVar(&transformOptions.format, "format", "", "force all values to this format")
46+
transformCmd.PersistentFlags().StringVar(&transformOptions.stdinFormat, "stdin-format", "", "read values from stdin, assuming this format; implies --stdout")
4147
jk.AddCommand(transformCmd)
4248
}
4349

@@ -68,6 +74,14 @@ func transform(cmd *cobra.Command, args []string) {
6874
vm.parameters.Set("jk.transform.overwrite", transformOptions.overwrite)
6975
setGenerateFormat(transformOptions.format, vm)
7076

77+
switch transformOptions.stdinFormat {
78+
case "json", "yaml":
79+
vm.parameters.Set("jk.transform.stdin.format", transformOptions.stdinFormat)
80+
vm.parameters.Set("jk.transform.stdout", true)
81+
default:
82+
break
83+
}
84+
7185
var module string
7286
switch {
7387
case transformOptions.inline:

0 commit comments

Comments
 (0)