Skip to content

Commit

Permalink
Remove -i and -o command line options
Browse files Browse the repository at this point in the history
The main use case of DjHTML is to modify files in-place. Therefore, the `-i`
(`--in-place`) argument has been made the default and removed, leaving only
a deprecation error. The `-o` (`--output-file`) argument has also been
removed.

Reading from standard input and writing to standard output is still
supported by using "-" as the filename. By the same mechanism, writing the
output to a different file is also still possible by using output
redirection:

    djhtml - < input.html > output.html

Since this is a backwards-incompatible change, the next release will
increment the major version number. No changes have been made to the
indentation algorithm.

Closes #66
Closes #69
Closes #70
  • Loading branch information
JaapJoris committed Jan 30, 2023
1 parent 56365d5 commit 4a44f29
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 169 deletions.
6 changes: 3 additions & 3 deletions .pre-commit-hooks.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
- id: djhtml
name: DjHTML
entry: djhtml -i
entry: djhtml
types: [html]
language: python
language_version: python3
Expand All @@ -9,7 +9,7 @@

- id: djcss
name: DjCSS
entry: djcss -i
entry: djcss
types_or: [css, scss]
language: python
language_version: python3
Expand All @@ -18,7 +18,7 @@

- id: djjs
name: DjJS
entry: djjs -i
entry: djjs
types: [javascript]
language: python
language_version: python3
Expand Down
19 changes: 7 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,29 +62,24 @@ Install DjHTML with the following command:
## Usage

After installation you can indent templates using the `djhtml`
command. The default is to read from standard in and to write the
indented output to standard out. To modify the source file in-place,
use the `-i` / `--in-place` option and specify a filename:
command:

$ djhtml -i template.html
$ djhtml template.html
reindented template.html
1 template has been reindented.

Normally, the exit status of 0 means everything went well, regardless
of whether any files were changed. If any errors were encountered, the
exit status indicated the number of problematic files. However, when
the option `-c` / `--check` is used, the exit status is the number of
files that would have changed, but no changes are actually made.
An exit status of 0 means that everything went well, regardless of
whether any files were changed. When the option `-c` / `--check` is
used, the exit status is 1 when one or more files would have changed,
but no changes are actually made.

All available options are:

- `-h` / `--help`: show overview of available options
- `-i` / `--in-place`: modify files in-place
- `-c` / `--check`: don't modify files; the exit status is the number
of files that would have changed
- `-q` / `--quiet`: don't print any output
- `-t` / `--tabwidth`: set tabwidth (default is 4)
- `-o` / `--output-file`: write output to specified file


## `fmt:off` and `fmt:on`
Expand Down Expand Up @@ -157,7 +152,7 @@ the default tabwidth, you change the `entry` point of these hooks:
hooks:
- id: djhtml
# Use a tabwidth of 2 for HTML files
entry: djhtml -i -t 2
entry: djhtml --tabwith 2
- id: djcss
- id: djjs
```
Expand Down
250 changes: 96 additions & 154 deletions djhtml/__main__.py
Original file line number Diff line number Diff line change
@@ -1,210 +1,152 @@
import argparse
import sys
from pathlib import Path
"""
Entrypoint for all 4 command-line tools. Typical usage:
from . import modes
$ djhtml file1.html file2.html
Passing "-" as the filename will read from standard input and write to
standard output. Example usage:
def verify_changed(source, result):
"""
Verify that the source is either exactly equal to the result or
that the result has only changed by added or removed whitespace.
$ djhtml - < input.html > output.html
"""

"""
output_lines = result.split("\n")
changed = False
for line_nr, line in enumerate(source.split("\n")):
if line != output_lines[line_nr]:
changed = True
if line.strip() != output_lines[line_nr].strip():
raise IndentationError("Non-whitespace changes detected. Core dumped.")
import sys
from pathlib import Path

return changed
from . import modes, options


def main():
"""
Entrypoint for all 4 command-line tools. Typical usage:
$ djhtml -i file1.html file2.html
changed_files = 0
unchanged_files = 0
problematic_files = 0

"""
target_extension = ".html"
suffixes = [".html"]
Mode = modes.DjHTML
if sys.argv[0].endswith("djtxt"):
Mode = modes.DjTXT
target_extension = ".txt"
suffixes = [".txt"]
if sys.argv[0].endswith("djcss"):
Mode = modes.DjCSS
target_extension = ".css"
suffixes = [".css", ".scss"]
if sys.argv[0].endswith("djjs"):
Mode = modes.DjJS
target_extension = ".js"
suffixes = [".js"]

changed_files = 0
unchanged_files = 0
problematic_files = 0
if len(options.input_filenames) > 1 and "-" in options.input_filenames:
sys.exit("I’m sorry Dave, I’m afraid I can’t do that.")

parser = argparse.ArgumentParser(
description=(
"DjHTML is a fully automatic template indenter that works with mixed"
" HTML/CSS/Javascript templates that contain Django or Jinja template"
" tags. It works similar to other code-formatting tools such as Black and"
" interoperates nicely with pre-commit. Full documentation can be found at"
" https://github.com/rtts/djhtml"
),
)
parser.add_argument(
"-i", "--in-place", action="store_true", help="modify files in-place"
)
parser.add_argument("-c", "--check", action="store_true", help="don't modify files")
parser.add_argument("-q", "--quiet", action="store_true", help="be quiet")
parser.add_argument(
"-t",
"--tabwidth",
metavar="N",
type=int,
default=4,
help="tabwidth (default is 4)",
)
parser.add_argument(
"-o",
"--output-file",
metavar="filename",
default="-",
help="output filename",
)
parser.add_argument(
"input_filenames",
metavar="filenames",
nargs="*",
default=["-"],
help="input filenames (either paths or directories)",
)
parser.add_argument("-d", "--debug", action="store_true", help=argparse.SUPPRESS)
args = parser.parse_args()

if args.in_place and "-" in args.input_filenames:
sys.exit("I’m sorry Dave, I’m afraid I can’t do that")

if len(args.input_filenames) > 1 and not args.in_place and not args.check:
sys.exit("Will not modify files in-place without -i option")

for input_filename in _generate_files(args.input_filenames, target_extension):
for filename in _generate_filenames(options.input_filenames, suffixes):
# Read input file
try:
input_file = (
sys.stdin if input_filename == "-" else open(input_filename, "r")
)
input_file = sys.stdin if filename == "-" else open(filename, "r")
source = input_file.read()
except Exception as e:
problematic_files += 1
if not args.quiet:
print(f"Error opening {input_filename}: {e}", file=sys.stderr)
continue
_error(f"Cannot open {filename}: {e}")
finally:
input_file.close()

# Indent input file
try:
if args.debug:
if options.debug:
print(Mode(source).debug())
sys.exit()
result = Mode(source).indent(args.tabwidth)
result = Mode(source).indent(options.tabwidth)
except SyntaxError as e:
problematic_files += 1
if not args.quiet:
print(
f"Syntax error in {input_file.name}:"
f" {str(e) or e.__class__.__name__}",
file=sys.stderr,
)
_error(f"Syntax error in {filename}: {str(e) or e.__class__.__name__}")
continue
except Exception:
print(
f"\nFatal error while processing {input_file.name}\n\n"
_error(
f"Fatal error while processing {filename}\n\n"
" If you have time and are using the latest version, we\n"
" would very much appreciate if you opened an issue on\n"
" https://github.com/rtts/djhtml/issues\n",
file=sys.stderr,
" https://github.com/rtts/djhtml/issues\n"
)
raise
finally:
input_file.close()

changed = verify_changed(source, result)

# Print to stdout and exit
if not args.in_place and not args.check and args.output_file == "-":
if not args.quiet:
print(result, end="")
sys.exit(1 if args.check and changed else 0)
changed = _verify_changed(source, result)
if changed:
changed_files += 1
else:
unchanged_files += 1

# Write output file
if changed and args.check:
changed_files += 1
if filename == "-":
if not options.check:
print(result, end="")
elif changed:
output_filename = input_file.name if args.in_place else args.output_file
try:
output_file = open(output_filename, "w")
output_file = open(filename, "w")
output_file.write(result)
output_file.close()
changed_files += 1
except Exception as e:
changed_files -= 1
problematic_files += 1
if not args.quiet:
print(f"Error writing {output_filename}: {e}", file=sys.stderr)
_error(f"Error writing {filename}: {e}")
continue
if not args.quiet:
print(
f"reindented {output_file.name}",
file=sys.stderr,
)
else:
unchanged_files += 1
_info(f"reindented {output_file.name}")

# Print final summary
if not args.quiet:
s = "s" if changed_files != 1 else ""
have = "would have" if args.check else "have" if s else "has"
print(
f"{changed_files} template{s} {have} been reindented.",
file=sys.stderr,
s = "s" if changed_files != 1 else ""
have = "would have" if options.check else "have" if s else "has"
_info(f"{changed_files} template{s} {have} been reindented.")
if unchanged_files:
s = "s" if unchanged_files != 1 else ""
were = "were" if s else "was"
_info(f"{unchanged_files} template{s} {were} already perfect!")
if problematic_files:
s = "s" if problematic_files != 1 else ""
_info(
f"{problematic_files} template{s} could not be processed due to an error."
)
if unchanged_files:
s = "s" if unchanged_files != 1 else ""
were = "were" if s else "was"
print(
f"{unchanged_files} template{s} {were} already perfect!",
file=sys.stderr,
)
if problematic_files:
s = "s" if problematic_files != 1 else ""
print(
f"{problematic_files} template{s} could not be processed due to an"
" error.",
file=sys.stderr,
)

sys.exit(changed_files if args.check else problematic_files)
# Exit with appropriate exit status
if problematic_files:
sys.exit(1)
if options.check and changed_files:
sys.exit(1)
sys.exit(0)


def _generate_files(input_filenames, suffix):
for file_name in input_filenames:
if file_name == "-":
yield file_name
def _generate_filenames(paths, suffixes):
for filename in paths:
if filename == "-":
yield filename
else:
file_path = Path(file_name)
if file_path.is_file():
yield file_path
elif file_path.is_dir():
yield from _generate_files_from_folder(file_path, suffix)


def _generate_files_from_folder(folder, suffix):
for file_path in folder.iterdir():
if file_path.is_file() and file_path.suffix == suffix:
yield file_path
elif file_path.is_dir():
yield from _generate_files_from_folder(file_path, suffix)
path = Path(filename)
if path.is_file():
yield path
elif path.is_dir():
yield from _generate_filenames_from_directory(path, suffixes)


def _generate_filenames_from_directory(directory, suffixes):
for path in directory.iterdir():
if path.is_file() and path.suffix in suffixes:
yield path
elif path.is_dir():
yield from _generate_filenames_from_directory(path, suffixes)


def _verify_changed(source, result):
output_lines = result.split("\n")
changed = False
for line_nr, line in enumerate(source.split("\n")):
if line != output_lines[line_nr]:
changed = True
if line.strip() != output_lines[line_nr].strip():
raise IndentationError("Non-whitespace changes detected. Core dumped.")
return changed


def _info(msg):
if not options.quiet:
print(msg, file=sys.stderr)


def _error(msg):
_info(f"Error: {msg}")


if __name__ == "__main__":
Expand Down
Loading

0 comments on commit 4a44f29

Please sign in to comment.