Skip to content

Commit

Permalink
feat(CLI): renew command line arguments (#3)
Browse files Browse the repository at this point in the history
* chore: add change-case for support case style

* docs: add inputs arguments in README.md

* feat(CLI): add flags and options

* `-v, --version`: Print version information.
* `-C, --case`: Sets case for export name declarations

* refactor(CLI): remove unused and fix typo for function declarations

* feat(CLI): add missing style case

* feat(CLI): support multiple input directories or files
  • Loading branch information
r17x committed Sep 7, 2021
1 parent e391ba3 commit 5df56be
Show file tree
Hide file tree
Showing 5 changed files with 256 additions and 43 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ create-chakra-icons [FLAGS] [OPTIONS] [INPUT]

### Options
```console
-i, --input <PATH> This option for read the input from PATH from FILE or DIRECTORIES.
-i, --input <PATH> This option for read the input from PATH of FILE or DIRECTORIES.
[e.g.: -i some/path , -i file.svg]
-o, --output <PATH> Writes the output. [default: stdout]
-n, --name <STRING> Sets value for `displayName` properties
Expand All @@ -39,6 +39,12 @@ create-chakra-icons [FLAGS] [OPTIONS] [INPUT]
[e.g.: -S "Icon"]
--ts, --typescript Sets output as TypeScript code.
```

### Input
```console
[INPUT] This option for read the input from PATH of FILE or DIRECTORIES.
[e.g.: create-chakra-icons ./MyICON.svg ~/assets]
```
### Examples

#### Pipelines command:
Expand Down
141 changes: 105 additions & 36 deletions index.bin.js
Original file line number Diff line number Diff line change
@@ -1,73 +1,142 @@
#!/usr/bin/env node
const {
name: packageName,
version: packageVersion,
} = require("./package.json");
const Fs = require("fs");
const Path = require("path");
const BabelGenerator = require("@babel/generator").default;
const argv = require("minimist")(process.argv.slice(2));
const { createChakraIcon } = require("./lib/chakra");
const { stdout: output, stdin: input, exit } = process;
const {
stdout: output,
stdin: input,
exit,
stderr: error,
} = require("process");
const {
pascalCase: PascalCase,
camelCase,
snakeCase: snake_case,
constantCase: CONSTANT_CASE,
} = require("change-case");
const encoding = "utf-8";

if (input.isTTY) {
main(argv);
} else {
input.setEncoding(encoding);
input.on("data", function (data) {
if (data) {
const name = argv.name || argv.n || "Unamed";
const exportNameCase = argv.C || argv.case;
const source = createCode(name, {
file: data,
displayName: name,
source: data,
displayName: stringToCase(name, exportNameCase),
});
output.write(source);
}
});
}

function createCode(name, ...svgs) {
const icon = createChakraIcon(name, ...svgs);
function createCode(...sources) {
const icon = createChakraIcon(...sources);
return BabelGenerator(icon).code;
}

function stringToCase(str, _case) {
return {
[true]: PascalCase(str),
[_case === "pascal"]: PascalCase(str),
[_case === "camel"]: camelCase(str),
[_case === "constant"]: CONSTANT_CASE(str),
[_case === "snake"]: snake_case(str),
}[true];
}

function stringToInput({ displayName, exportNameCase, encoding }) {
return function (acc, str) {
if (Fs.existsSync(str)) {
if (Fs.lstatSync(str).isDirectory()) {
const pathResolved = Path.resolve(str);
acc.push(
...Fs.readdirSync(pathResolved)
.filter((f) => f.split(".")[1] === "svg")
.map((f) => Path.join(pathResolved, f))
.map((source) => ({
displayName: stringToCase(
Path.basename(source).split(".")[0],
exportNameCase
),
source: Fs.readFileSync(source, encoding),
}))
);
} else {
acc.push({
displayName: stringToCase(displayName, exportNameCase),
source: Fs.readFileSync(str, encoding),
});
}
}
return acc;
};
}
// :: [Object] -> () *Effect*
function main(args) {
const fileOrDirs = args.i || args.input;
const inputs = (args.i && [args.i]) || (args.input && [args.input]) || args._;
const version = args.V || args.version;
const outFile = args.o || args.output;
const name = args.name || args.n || "Unamed";
const exportNameCase = args.C || args.case;

// check is {file} exist
Fs.exists(fileOrDirs, (isExist) => {
if (!isExist) {
console.error("Cannot handle input") && exit(1);
}
// all the input will put in an array {_files}
let _files = [];
// handle when {-i} || {--input} is file.svg or dirs/
if (Fs.lstatSync(fileOrDirs).isDirectory()) {
const pathResolved = Path.resolve(fileOrDirs);
_files = _files.concat(
Fs.readdirSync(pathResolved)
.filter((f) => f.split(".")[1] === "svg")
.map((f) => Path.join(pathResolved, f))
);
} else {
_files = _files.concat([fileOrDirs]);
}
if (inputs.length > 0) {
// make code
const source = createCode(
name,
..._files.map((_file) => ({
file: Fs.readFileSync(_file, encoding),
displayName: (() => {
const [_name] = Path.basename(_file).split(".") || [name];
return _name;
})(),
}))
...inputs.reduce(
stringToInput({ displayName: name, exportNameCase, encoding }),
[]
)
);
// write output in output
outFile
return outFile
? Fs.writeFile(Path.resolve(outFile), source, (err) => {
if (err) {
console.log(err) && exit(1);
error.write(err, () => exit(1));
}
})
: output.write(source);
});
: output.write(`${source}`);
} else if (version) {
return output.write(packageVersion);
}

output.write(`
USAGE: ${packageName} [FLAGS] [OPTIONS] [INPUT]
By default, output is written to stdout.
Stdin is read if is piped to create-chakra-icons.
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-i, --input <PATH> This option for read the input from PATH from FILE or DIRECTORIES.
[e.g.: -i some/path , -i file.svg]
-o, --output <PATH> Writes the output. [default: stdout]
-n, --name <STRING> Sets value for \`displayName\` properties
(*ONLY for pipelines command). [default: Unamed] [e.g. -n "MyIcon"]
-C, --case <snake|camel|constant|pascal>
Sets for case [snake|camel|constant|pascal] in export named declaration
output. [default: pascal]
-S, --suffix <STRING> Sets for suffix in export named declaration.
[e.g.: -S "Icon"]
--ts, --typescript Sets output as TypeScript code. (UNAVAILABLE, SOON).
[INPUT]: This option for read the input from PATH of FILE or DIRECTORIES.
[e.g.: create-chakra-icons ./MyICON.svg ~/assets]
${packageName} (version: ${packageVersion})
`);
}
14 changes: 8 additions & 6 deletions lib/chakra.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ const ast = require("./ast");
* @param {String} displayName
* @returns {Object}
*/
const createChakraIcon = (displayName, ...svgs) => {
const perFileCode = ({ file: svg, displayName }) => {
const createChakraIcon = (...sources) => {
const perFileCode = ({ source: svg, displayName }) => {
const hast = SvgParser.parse(svg);

if (ast.hastChildrenLength(hast) > 1) {
Expand All @@ -32,8 +32,7 @@ const createChakraIcon = (displayName, ...svgs) => {
});
return iconAST;
};

const svgCodes = [...svgs].map(perFileCode);
const svgCodes = [...sources].map(perFileCode);

const hasVariableDeclaratorInit =
(is) =>
Expand All @@ -48,15 +47,18 @@ const createChakraIcon = (displayName, ...svgs) => {
}) =>
type === is;

// TODO: make it more accurate with {@babel/types}.isArrowFunctionExpression
const hasArrowFunctionExpression = hasVariableDeclaratorInit(
"ArrowFunctionExpression"
);
// TODO: make it more accurate with {@babel/types}.isCallExpression
const hasCallExpression = hasVariableDeclaratorInit("CallExpression");
const isNotEmptryString = (str) => str !== "";
const isNotEmptyString = (str) => str !== "";
// usage for generate import module
const imports = [
svgCodes.some(hasArrowFunctionExpression) ? "Icon" : "",
svgCodes.some(hasCallExpression) ? "createIcon" : "",
].filter(isNotEmptryString);
].filter(isNotEmptyString);
const program = ast.toSource(
ast.toImportDeclaration("@chakra-ui/react", ...imports),
...svgCodes
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@babel/generator": "7.15.0",
"@babel/types": "7.15.0",
"@svgr/hast-util-to-babel-ast": "5.5.0",
"change-case": "4.1.2",
"minimist": "1.2.5",
"svg-parser": "2.0.4"
},
Expand Down

0 comments on commit 5df56be

Please sign in to comment.