Skip to content

Commit

Permalink
feat(stories2csv): expose cli interface for csv generation
Browse files Browse the repository at this point in the history
  • Loading branch information
EggDice committed Nov 27, 2020
1 parent 96b2ea1 commit c39d986
Show file tree
Hide file tree
Showing 10 changed files with 148 additions and 20 deletions.
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,26 @@ A checker and code generation tool for our markdown formatted materials.

### If you have NodeJS and NPM

`npm install -g https://github.com/green-fox-academy/markdown-tools`
```shell
npm install -g https://github.com/green-fox-academy/markdown-tools
```

or

```shell
git clone https://github.com/green-fox-academy/markdown-tools
cd markdown-tools
npm link
```


## Usage

### Generate Jira compatible `csv` from markdown

```
markdown-tools stories2csv -i stories.md -o stories.csv
```

For further details see the `--help` flag on the cli tool or read the `features`.

2 changes: 1 addition & 1 deletion features/cli.feature
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Feature: Show the features if no command is specified
Given the package installed
Given a bash prompt in "test/" as working directory
When the "markdown-tools" command is executed
Then it should print to the standard out:
Then it should print to the standard error:
"""
markdown-tools <command>
Expand Down
5 changes: 5 additions & 0 deletions features/steps/steps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ Then('it should print to the standard out:', async function (content: string) {
assert.equal(stdOut, content);
});

Then('it should print to the standard error:', async function (content: string) {
const stdErr: string = await this.cli.getStdErr();
assert.equal(stdErr, content);
});

Then('the last exit code should be {int}', async function (exitCode: number) {
const lastExitCode: number = await this.cli.getLastExitCode();
assert.equal(lastExitCode, exitCode);
Expand Down
55 changes: 55 additions & 0 deletions features/stories2csv.feature
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,58 @@ Feature: Generate Jira importable CSV file from markdown story list
- Something"
Task one,2,1,Sub-task,Do something unexpected
"""
Then the last exit code should be 0

Scenario:
Given the package installed
Given a bash prompt in "test/" as working directory
When the "markdown-tools stories2csv" command is executed
Then it should print to the standard error:
"""
markdown-tools stories2csv
Creates csv file for Jira from markdown story definitions
Options:
--help Show help [boolean]
--version Show version number [boolean]
-i, --input_file [string] [required]
-o, --output_file [string] [required]
Missing required arguments: i, o
"""
Then the last exit code should be 1

Scenario:
Given the package installed
Given a bash prompt in "test/" as working directory
When the "markdown-tools stories2csv -i stories.md" command is executed
Then it should print to the standard error:
"""
markdown-tools stories2csv
Creates csv file for Jira from markdown story definitions
Options:
--help Show help [boolean]
--version Show version number [boolean]
-i, --input_file [string] [required]
-o, --output_file [string] [required]
Missing required argument: o
"""
Then the last exit code should be 1

Scenario:
Given the package installed
Given a bash prompt in "test/" as working directory
When the "markdown-tools stories2csv -i stories.md -o stories.csv" command is executed
Then it should print to the standard error:
"""
Unable to open file: "stories.md"
"""
Then the last exit code should be 1

2 changes: 1 addition & 1 deletion features/world/realCli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const runCliLine = (line: string): Promise<CommandResult> =>
});

spawned.stderr.on('data', data => {
stdout += data;
stderr += data;
});

spawned.on('close', exitCode => {
Expand Down
32 changes: 24 additions & 8 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,27 @@ const FEATURES = {
stories2csv,
};

const parseArgs = (argv: string[]): Promise<{output: string, args: string[]}> =>
const parseArgs = (argv: string[]): Promise<{output: string, args: any}> =>
new Promise((resolve, reject) => {
yargs
.strict()
.demandCommand(1, 'Please specify at least one command!')
.command('stories2csv', 'Creates csv file for Jira from markdown story definitions')
.command('stories2csv', 'Creates csv file for Jira from markdown story definitions', (yargs) => {
return yargs.options({
'i': {
alias: 'input_file',
demandOption: true,
type: 'string',
requiresArg: true,
},
'o': {
alias: 'output_file',
demandOption: true,
type: 'string',
requiresArg: true,
}
});
})
.parse(argv, (err: any, args: any, output: any) => {
if (err) {
reject({err, args, output});
Expand All @@ -25,16 +40,17 @@ const parseArgs = (argv: string[]): Promise<{output: string, args: string[]}> =>
});

export async function runCli({argv, stdout, stderr}: NodeJS.Process): Promise<number> {
if (Object.keys(FEATURES).includes(argv[2])) {
await FEATURES[argv[2]](argv);
return 0;
}
try {
const { output } = await parseArgs(argv.slice(2));
const { output, args } = await parseArgs(argv.slice(2));
if (args._.length) {
await FEATURES[args._[0]](args);
stdout.write("" + EOL);
return 0;
}
stdout.write(output + EOL);
return 0;
} catch ({err, output}) {
stdout.write(output + EOL);
stderr.write(output + EOL);
return 1;
}
}
18 changes: 12 additions & 6 deletions src/stories2csv/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@ import { parse } from './parse';
import { transform } from './transform';
import { generate } from './generate';

export const stories2csv = async (argv: string[]) => {
const contentBuffer = await fs.readFile(argv[4]);
const content = contentBuffer.toString();
const parsedContent = parse(content);
const rows = transform(parsedContent.stories);
return fs.writeFile(argv[6], generate(rows), 'utf-8');
export const stories2csv = async (argv: any) => {
try {
const contentBuffer = await fs.readFile(argv.i);
const content = contentBuffer.toString();
const parsedContent = parse(content);
const rows = transform(parsedContent.stories);
return fs.writeFile(argv.o, generate(rows), 'utf-8');
} catch (e) {
if (e.code === 'ENOENT') {
throw ({output: `Unable to open file: "${e.path}"`});
}
}
};
24 changes: 24 additions & 0 deletions src/stories2csv/parse.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,30 @@ More details
});
});

test('parse a single story without value statement', () => {
const markdown = `
# First Story
## Acceptance Criteria
- It should be awesome
`;

expect(parse(markdown)).toEqual({
stories: [{
title: 'First Story',
valueStatement: '',
description: '',
acceptanceCriteria: '- It should be awesome',
defintionOfDone: '',
subTasks: [],
}],
errors: [],
warnings: [],
});
});


test('parse complex description section', () => {
const markdown = `
# First Story
Expand Down
4 changes: 2 additions & 2 deletions src/stories2csv/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ interface ParserOutput {
const SELECTOR = {
SEPARATOR: 'thematicBreak',
MAIN_HEADER: 'heading[depth=1]',
SECOND_HEADING: 'heading[depth=1] + heading[depth=2]',
SECOND_HEADING: 'heading[depth=1] + heading[depth=2]:has(:not(text[value="Acceptance Criteria"]))',
ACCEPTANCE_HEADING: 'heading:has(text[value="Acceptance Criteria"])',
TASKS_SECTION_HEADING: 'heading[depth=2]:has(text[value="Tasks"])',
TASK_HEADING: 'heading[depth=3]',
Expand Down Expand Up @@ -97,7 +97,7 @@ const getSubtask = (taskTree: Parent): SubTask =>
});

const getText = (tree: Parent, selector: string): string =>
(select(selector + ' text', tree)?.value as string).trim();
(select(selector + ' text', tree)?.value as string ?? '').trim();

const getTextBetween = (tree: Parent, beforeSelector: string, afterSelector: string): string => {
const beforeNode = select(beforeSelector, tree);
Expand Down
2 changes: 1 addition & 1 deletion src/stories2csv/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const getStoryDescription = (story: Story) => {
story.description,
ACCEPTANCE_TITLE,
story.acceptanceCriteria,
].join(EOL);
].join(EOL)//.replace(new RegExp(EOL, 'g'), EOL + '\\\\ ');
};

const getSubtasks = ({story, storyId, counter}: {story: Story, storyId: string, counter: () => number}) =>
Expand Down

0 comments on commit c39d986

Please sign in to comment.