-
Notifications
You must be signed in to change notification settings - Fork 14
/
json_include.py
120 lines (85 loc) · 3.57 KB
/
json_include.py
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
#!/usr/bin/env python
# coding: utf-8
import os
import re
import json
from collections import OrderedDict
import argparse
OBJECT_TYPES = (dict, list)
INCLUDE_KEY = '...'
INCLUDE_VALUE_PATTERN = re.compile(r'^<([\w\-\.]+)>$')
_included_cache = {}
def read_file(filepath):
with open(filepath, 'r') as f:
return f.read()
def get_include_name(value):
if isinstance(value, basestring):
rv = INCLUDE_VALUE_PATTERN.search(value)
if rv:
return rv.groups()[0]
return None
def walk_through_to_include(o, dirpath):
if isinstance(o, dict):
is_include_exp = False
if set(o) == set([INCLUDE_KEY]):
include_name = get_include_name(o.values()[0])
if include_name:
is_include_exp = True
o.clear()
if include_name not in _included_cache:
_included_cache[include_name] = parse_json_include(dirpath, include_name, True)
o.update(_included_cache[include_name])
if is_include_exp:
return
for k, v in o.iteritems():
if isinstance(v, OBJECT_TYPES):
walk_through_to_include(v, dirpath)
elif isinstance(o, list):
for i in o:
if isinstance(i, OBJECT_TYPES):
walk_through_to_include(i, dirpath)
def parse_json_include(dirpath, filename, is_include=False):
filepath = os.path.join(dirpath, filename)
json_str = read_file(filepath)
d = json.loads(json_str, object_pairs_hook=OrderedDict)
if is_include:
assert isinstance(d, dict),\
'The JSON file being included should always be a dict rather than a list'
walk_through_to_include(d, dirpath)
return d
def build_json_include(dirpath, filename, indent=4):
"""Parse a json file and build it by the include expression recursively.
:param str dirpath: The directory path of source json files.
:param str filename: The name of the source json file.
:return: A json string with its include expression replaced by the indicated data.
:rtype: str
"""
d = parse_json_include(dirpath, filename)
return json.dumps(d, indent=indent, separators=(',', ': '))
def build_json_include_to_files(dirpath, filenames, target_dirpath, indent=4):
"""Build a list of source json files and write the built result into
target directory path with the same file name they have.
Since all the included JSON will be cached in the parsing process,
this function will be a better way to handle multiple files than build each
file seperately.
:param str dirpath: The directory path of source json files.
:param list filenames: A list of source json files.
:param str target_dirpath: The directory path you want to put built result into.
:rtype: None
"""
assert isinstance(filenames, list), '`filenames must be a list`'
if not os.path.exists(target_dirpath):
os.makedirs(target_dirpath)
for i in filenames:
json = build_json_include(dirpath, i, indent)
target_filepath = os.path.join(target_dirpath, i)
with open(target_filepath, 'w') as f:
f.write(json)
def main():
parser = argparse.ArgumentParser(description='Command line tool to build JSON file by include syntax.')
parser.add_argument('dirpath', metavar="DIR", help="The directory path of source json files")
parser.add_argument('filename', metavar="FILE", help="The name of the source json file")
args = parser.parse_args()
print build_json_include(args.dirpath, args.filename)
if __name__ == '__main__':
main()