Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: enable comment support by default + fix: use files trailing arguments in Node + fix: confirm minification of files with unknown extension + fix: warn on not finding any files #25

Merged
merged 5 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"cSpell.words": ["jsonminify", "minijson"]
}
3 changes: 1 addition & 2 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ dub build --config=executable --build=release-nobounds --compiler=ldc2
```

- Download Native Binaries from
https://github.com/aminya/minijson/releases/latest

https://github.com/aminya/minijson/releases/latest

### CLI Usage

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"format.d": "dub run --build=release --quiet dfmt -- --soft_max_line_length 110 --indent_size 2 --inplace ./src ./benchmark",
"lint": "eslint . --fix",
"prepublishOnly": "shx rm -rf ./dist/tsconfig.tsbuildinfo ./dist/*.zip ./dist/build.* && chmod +x ./dist/*/minijson",
"start.benchmark.js": "node ./benchmark/js-benchmark.mjs",
"start.benchmark.node": "node ./benchmark/native-benchmark.mjs",
"start.browser": "servor ./dist/ --browse --reload",
"start.node": "node ./dist/node/cli.js",
Expand Down
17 changes: 7 additions & 10 deletions src/native/cli.d
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,24 @@ module minijson.cli;
import minijson.lib : minifyFiles, minifyStrings;
import argparse;

@(Command("minijson")
.Description(`minijson: minify json files with support for comments
@(Command("minijson").Description(`minijson: minify json files with support for comments

# Minify the specified files
minijson ./dist/**/*.json ./build/a.json
minijson file1_with_comment.json file2_with_comment.json

# Minify the specified files (supports comments)
minijson --comment file1_with_comment.json file2_with_comment.json
# Minify the specified files and disable comment support for faster minification
minijson --comment=false file1_no_comment.json file2_no_comment.json

# Minify the specified json string
minijson --str '{"some_json": "string_here"}'

# Minify the specified json string (supports comments)
minijson --comment --str '{"some_json": "string_here"} //comment'
minijson --str '{"some_json": "string_here"} //comment'

More information at https://github.com/aminya/minijson
`)
)
`))
struct Options
{
bool comment = false;
bool comment = true;
string[] str;
// (Deprecated) A list of files to minify (for backwards compatiblitity with getopt)
string[] file;
Expand Down
150 changes: 119 additions & 31 deletions src/native/lib.d
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,66 @@ const tokenizerNoComment = ctRegex!(`[\n\r"[]]`, "g");

Params:
jsonString = the json string you want to minify
hasComment = a boolean to support comments in json. Default: `false`.
hasComment = a boolean to support comments in json. Default: `true`.

Return:
the minified json string
*/
string minifyString(in string jsonString, in bool hasComment = false) @trusted
string minifyString(in string jsonString, in bool hasComment = true) @trusted
{
return hasComment ? minifyStringWithComments(jsonString) : minifyStringNoComments(jsonString);
}

string minifyStringNoComments(in string jsonString) @trusted
{
auto in_string = false;
string result;
size_t from = 0;
auto rightContext = "";

auto match = jsonString.matchAll(tokenizerNoComment);

while (!match.empty())
{
const matchFrontHit = match.front().hit();

rightContext = match.post();

// update from for the next iteration
const prevFrom = from;
from = jsonString.length - rightContext.length; // lastIndex

auto leftContextSubstr = match.pre()[prevFrom .. $];
const noLeftContext = leftContextSubstr.length == 0;
if (!noLeftContext)
{
if (!in_string)
{
leftContextSubstr = remove_spaces(leftContextSubstr);
}
result ~= leftContextSubstr;
}
if (matchFrontHit == "\"")
{
if (!in_string || noLeftContext || hasNoSlashOrEvenNumberOfSlashes(leftContextSubstr))
{
// start of string with ", or unescaped " character found to end string
in_string = !in_string;
}
--from; // include " character in next catch
rightContext = jsonString[from .. $];
}
else if (notSlashAndNoSpaceOrBreak(matchFrontHit))
{
result ~= matchFrontHit;
}
match.popFront();
}
result ~= rightContext;
return result;
}

string minifyStringWithComments(in string jsonString) @trusted
{
auto in_string = false;
auto in_multiline_comment = false;
Expand All @@ -26,9 +80,7 @@ string minifyString(in string jsonString, in bool hasComment = false) @trusted
size_t from = 0;
auto rightContext = "";

const tokenizer = !hasComment ? tokenizerNoComment : tokenizerWithComment;

auto match = jsonString.matchAll(tokenizer);
auto match = jsonString.matchAll(tokenizerWithComment);

while (!match.empty())
{
Expand All @@ -40,10 +92,9 @@ string minifyString(in string jsonString, in bool hasComment = false) @trusted
const prevFrom = from;
from = jsonString.length - rightContext.length; // lastIndex

const notInComment = (!in_multiline_comment && !in_singleline_comment);
const noCommentOrNotInComment = !hasComment || notInComment;
const notInComment = !in_multiline_comment && !in_singleline_comment;

if (noCommentOrNotInComment)
if (notInComment)
{
auto leftContextSubstr = match.pre()[prevFrom .. $];
const noLeftContext = leftContextSubstr.length == 0;
Expand All @@ -67,7 +118,7 @@ string minifyString(in string jsonString, in bool hasComment = false) @trusted
}
}
// comments
if (hasComment && !in_string)
if (!in_string)
{
if (notInComment)
{
Expand All @@ -93,7 +144,7 @@ string minifyString(in string jsonString, in bool hasComment = false) @trusted
in_singleline_comment = false;
}
}
else if (!hasComment && notSlashAndNoSpaceOrBreak(matchFrontHit))
else if (notSlashAndNoSpaceOrBreak(matchFrontHit))
{
result ~= matchFrontHit;
}
Expand Down Expand Up @@ -176,7 +227,7 @@ private bool hasNoSpace(const ref string str) @trusted
Return:
the minified json strings
*/
string[] minifyStrings(in string[] jsonStrings, in bool hasComment = false) @trusted
string[] minifyStrings(in string[] jsonStrings, in bool hasComment = true) @trusted
{
import std.algorithm : map;
import std.array : array;
Expand All @@ -191,7 +242,7 @@ string[] minifyStrings(in string[] jsonStrings, in bool hasComment = false) @tru
paths = the paths to the files. It could be glob patterns.
hasComment = a boolean to support comments in json. Default: `false`.
*/
void minifyFiles(in string[] paths, in bool hasComment = false) @trusted
void minifyFiles(in string[] paths, in bool hasComment = true) @trusted
{
import std.parallelism : parallel;
import std.algorithm;
Expand All @@ -202,33 +253,70 @@ void minifyFiles(in string[] paths, in bool hasComment = false) @trusted
import std.stdio : writeln;

// get the files from the given paths (resolve glob patterns)
auto files = paths
.map!((path) {
if (path.exists)
auto files = paths.map!((path) {
if (path.exists)
{
if (path.isFile)
{
if (path.isFile)
{
return [path];
}
else if (path.isDir)
{
return glob(path ~ "/**/*.json");
}
else
{
throw new Exception("The given path is not a file or a directory: " ~ path);
}
return [path];
}
else if (path.isDir)
{
return glob(path ~ "/**/*.json");
}
else
{
return glob(path);
throw new Exception("The given path is not a file or a directory: " ~ path);
}
})
.joiner()
.array();
}
else
{
return glob(path);
}
}).joiner().array();

if (files.empty)
{
writeln("No files found.");
return;
}

if (!confirmExtension(files))
{
return;
}

foreach (file; files.parallel())
{
write(file, minifyString(readText(file), hasComment));
}
}

bool confirmExtension(string[] files) @trusted
{
auto confirmExtension = false;
import std.path : extension;

foreach (file; files)
{
// if the file extension is not json, jsonc, or json5, confirm before minifying
auto fileExtension = file.extension();
if (fileExtension != ".json" && fileExtension != ".jsonc" && fileExtension != ".json5")
{
if (!confirmExtension)
{
import std.stdio : readln, writeln;

writeln("The file ", file, " doesn't have a json extension. Do you want to minify it? (y/n)");
auto input = readln();
confirmExtension = input == "y";
if (!confirmExtension)
{
return false;
}
}
}
}

return true;
}
4 changes: 2 additions & 2 deletions src/native/libc.d
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import minijson.lib : minifyString;

Params:
jsonString = the json string you want to minify
hasComment = a boolean to support comments in json. Default: `false`.
hasComment = a boolean to support comments in json. Default: `true`.

Return:
the minified json string
*/
extern (C) auto c_minifyString(char* jsonCString, bool hasComment = false)
extern (C) auto c_minifyString(char* jsonCString, bool hasComment = true)
{
import std : fromStringz, toStringz;

Expand Down
Loading
Loading