File: json.format.py

package info (click to toggle)
behave 1.2.6-6
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 4,160 kB
  • sloc: python: 19,857; makefile: 137; sh: 18
file content (167 lines) | stat: -rwxr-xr-x 6,122 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Utility script to format/beautify one or more JSON files.

REQUIRES: Python >= 2.6 (json module is part of Python standard library)
LICENSE:  BSD
"""

from __future__ import absolute_import

__author__    = "Jens Engel"
__copyright__ = "(c) 2011-2013 by Jens Engel"
VERSION = "0.2.2"

# -- IMPORTS:
import os.path
import glob
import logging
from optparse import OptionParser
import sys
try:
    import json
except ImportError:
    import simplejson as json   #< BACKWARD-COMPATIBLE: Python <= 2.5


# ----------------------------------------------------------------------------
# CONSTANTS:
# ----------------------------------------------------------------------------
DEFAULT_INDENT_SIZE = 2

# ----------------------------------------------------------------------------
# FUNCTIONS:
# ----------------------------------------------------------------------------
def json_format(filename, indent=DEFAULT_INDENT_SIZE, **kwargs):
    """
    Format/Beautify a JSON file.

    :param filename:    Filename of a JSON file to process.
    :param indent:      Number of chars to indent per level (default: 4).
    :returns: >= 0, if successful (written=1, skipped=2). Zero(0), otherwise.
    :raises:  ValueError,           if parsing JSON file contents fails.
    :raises:  json.JSONDecodeError, if parsing JSON file contents fails.
    :raises:  IOError (Error 2), if file not found.
    """
    console  = kwargs.get("console", logging.getLogger("console"))
    encoding = kwargs.get("encoding", None)
    dry_run  = kwargs.get("dry_run", False)
    if indent is None:
        sort_keys = False
    else:
        sort_keys = True

    message = "%s ..." % filename
#    if not (os.path.exists(filename) and os.path.isfile(filename)):
#        console.error("%s ERROR: file not found.", message)
#        return 0

    contents = open(filename, "r").read()
    data      = json.loads(contents, encoding=encoding)
    contents2 = json.dumps(data, indent=indent, sort_keys=sort_keys)
    contents2 = contents2.strip()
    contents2 = "%s\n" % contents2
    if contents == contents2:
        console.info("%s SKIP (already pretty)", message)
        return 2 #< SKIPPED.
    elif not dry_run:
        outfile = open(filename, "w")
        outfile.write(contents2)
        outfile.close()
        console.warn("%s OK", message)
        return 1 #< OK

def json_formatall(filenames, indent=DEFAULT_INDENT_SIZE, dry_run=False):
    """
    Format/Beautify a JSON file.

    :param filenames:  Format one or more JSON files.
    :param indent:     Number of chars to indent per level (default: 4).
    :returns:  0, if successful. Otherwise, number of errors.
    """
    errors = 0
    console = logging.getLogger("console")
    for filename in filenames:
        try:
            result = json_format(filename, indent=indent, console=console,
                                 dry_run=dry_run)
            if not result:
                errors += 1
#        except json.decoder.JSONDecodeError, e:
#            console.error("ERROR: %s (filename: %s)", e, filename)
#            errors += 1
        except Exception as e:
            console.error("ERROR %s: %s (filename: %s)",
                          e.__class__.__name__, e, filename)
            errors += 1
    return errors

# ----------------------------------------------------------------------------
# MAIN FUNCTION:
# ----------------------------------------------------------------------------
def main(args=None):
    """Boilerplate for this script."""
    if args is None:
        args = sys.argv[1:]

    usage_ = """%prog [OPTIONS] JsonFile [MoreJsonFiles...]
Format/Beautify one or more JSON file(s)."""
    parser = OptionParser(usage=usage_, version=VERSION)
    parser.add_option("-i", "--indent", dest="indent_size",
                default=DEFAULT_INDENT_SIZE, type="int",
                help="Indent size to use (default: %default).")
    parser.add_option("-c", "--compact", dest="compact",
                action="store_true", default=False,
                help="Use compact format (default: %default).")
    parser.add_option("-n", "--dry-run", dest="dry_run",
                action="store_true", default=False,
                help="Check only if JSON is well-formed (default: %default).")
    options, filenames = parser.parse_args(args)    #< pylint: disable=W0612
    if not filenames:
        parser.error("OOPS, no filenames provided.")
    if options.compact:
        options.indent_size = None

    # -- STEP: Init logging subsystem.
    format_ = "json.format: %(message)s"
    logging.basicConfig(level=logging.WARN, format=format_)
    console = logging.getLogger("console")

    # -- DOS-SHELL SUPPORT: Perform filename globbing w/ wildcards.
    skipped = 0
    filenames2 = []
    for filename in filenames:
        if "*" in filenames:
            files = glob.glob(filename)
            filenames2.extend(files)
        elif os.path.isdir(filename):
            # -- CONVENIENCE-SHORTCUT: Use DIR as shortcut for JSON files.
            files = glob.glob(os.path.join(filename, "*.json"))
            filenames2.extend(files)
            if not files:
                console.info("SKIP %s, no JSON files found in dir.", filename)
                skipped += 1
        elif not os.path.exists(filename):
            console.warn("SKIP %s, file not found.", filename)
            skipped += 1
            continue
        else:
            assert os.path.exists(filename)
            filenames2.append(filename)
    filenames = filenames2

    # -- NORMAL PROCESSING:
    errors  = json_formatall(filenames, options.indent_size,
                             dry_run=options.dry_run)
    console.error("Processed %d files (%d with errors, skipped=%d).",
                  len(filenames), errors, skipped)
    if not filenames:
        errors += 1
    return errors

# ----------------------------------------------------------------------------
# AUTO-MAIN:
# ----------------------------------------------------------------------------
if __name__ == "__main__":
    sys.exit(main())