tool.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. # Copyright 2014 Google Inc. All rights reserved.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. """A tool to parse and pretty-print JSON5.
  15. Usage:
  16. $ echo '{foo:"bar"}' | python -m json5
  17. {
  18. foo: 'bar',
  19. }
  20. $ echo '{foo:"bar"}' | python -m json5 --as-json
  21. {
  22. "foo": "bar"
  23. }
  24. """
  25. import argparse
  26. import sys
  27. import json5
  28. from json5.host import Host
  29. from json5.version import __version__
  30. QUOTE_STYLES = {q.value: q for q in json5.QuoteStyle}
  31. def main(argv=None, host=None):
  32. host = host or Host()
  33. args = _parse_args(host, argv)
  34. if args.version:
  35. host.print(__version__)
  36. return 0
  37. if args.cmd:
  38. inp = args.cmd
  39. elif args.file == '-':
  40. inp = host.stdin.read()
  41. else:
  42. inp = host.read_text_file(args.file)
  43. if args.indent == 'None':
  44. args.indent = None
  45. else:
  46. try:
  47. args.indent = int(args.indent)
  48. except ValueError:
  49. pass
  50. if args.as_json:
  51. args.quote_keys = True
  52. args.trailing_commas = False
  53. args.quote_style = json5.QuoteStyle.ALWAYS_DOUBLE.value
  54. obj = json5.loads(inp, strict=args.strict)
  55. s = json5.dumps(
  56. obj,
  57. indent=args.indent,
  58. quote_keys=args.quote_keys,
  59. trailing_commas=args.trailing_commas,
  60. quote_style=QUOTE_STYLES[args.quote_style],
  61. )
  62. host.print(s)
  63. return 0
  64. class _HostedArgumentParser(argparse.ArgumentParser):
  65. """An argument parser that plays nicely w/ host objects."""
  66. def __init__(self, host, **kwargs):
  67. self.host = host
  68. super().__init__(**kwargs)
  69. def exit(self, status=0, message=None):
  70. if message:
  71. self._print_message(message, self.host.stderr)
  72. sys.exit(status)
  73. def error(self, message):
  74. self.host.print(f'usage: {self.usage}', end='', file=self.host.stderr)
  75. self.host.print(' -h/--help for help\n', file=self.host.stderr)
  76. self.exit(2, f'error: {message}\n')
  77. def print_help(self, file=None):
  78. self.host.print(self.format_help(), file=file)
  79. def _parse_args(host, argv):
  80. usage = 'json5 [options] [FILE]\n'
  81. parser = _HostedArgumentParser(
  82. host,
  83. prog='json5',
  84. usage=usage,
  85. description=__doc__,
  86. formatter_class=argparse.RawDescriptionHelpFormatter,
  87. )
  88. parser.add_argument(
  89. '-V',
  90. '--version',
  91. action='store_true',
  92. help=f'show JSON5 library version ({__version__})',
  93. )
  94. parser.add_argument(
  95. '-c',
  96. metavar='STR',
  97. dest='cmd',
  98. help='inline json5 string to read instead of reading from a file',
  99. )
  100. parser.add_argument(
  101. '--as-json',
  102. dest='as_json',
  103. action='store_const',
  104. const=True,
  105. default=False,
  106. help='output as JSON (same as --quote-keys --no-trailing-commas)',
  107. )
  108. parser.add_argument(
  109. '--indent',
  110. dest='indent',
  111. default=4,
  112. help='amount to indent each line (default is 4 spaces)',
  113. )
  114. parser.add_argument(
  115. '--quote-keys',
  116. action='store_true',
  117. default=False,
  118. help='quote all object keys',
  119. )
  120. parser.add_argument(
  121. '--no-quote-keys',
  122. action='store_false',
  123. dest='quote_keys',
  124. help="don't quote object keys that are identifiers "
  125. '(this is the default)',
  126. )
  127. parser.add_argument(
  128. '--trailing-commas',
  129. action='store_true',
  130. default=True,
  131. help='add commas after the last item in multi-line '
  132. 'objects and arrays (this is the default)',
  133. )
  134. parser.add_argument(
  135. '--no-trailing-commas',
  136. dest='trailing_commas',
  137. action='store_false',
  138. help='do not add commas after the last item in multi-line lists '
  139. 'and objects',
  140. )
  141. parser.add_argument(
  142. '--strict',
  143. action='store_true',
  144. default=True,
  145. help='Do not allow control characters (\\x00-\\x1f) in strings '
  146. '(default)',
  147. )
  148. parser.add_argument(
  149. '--no-strict',
  150. dest='strict',
  151. action='store_false',
  152. help='Allow control characters (\\x00-\\x1f) in strings',
  153. )
  154. parser.add_argument(
  155. '--quote-style',
  156. action='store',
  157. default='always_double',
  158. choices=QUOTE_STYLES.keys(),
  159. help='Controls how strings are encoded. By default they are always '
  160. 'double-quoted ("always_double")',
  161. )
  162. parser.add_argument(
  163. 'file',
  164. metavar='FILE',
  165. nargs='?',
  166. default='-',
  167. help='optional file to read JSON5 document from; if '
  168. 'not specified or "-", will read from stdin '
  169. 'instead',
  170. )
  171. return parser.parse_args(argv)
  172. if __name__ == '__main__': # pragma: no cover
  173. sys.exit(main())