]> git.ipfire.org Git - people/ms/ipfire-3.x.git/commitdiff
Introduced a new script that checks the code (-quality).
authorMichael Tremer <michael.tremer@ipfire.org>
Sat, 12 Jul 2008 20:50:06 +0000 (20:50 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Sat, 12 Jul 2008 20:50:06 +0000 (20:50 +0000)
Will need some testing.

tools/code-beautify [new file with mode: 0644]
tools/make-check
tools/make-git
tools/make-interactive

diff --git a/tools/code-beautify b/tools/code-beautify
new file mode 100644 (file)
index 0000000..a9c3a92
--- /dev/null
@@ -0,0 +1,338 @@
+#! /usr/bin/python
+
+"""code-saner [-d][-r][-v] [ path ... ]
+
+-d (--dryrun)  Dry run.  Analyze, but don't make any changes to, files.
+-r (--recurse) Recurse.  Search for all .py files in subdirectories too.
+-b (--backup)  Keep backups of files.
+-v (--verbose) Verbose.  Print informative msgs; else no output.
+-h (--help)    Help.     Print this usage information and exit.
+"""
+
+__version__ = "1"
+
+import tokenize
+import os
+import sys
+
+verbose = 0
+recurse = 0
+dryrun  = 0
+backup  = 0
+
+def usage(msg=None):
+    if msg is not None:
+        print >> sys.stderr, msg
+    print >> sys.stderr, __doc__
+
+def errprint(*args):
+    sep = ""
+    for arg in args:
+        sys.stderr.write(sep + str(arg))
+        sep = " "
+    sys.stderr.write("\n")
+
+def main():
+    import getopt
+    global verbose, recurse, dryrun, backup
+    try:
+        opts, args = getopt.getopt(sys.argv[1:], "drbvh",
+                                   ["dryrun", "recurse", "backup" "verbose", "help"])
+    except getopt.error, msg:
+        usage(msg)
+        return
+    for o, a in opts:
+        if o in ('-d', '--dryrun'):
+            dryrun += 1
+        elif o in ('-r', '--recurse'):
+            recurse += 1
+        elif o in ('-b', '--backup'):
+            backup += 1
+        elif o in ('-v', '--verbose'):
+            verbose += 1
+        elif o in ('-h', '--help'):
+            usage()
+            return
+    if not args:
+        r = Reindenter(sys.stdin)
+        r.run()
+        r.write(sys.stdout)
+        return
+    for arg in args:
+        check(arg)
+
+endings = {
+    'images' : (".png", ".gif", ".jpg", ".jpeg",),
+    'styles' : (".css",),
+    'config' : (".conf", ".cnf", ".cf", ".cfg", ".config",),
+    'python' : (".py",),
+    'script' : (".pl", ".c", ".h", ".sh", ".txt",),
+}
+
+def FileObject(file):
+    object = None
+    try:
+        f = open(file)
+    except IOError, msg:
+        errprint("%s: I/O Error: %s" % (file, str(msg)))
+        raise
+
+    # Python
+    for ending in endings['python']:
+        if file.endswith(ending):
+            object = PythonFile(f=f, file=file)
+
+    # Configs
+    for ending in endings['config']:
+        if file.endswith(ending):
+            object = ConfigFile(f=f, file=file)
+
+    # Perl
+    for ending in endings['script']:
+        if file.endswith(ending):
+            object = ScriptFile(f=f, file=file)
+
+    # Styles
+    for ending in endings['styles']:
+        if file.endswith(ending):
+            object = StyleFile(f=f, file=file)
+
+    # Images
+    for ending in endings['images']:
+        if file.endswith(ending):
+            object = ImageFile(f=f, file=file)
+
+    f.close()
+    return object or UnknownFile(f=f, file=file)
+
+def check(file):
+    if os.path.isdir(file) and not os.path.islink(file):
+        if verbose:
+            print "listing directory", file
+        names = os.listdir(file)
+        for name in names:
+            fullname = os.path.join(file, name)
+            if ((recurse and not os.path.islink(fullname))):
+                check(fullname)
+        return
+
+    if verbose:
+        print "checking", file, "...",
+    try:
+        r = FileObject(file)
+    except IOError:
+        return
+
+    if r.run():
+        if verbose:
+            print "changed."
+            if dryrun:
+                print "But this is a dry run, so leaving it alone."
+        if not dryrun:
+            if backup:
+                bak = file + ".bak"
+                if os.path.exists(bak):
+                    os.remove(bak)
+                os.rename(file, bak)
+                if verbose:
+                    print "renamed", file, "to", bak
+            f = open(file, "w")
+            r.write(f)
+            f.close()
+            if verbose:
+                print "wrote new", file
+    else:
+        if verbose:
+            print "unchanged."
+
+def _rstrip(line, junk='\n \t'):
+    """Return line stripped of trailing spaces, tabs, newlines.
+
+    Note that line.rstrip() instead also strips sundry control characters,
+    but at least one known Emacs user expects to keep junk like that, not
+    mentioning Barry by name or anything <wink>.
+    """
+
+    i = len(line)
+    while i > 0 and line[i-1] in junk:
+        i -= 1
+    return line[:i]
+
+# Count number of leading blanks.
+def getlspace(line):
+    i, n = 0, len(line)
+    while i < n and line[i] == " ":
+        i += 1
+    return i
+
+class DefaultFile:
+    def __init__(self, f, file=None):
+        self.file = file
+
+    def run(self):
+        pass
+        #if verbose:
+        #    errprint("Can't guess filetype of given file %s" % self.file)
+
+    def rm_trailing_lines(self, lines):
+        # Remove trailing empty lines.
+        while lines and lines[-1] == "\n":
+            lines.pop()
+        return lines
+
+    def expand_tabs(self, lines):
+        return [_rstrip(line).expandtabs() + "\n" for line in lines]
+
+class TextFile(DefaultFile):
+    def __init__(self, f, file=None):
+        self.file = file            # Save filename.
+        self.raw = f.readlines()    # Raw file lines.
+
+        # File lines, rstripped. Dummy at start is so
+        # that we can use tokenize's 1-based line numbering easily.
+        # Note that a line is all-blank iff it's "\n".
+        self.lines = [_rstrip(line) + "\n" for line in self.raw]
+
+    def run(self):
+        self.after = self.lines
+        return self.raw != self.after
+
+    def write(self, f):
+        f.writelines(self.after)
+
+class PythonFile(TextFile):
+    def __init__(self, f, file=None):
+        TextFile.__init__(self, f, file)
+        self.find_stmt = 1  # next token begins a fresh stmt?
+        self.level = 0      # current indent level
+
+        self.lines = self.expand_tabs(self.lines)
+        self.lines.insert(0, None)
+        self.index = 1  # index into self.lines of next line
+
+        # List of (lineno, indentlevel) pairs, one for each stmt and
+        # comment line.  indentlevel is -1 for comment lines, as a
+        # signal that tokenize doesn't know what to do about them;
+        # indeed, they're our headache!
+        self.stats = []
+
+    def run(self):
+        tokenize.tokenize(self.getline, self.tokeneater)
+        # Remove trailing empty lines.
+        lines = self.rm_trailing_lines(self.lines)
+        # Sentinel.
+        stats = self.stats
+        stats.append((len(lines), 0))
+        # Map count of leading spaces to # we want.
+        have2want = {}
+        # Program after transformation.
+        after = self.after = []
+        # Copy over initial empty lines -- there's nothing to do until
+        # we see a line with *something* on it.
+        i = stats[0][0]
+        after.extend(lines[1:i])
+        for i in range(len(stats)-1):
+            thisstmt, thislevel = stats[i]
+            nextstmt = stats[i+1][0]
+            have = getlspace(lines[thisstmt])
+            want = thislevel * 4
+            if want < 0:
+                # A comment line.
+                if have:
+                    # An indented comment line.  If we saw the same
+                    # indentation before, reuse what it most recently
+                    # mapped to.
+                    want = have2want.get(have, -1)
+                    if want < 0:
+                        # Then it probably belongs to the next real stmt.
+                        for j in xrange(i+1, len(stats)-1):
+                            jline, jlevel = stats[j]
+                            if jlevel >= 0:
+                                if have == getlspace(lines[jline]):
+                                    want = jlevel * 4
+                                break
+                    if want < 0:           # Maybe it's a hanging
+                                           # comment like this one,
+                        # in which case we should shift it like its base
+                        # line got shifted.
+                        for j in xrange(i-1, -1, -1):
+                            jline, jlevel = stats[j]
+                            if jlevel >= 0:
+                                want = have + getlspace(after[jline-1]) - \
+                                       getlspace(lines[jline])
+                                break
+                    if want < 0:
+                        # Still no luck -- leave it alone.
+                        want = have
+                else:
+                    want = 0
+            assert want >= 0
+            have2want[have] = want
+            diff = want - have
+            if diff == 0 or have == 0:
+                after.extend(lines[thisstmt:nextstmt])
+            else:
+                for line in lines[thisstmt:nextstmt]:
+                    if diff > 0:
+                        if line == "\n":
+                            after.append(line)
+                        else:
+                            after.append(" " * diff + line)
+                    else:
+                        remove = min(getlspace(line), -diff)
+                        after.append(line[remove:])
+        return self.raw != self.after
+
+    # Line-getter for tokenize.
+    def getline(self):
+        if self.index >= len(self.lines):
+            line = ""
+        else:
+            line = self.lines[self.index]
+            self.index += 1
+        return line
+
+    # Line-eater for tokenize.
+    def tokeneater(self, type, token, (sline, scol), end, line,
+                   INDENT=tokenize.INDENT,
+                   DEDENT=tokenize.DEDENT,
+                   NEWLINE=tokenize.NEWLINE,
+                   COMMENT=tokenize.COMMENT,
+                   NL=tokenize.NL):
+
+        if type == NEWLINE:
+            # A program statement, or ENDMARKER, will eventually follow,
+            # after some (possibly empty) run of tokens of the form
+            #     (NL | COMMENT)* (INDENT | DEDENT+)?
+            self.find_stmt = 1
+
+        elif type == INDENT:
+            self.find_stmt = 1
+            self.level += 1
+
+        elif type == DEDENT:
+            self.find_stmt = 1
+            self.level -= 1
+
+        elif type == COMMENT:
+            if self.find_stmt:
+                self.stats.append((sline, -1))
+                # but we're still looking for a new stmt, so leave
+                # find_stmt alone
+
+        elif type == NL:
+            pass
+
+        elif self.find_stmt:
+            # This is the first "real token" following a NEWLINE, so it
+            # must be the first token of the next program statement, or an
+            # ENDMARKER.
+            self.find_stmt = 0
+            if line:   # not endmarker
+                self.stats.append((sline, self.level))
+
+UnknownFile = ImageFile = DefaultFile           # just the empty file object, do nothing
+ConfigFile = ScriptFile = StyleFile = TextFile    # simple textfiles
+
+if __name__ == '__main__':
+    main()
index 8d9ed2195a6870785ce34e8eca52a5ca490c9e0e..d85f0b69f8984531b7c31c631c46ab86c4545074 100644 (file)
@@ -140,11 +140,41 @@ check_rootfiles() {
 
 }
 
+check_code() {
+    ACTION=$1
+    ARGS="--recurse"
+
+    echo -n " Running code checks"
+
+       case "$ACTION" in
+               --fix)
+                       echo -n " and fix"
+                       ;;
+               "")
+                       ARGS+=" --dryrun"
+                       ;;
+               *)
+                       exiterror "This is not a valid option: $ACTION"
+                       ;;
+       esac
+       
+       echo -n "..." # End the line
+
+    python $BASEDIR/tools/code-beautify $ARGS $BASEDIR/{config,doc,lfs,src,tools}
+    if [ "$?" -eq "0" ]; then
+        beautify message DONE
+    else
+        beautify message FAIL
+        exit 1
+    fi
+}
+
 check_sanity() {
 
        echo "Checking sanity of working directory..."
 
        check_rootfiles $*
+       check_code $*
 
 }
 
index 443d3764309af93eae4199f0dc0585edd5ad86cd..aa67fb7eb827f3ceb02ef990f4a253674f7b6765 100644 (file)
@@ -109,7 +109,7 @@ git_commit() {
 }
 
 git_diff() {
-       check_sanity
+       check_sanity --fix
 
        echo -ne "Make a local diff to last revision"
        git diff HEAD > ipfire-diff-$(date +'%Y-%m-%d-%H:%M').diff
index be3d624de5448f4fd4734af9df1dda625ff8ebfd..8bd16aa09acdc0f1ceac7adeaa78e8d50f013d62 100644 (file)
@@ -27,6 +27,8 @@ case "$1" in
 build)
        # check for prerequisites
        check_build
+       # check for sanity of code
+       check_sanity
        # compile the distro right now
        build
        # beeps when finished