Skip to content

Commit

Permalink
feat. Now differentiate between entirely fixed vs modified files. Closes
Browse files Browse the repository at this point in the history
 #78.
  • Loading branch information
tamere-allo-peter committed Mar 23, 2022
1 parent 7248995 commit 6384ae9
Show file tree
Hide file tree
Showing 8 changed files with 51 additions and 33 deletions.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ Both summaries and diagnostic information are sent to stderr.
This command exits with status `2` if there are incompatible command
line options. It exits with `-2` if yamllint is not available on your
system. Otherwise it exits with `0` if all input files either are
skipped or successfully pass `yamllint` strict mode, else `-1`.
skipped, entirely fixed, or already successfully passed `yamllint`
strict mode before, else `-1`.
For convenience, all or parts of the command line arguments can be
read from a file, one per line, by using the well known `@argsfile`
Expand Down Expand Up @@ -131,6 +132,7 @@ So you can get a nicely colored (and validated `json` output) :
"filestofix": 1,
"passedstrictmode": 1,
"modified": 0,
"fixed": 0,
"skipped": 0,
"notwriteable": 0,
"unknown": 0,
Expand All @@ -140,7 +142,8 @@ So you can get a nicely colored (and validated `json` output) :
"issues": 0,
"handled": 0
}
}
},
"nochangemode": false
}
```
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[metadata]
version = 0.4.5
version = 0.4.6
name = yamlfixer-opt-nc
description = automates the fixing of problems reported by yamllint
long_description = file: README.md
Expand Down
2 changes: 1 addition & 1 deletion yamlfixer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

import time

__version__ = "0.4.5"
__version__ = "0.4.6"
__author__ = "OPT-NC"
__license__ = "GPLv3+"
__copyright__ = "Copyright (C) 2021-%s %s" % (time.strftime("%Y",
Expand Down
1 change: 0 additions & 1 deletion yamlfixer/__main__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

# -*- coding: utf-8 -*-
#
# This program is free software: you can redistribute it and/or modify
Expand Down
6 changes: 3 additions & 3 deletions yamlfixer/constants.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

# -*- coding: utf-8 -*-
#
# This program is free software: you can redistribute it and/or modify
Expand All @@ -19,8 +18,9 @@

FIX_PASSEDLINTER = 0
FIX_MODIFIED = 1
FIX_SKIPPED = 2
FIX_PERMERROR = 3
FIX_FIXED = 2
FIX_SKIPPED = 3
FIX_PERMERROR = 4

FIXER_UNHANDLED = -1
FIXER_HANDLED = 0
Expand Down
35 changes: 22 additions & 13 deletions yamlfixer/filefixer.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@
import os
import subprocess

from .constants import FIX_PASSEDLINTER, FIX_MODIFIED, FIX_SKIPPED, FIX_PERMERROR
from .constants import FIX_PASSEDLINTER, FIX_MODIFIED, FIX_FIXED, FIX_SKIPPED, FIX_PERMERROR
from .constants import FIXER_HANDLED
from .constants import EXIT_PROBLEM

from .problemfixer import ProblemFixer

LINTERCOMMAND = 'yamllint --format parsable --strict -'

class FileFixer: # pylint: disable=too-many-instance-attributes
class FileFixer: # pylint: disable=too-many-instance-attributes
"""To hold file fixing logic."""
def __init__(self, yamlfixer, filename):
"""Initialize a file to fix."""
Expand Down Expand Up @@ -61,13 +61,13 @@ def canonicalizeproblems(self, linteroutput):
del problemlines[1]
self.issues -= 1
except KeyError:
pass # No problem reported on shebang line by yamllint
pass # No problem reported on shebang line by yamllint
# This line won't ever see the fixer so all subsequent lines must be offset by -1
self.loffset = -1

return problemlines

def lint(self):
def lint(self, contents=None):
"""Launches the linter on a file's contents.
Returns the (linter's exitcode, linter's stdout) tuple.
Expand All @@ -77,7 +77,7 @@ def lint(self):
capture_output=True,
text=True,
check=False,
input=self.incontents,
input=(contents or self.incontents),
encoding='utf-8')
return (linter.returncode, linter.stdout)

Expand All @@ -102,19 +102,20 @@ def dump(self, outcontents):
retcode = FIX_SKIPPED
else:
retcode = FIX_MODIFIED
finaloutput = self.shebang + (outcontents or '')
if self.yfixer.arguments.nochange:
# We don't want to modify anything
if self.filename == '-': # Always dump original input to stdout in this case
if self.filename == '-': # Always dump original input to stdout in this case
sys.stdout.write(self.shebang + (self.incontents or ''))
sys.stdout.flush()
else:
# It seems we really want to fix things.
if self.filename == '-': # Always dump to stdout in this case
sys.stdout.write(self.shebang + (outcontents or ''))
if self.filename == '-': # Always dump to stdout in this case
sys.stdout.write(finaloutput)
sys.stdout.flush()
elif retcode == FIX_MODIFIED: # Don't write unnecessarily
elif retcode == FIX_MODIFIED: # Don't write unnecessarily
try:
if self.yfixer.arguments.backup: # pylint: disable=no-member
if self.yfixer.arguments.backup: # pylint: disable=no-member
# Try to make a backup of the original file
try:
os.replace(self.filename,
Expand All @@ -123,10 +124,18 @@ def dump(self, outcontents):
self.yfixer.error(f"impossible to create a backup : {msg}")
# Overwrite the original file with the new contents
with open(self.filename, 'w') as yamlfile:
yamlfile.write(self.shebang + (outcontents or ''))
yamlfile.write(finaloutput)
except PermissionError as msg:
self.yfixer.error(f"impossible to save fixed contents : {msg}")
self.yfixer.error(f"impossible to save modified contents : {msg}")
retcode = FIX_PERMERROR

# We've successfully modified the file, so we lint its new contents
if retcode == FIX_MODIFIED:
(ltexitcode, _) = self.lint(finaloutput)
if not ltexitcode:
# We know we have succesfully fixed the file
# because it now passes yamlllint's strict mode.
retcode = FIX_FIXED
return retcode

def fix(self):
Expand All @@ -143,7 +152,7 @@ def fix(self):
if not ltexitcode:
self.dump(self.incontents)
return FIX_PASSEDLINTER
if ltexitcode == 127: # yamllint not found !
if ltexitcode == 127: # yamllint not found !
self.yfixer.error("yamllint is not in your PATH, please ensure it's installed.")
sys.exit(EXIT_PROBLEM)

Expand Down
10 changes: 5 additions & 5 deletions yamlfixer/problemfixer.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,14 @@ def get_indentation(self, offset=0):
# warning/error level.
#

def fix_missing_docstart(self, left, right): # pylint: disable=unused-argument
def fix_missing_docstart(self, left, right): # pylint: disable=unused-argument
"""Fix:
- missing document start
"""
self.ffixer.lines.insert(0, '---')
self.ffixer.loffset += 1

def fix_newlineateof(self, left, right): # pylint: disable=unused-argument,no-self-use
def fix_newlineateof(self, left, right): # pylint: disable=unused-argument,no-self-use
"""Fix:
- no new line character at the end of file
"""
Expand Down Expand Up @@ -122,7 +122,7 @@ def fix_trailingspaces(self, left, right):
# No need to adjust coffset because we are at EOL by definition
self.ffixer.lines[self.linenum] = (left + right).rstrip()

def fix_toomany_blanklines(self, left, right): # pylint: disable=unused-argument
def fix_toomany_blanklines(self, left, right): # pylint: disable=unused-argument
"""Fix:
- too many blank lines
"""
Expand All @@ -133,7 +133,7 @@ def fix_toomany_blanklines(self, left, right): # pylint: disable=unused-argument
del self.ffixer.lines[self.linenum - nblines + 1:self.linenum + 1]
self.ffixer.loffset -= nblines

def fix_syntax_tabchar(self, left, right): # pylint: disable=unused-argument
def fix_syntax_tabchar(self, left, right): # pylint: disable=unused-argument
"""Fix:
- syntax error: found character '\\t' that cannot start any token (syntax)
"""
Expand Down Expand Up @@ -207,7 +207,7 @@ def fix_wrong_indentation(self, left, right):
self.ffixer.lines[self.linenum] = (left + right)[-offset:]
self.ffixer.coffset += offset

def fix_linetoolong(self, left, right): # pylint: disable=unused-argument
def fix_linetoolong(self, left, right): # pylint: disable=unused-argument
"""Fix:
- line too long
"""
Expand Down
21 changes: 14 additions & 7 deletions yamlfixer/yamlfixer.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,30 +20,32 @@
import os
import json

from .constants import FIX_PASSEDLINTER, FIX_MODIFIED, FIX_SKIPPED, FIX_PERMERROR
from .constants import FIX_PASSEDLINTER, FIX_MODIFIED, FIX_FIXED, FIX_SKIPPED, FIX_PERMERROR
from .constants import EXIT_OK, EXIT_NOK
from .filefixer import FileFixer
from .problemfixer import ProblemFixer

COLORSEQ = {"PASSED": "38;2;0;255;0m",
"MODIFIED": "38;2;0;0;255m",
"FIXED": "38;2;0;255;0m",
"SKIPPED": "38;2;255;0;255m",
"ERROR": "38;2;255;0;0m",
"UNKNOWN": "38;2;255;255;0m",
}

class YAMLFixer:
class YAMLFixer: # pylint: disable=too-many-instance-attributes
"""To hold files fixing logic."""
def __init__(self, arguments):
"""Initialize the fixer for all files."""
self.arguments = arguments
self.passed = self.modified \
= self.fixed \
= self.skipped \
= self.permerrors \
= self.unknown = 0
self.summary = []

def info(self, message): # pylint: disable=no-self-use
def info(self, message): # pylint: disable=no-self-use
"""Output an informational message to stderr."""
sys.stderr.write(f"{message}\n")

Expand All @@ -52,7 +54,7 @@ def debug(self, message):
if self.arguments.debug:
sys.stderr.write(f"DEBUG: {message}\n")

def error(self, message): # pylint: disable=no-self-use
def error(self, message): # pylint: disable=no-self-use
"""Output an error message to stderr."""
sys.stderr.write(f"ERROR: {message}\n")

Expand All @@ -61,7 +63,8 @@ def statistics(self):
if self.arguments.summary or self.arguments.plainsummary:
self.info(f"Files to fix: {len(self.arguments.filenames)}")
self.info(f"{self.passed} files successfully passed yamllint strict mode")
self.info(f"{self.modified} files were modified")
self.info(f"{self.modified} files were modified but problems remain")
self.info(f"{self.fixed} files were entirely fixed")
self.info(f"{self.skipped} files were skipped")
self.info(f"{self.permerrors} files were not writeable")
self.info(f"{self.unknown} files with unknown status")
Expand All @@ -83,6 +86,7 @@ def statistics(self):
summarymapping = {"filestofix": len(self.arguments.filenames),
"passedstrictmode": self.passed,
"modified": self.modified,
"fixed": self.fixed,
"skipped": self.skipped,
"notwriteable": self.permerrors,
"unknown": self.unknown,
Expand Down Expand Up @@ -117,7 +121,6 @@ def fix(self):
else:
absfilename = os.path.abspath(filename)
filetofix = FileFixer(self, filename)
# pylint: disable=no-member
self.debug(f"Fixing {absfilename} ... ")
status = filetofix.fix()
if status == FIX_PASSEDLINTER:
Expand All @@ -128,6 +131,10 @@ def fix(self):
self.debug(f"was modified.")
txtstatus = "MODIFIED"
self.modified += 1
elif status == FIX_FIXED:
self.debug(f"was fixed.")
txtstatus = " FIXED"
self.fixed += 1
elif status == FIX_SKIPPED:
self.debug(f"was skipped.")
txtstatus = " SKIPPED"
Expand All @@ -146,6 +153,6 @@ def fix(self):
filetofix.issueshandled))

self.statistics()
if (self.passed + self.skipped) == len(self.arguments.filenames):
if (self.passed + self.skipped + self.fixed) == len(self.arguments.filenames):
return EXIT_OK
return EXIT_NOK

0 comments on commit 6384ae9

Please sign in to comment.