manifest: add a --json output option
Sometimes parsing JSON is easier than parsing XML, especially when
the XML format is limited (which ours is). Add a --json option to
the manifest command to quickly emit that form.
Bug: https://crbug.com/gerrit/11743
Change-Id: Ia2bb254a78ae2b70a851638b4545fcafe8c1a76b
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/280436
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
diff --git a/manifest_xml.py b/manifest_xml.py
index bf730ca..e1ef330 100644
--- a/manifest_xml.py
+++ b/manifest_xml.py
@@ -283,9 +283,8 @@
def _ParseGroups(self, groups):
return [x for x in re.split(r'[,\s]+', groups) if x]
- def Save(self, fd, peg_rev=False, peg_rev_upstream=True, peg_rev_dest_branch=True, groups=None):
- """Write the current manifest out to the given file descriptor.
- """
+ def ToXml(self, peg_rev=False, peg_rev_upstream=True, peg_rev_dest_branch=True, groups=None):
+ """Return the current manifest XML."""
mp = self.manifestProject
if groups is None:
@@ -459,6 +458,56 @@
' '.join(self._repo_hooks_project.enabled_repo_hooks))
root.appendChild(e)
+ return doc
+
+ def ToDict(self, **kwargs):
+ """Return the current manifest as a dictionary."""
+ # Elements that may only appear once.
+ SINGLE_ELEMENTS = {
+ 'notice',
+ 'default',
+ 'manifest-server',
+ 'repo-hooks',
+ }
+ # Elements that may be repeated.
+ MULTI_ELEMENTS = {
+ 'remote',
+ 'remove-project',
+ 'project',
+ 'extend-project',
+ 'include',
+ # These are children of 'project' nodes.
+ 'annotation',
+ 'project',
+ 'copyfile',
+ 'linkfile',
+ }
+
+ doc = self.ToXml(**kwargs)
+ ret = {}
+
+ def append_children(ret, node):
+ for child in node.childNodes:
+ if child.nodeType == xml.dom.Node.ELEMENT_NODE:
+ attrs = child.attributes
+ element = dict((attrs.item(i).localName, attrs.item(i).value)
+ for i in range(attrs.length))
+ if child.nodeName in SINGLE_ELEMENTS:
+ ret[child.nodeName] = element
+ elif child.nodeName in MULTI_ELEMENTS:
+ ret.setdefault(child.nodeName, []).append(element)
+ else:
+ raise ManifestParseError('Unhandled element "%s"' % (child.nodeName,))
+
+ append_children(element, child)
+
+ append_children(ret, doc.firstChild)
+
+ return ret
+
+ def Save(self, fd, **kwargs):
+ """Write the current manifest out to the given file descriptor."""
+ doc = self.ToXml(**kwargs)
doc.writexml(fd, '', ' ', '\n', 'UTF-8')
def _output_manifest_project_extras(self, p, e):
diff --git a/subcmds/manifest.py b/subcmds/manifest.py
index f0a0d06..0052d7a 100644
--- a/subcmds/manifest.py
+++ b/subcmds/manifest.py
@@ -15,6 +15,8 @@
# limitations under the License.
from __future__ import print_function
+
+import json
import os
import sys
@@ -68,6 +70,10 @@
help='If in -r mode, do not write the dest-branch field. '
'Only of use if the branch names for a sha1 manifest are '
'sensitive.')
+ p.add_option('--json', default=False, action='store_true',
+ help='Output manifest in JSON format (experimental).')
+ p.add_option('--pretty', default=False, action='store_true',
+ help='Format output for humans to read.')
p.add_option('-o', '--output-file',
dest='output_file',
default='-',
@@ -83,10 +89,26 @@
fd = sys.stdout
else:
fd = open(opt.output_file, 'w')
- self.manifest.Save(fd,
- peg_rev=opt.peg_rev,
- peg_rev_upstream=opt.peg_rev_upstream,
- peg_rev_dest_branch=opt.peg_rev_dest_branch)
+ if opt.json:
+ print('warning: --json is experimental!', file=sys.stderr)
+ doc = self.manifest.ToDict(peg_rev=opt.peg_rev,
+ peg_rev_upstream=opt.peg_rev_upstream,
+ peg_rev_dest_branch=opt.peg_rev_dest_branch)
+
+ json_settings = {
+ # JSON style guide says Uunicode characters are fully allowed.
+ 'ensure_ascii': False,
+ # We use 2 space indent to match JSON style guide.
+ 'indent': 2 if opt.pretty else None,
+ 'separators': (',', ': ') if opt.pretty else (',', ':'),
+ 'sort_keys': True,
+ }
+ fd.write(json.dumps(doc, **json_settings))
+ else:
+ self.manifest.Save(fd,
+ peg_rev=opt.peg_rev,
+ peg_rev_upstream=opt.peg_rev_upstream,
+ peg_rev_dest_branch=opt.peg_rev_dest_branch)
fd.close()
if opt.output_file != '-':
print('Saved manifest to %s' % opt.output_file, file=sys.stderr)