Command Line Unminifier
by Gearbox
JavaScript and Cascading Style Sheets (CSS) files are usually posted online in minified format (minimum number of spaces and everything on a single line) in order to save transfer bandwidth. This makes it hard to analyze. Even though there are online tools to unminify such files, using a command line utility to do this work has the advantage of integration with already existing CLI tools (via pipes or command calls) and the potential to work in bulk.
Such a CLI tool can take the minified content of a JavaScript or CSS file using the standard input and output the unminified content using the standard output. The default space indentation can be set to two spaces, but can be overridden via an input parameter (indent). For example, if we name the tool script webballoon.py to unminify the content of a file, we can call it on Linux or Mac like:
$ cat script.min.js | ./webballoon.pyor to use a custom indent of four spaces:
$ cat script.min.js | ./webballoon.py --indent 4For implementation, Python 3 is a great choice, as it is installed by default on most Linux distros and has a huge number of packages for pretty much everything.
We start with the Python "shebang" line (#!/usr/bin/python3), which tells Bash to use the Python 3 interpreter if the file is called directly without passing it as a parameter to Python 3 (e.g. ./webballoon.py instead of python3 ./webballoon.py).
Next, we import the packages we need:
- argparse - to add support for input parameter parsing (for the "indent' optional parameter that we are going to use).
- sys - in order to use the standard input, output, and error streams and to specify script error code in case of unexpected input.
Although it might seem overkill to use a parameter parsing package for a single input parameter, the application might be further extended in the future, plus there's the advantage that you can call the script with a -h or --help parameter and it displays automatically generated help.
The _get_indent_size function is used to retrieve the value of the indent input parameter (defaults to 2 if the parameter is not specified). It uses the _check_larger_than_zero helper function to validate that the indent parameter has a specified value that's numeric and greater than zero.
We then use the _get_indent_spaces helper function to get a white space string of variable size that can be use to display the indent and the _print_to_stream helper function to print text to standard output stream without adding a new line at the end.
The core functionality resides in the main function. It is the module entry point and contains the input processing logic. It parses the input character-by-character and, based on the encountered characters, generates the output as follows:
1.) In case the { character is encountered, it means that what will follow is in an inner scope, so we output {, move to the next line, and output an increased indent.
2.) In case the } character is encountered, it means that what will follow is in an outer scope, so we move to the next line, output a decreased indent, }, move to the next line, and output the current indent.
3.) In case the ; character is encountered, it means that what will follow is on a separate line at the same indent, so we output ;, move to the next line, and output the indent.
4.) In case the , character is encountered, it means that multiple elements are separated, so we output a space followed by ,.
5.) Any other character is outputted as is. The full code listing below is available under Boost Software License 1.0 and is also available online at: github.com/gearbx/webballoon.
#!/usr/bin/python3 import argparse import sys _BAD_INPUT_ERROR_CODE = 1 def _check_larger_than_zero(value) -> int: try: v = int(value) if v <= 0: print(f"Invalid parameter value {value}", file=sys.stderr) sys.exit(_BAD_INPUT_ERROR_CODE) return v except Exception: print(f"Invalid parameter value {value}", file=sys.stderr) sys.exit(_BAD_INPUT_ERROR_CODE) def _get_indent_size() -> int: parser = argparse.ArgumentParser() parser.add_argument("--indent", type=_check_larger_than_zero, default=2, help="the indent size in spaces.(default 2)") args = parser.parse_args() return args.indent def _get_indent_spaces(indent: int) -> str: return " " * indent def _print_to_stream(text: str): print(text, end="", file=sys.stdout) def main(): indent_increase = _get_indent_size() indent = 0 for line in sys.stdin: for character in line: if character == "{": indent += indent_increase _print_to_stream(" {\n" + _get_indent_spaces(indent)) elif character == "}": indent = max(0, indent - indent_increase) _print_to_stream("\n" + _get_indent_spaces(indent) + "}\n" + _get_indent_spaces(indent)) elif character == ";": _print_to_stream(";\n" + _get_indent_spaces(indent)) elif character == ",": _print_to_stream(", ") else: _print_to_stream(character) if __name__ == "__main__": main()Code: webballoon.py