grepsr-cli 0.7.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- grepsr_cli-0.7.4.dist-info/LICENSE.md +1 -0
- grepsr_cli-0.7.4.dist-info/METADATA +92 -0
- grepsr_cli-0.7.4.dist-info/RECORD +35 -0
- grepsr_cli-0.7.4.dist-info/WHEEL +5 -0
- grepsr_cli-0.7.4.dist-info/entry_points.txt +2 -0
- grepsr_cli-0.7.4.dist-info/top_level.txt +1 -0
- grepsrcli/__init__.py +0 -0
- grepsrcli/controllers/__init__.py +0 -0
- grepsrcli/controllers/base.py +55 -0
- grepsrcli/controllers/crawler.py +480 -0
- grepsrcli/controllers/generate.py +34 -0
- grepsrcli/controllers/report.py +63 -0
- grepsrcli/core/__init__.py +0 -0
- grepsrcli/core/aws_s3.py +39 -0
- grepsrcli/core/config.py +191 -0
- grepsrcli/core/exc.py +4 -0
- grepsrcli/core/input_prompts.py +0 -0
- grepsrcli/core/message_log.py +34 -0
- grepsrcli/core/multiproc_server.py +126 -0
- grepsrcli/core/report_api.py +39 -0
- grepsrcli/core/sdk_setup.py +84 -0
- grepsrcli/core/test_local.py +136 -0
- grepsrcli/core/utils.py +308 -0
- grepsrcli/ext/__init__.py +0 -0
- grepsrcli/main.py +91 -0
- grepsrcli/plugins/__init__.py +0 -0
- grepsrcli/templates/__init__.py +0 -0
- grepsrcli/templates/autocomplete.jinja2 +12 -0
- grepsrcli/templates/autocomplete_zsh.jinja2 +20 -0
- grepsrcli/templates/composer.jinja2 +21 -0
- grepsrcli/templates/node_boilerplate.jinja2 +100 -0
- grepsrcli/templates/php_boilerplate.jinja2 +63 -0
- grepsrcli/templates/php_brp_boilerplate.jinja2 +83 -0
- grepsrcli/templates/php_vc_boilerplate.jinja2 +65 -0
- grepsrcli/templates/py_boilerplate.jinja2 +56 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
from os import path, system
|
|
2
|
+
import os
|
|
3
|
+
import json
|
|
4
|
+
import subprocess
|
|
5
|
+
import platform
|
|
6
|
+
from .message_log import Log
|
|
7
|
+
from .config import load_config, config_path as CONFIG_PATH
|
|
8
|
+
from .utils import get_docker_entrypoints, write_text, read_text
|
|
9
|
+
|
|
10
|
+
class TestLocal:
|
|
11
|
+
def __init__(self, type, base_path, plugin_name, params=None, params_file=None, is_multi_proc=False):
|
|
12
|
+
self.type = type
|
|
13
|
+
self.plugin_name = plugin_name
|
|
14
|
+
self.base_path = base_path
|
|
15
|
+
|
|
16
|
+
self.tmp_path = path.expanduser('~') + '/.grepsr/tmp/'
|
|
17
|
+
self.config = load_config('config.yml')
|
|
18
|
+
self.type_config = self.config[type]
|
|
19
|
+
self.params = params
|
|
20
|
+
self.params_file = params_file
|
|
21
|
+
self.is_multi_proc = is_multi_proc
|
|
22
|
+
self.test_script()
|
|
23
|
+
|
|
24
|
+
def test_script(self):
|
|
25
|
+
if self.type == "php":
|
|
26
|
+
if "env" in self.type_config:
|
|
27
|
+
if self.type_config['env']:
|
|
28
|
+
self.test_script_php(env=True)
|
|
29
|
+
else:
|
|
30
|
+
self.test_script_php(env=False)
|
|
31
|
+
else:
|
|
32
|
+
self.test_script_php(env=False)
|
|
33
|
+
elif self.type == "php_next":
|
|
34
|
+
self.test_script_php_next()
|
|
35
|
+
|
|
36
|
+
def test_script_php(self, env=False):
|
|
37
|
+
if self.is_multi_proc is None:
|
|
38
|
+
# only use the value of multiprocess if it is not passed explicitly from cmd line
|
|
39
|
+
try:
|
|
40
|
+
self.is_multi_proc = '1' if self.type_config['multiprocess'] == True else '0'
|
|
41
|
+
except KeyError:
|
|
42
|
+
pass
|
|
43
|
+
env_vars = []
|
|
44
|
+
if env:
|
|
45
|
+
env_vars = [
|
|
46
|
+
f'-e {env_var}={self.type_config["env"][env_var]}' for env_var in self.type_config["env"].keys()
|
|
47
|
+
]
|
|
48
|
+
if os.environ.get('GCLI_CHILD_PROC_NUM'):
|
|
49
|
+
print('\n', flush=True)
|
|
50
|
+
print('This is Child Process Number:', os.environ.get('GCLI_CHILD_PROC_NUM'), flush=True)
|
|
51
|
+
print('\n', flush=True)
|
|
52
|
+
env_vars.append(f'-e GCLI_CHILD_PROC_NUM={os.environ.get("GCLI_CHILD_PROC_NUM")}')
|
|
53
|
+
sdk_args_list = [
|
|
54
|
+
f'-s {self.plugin_name}',
|
|
55
|
+
]
|
|
56
|
+
if self.is_multi_proc is not None:
|
|
57
|
+
sdk_args_list.append(f'-m {self.is_multi_proc}')
|
|
58
|
+
docker_image_name = self.type_config["sdk_image"]
|
|
59
|
+
json_content = self.params
|
|
60
|
+
pre_entrypoint = self.type_config.get('pre_entry_run_file')
|
|
61
|
+
only_service = (not self.params) and (not self.params_file) and (not pre_entrypoint)
|
|
62
|
+
bash_cmd = ''
|
|
63
|
+
if only_service:
|
|
64
|
+
sdk_args = ' '.join(sdk_args_list)
|
|
65
|
+
else:
|
|
66
|
+
if self.params_file:
|
|
67
|
+
# should be able to pass any path, not limited to just tmp/
|
|
68
|
+
fp = path.expandvars(path.expanduser((self.params_file)))
|
|
69
|
+
if not path.isabs(fp):
|
|
70
|
+
fp = path.join(self.tmp_path, fp)
|
|
71
|
+
with open(fp, 'r', encoding='UTF-8') as fd:
|
|
72
|
+
json_content = fd.read()
|
|
73
|
+
|
|
74
|
+
pipe_params = ''
|
|
75
|
+
sh_contents = ['#!/bin/bash']
|
|
76
|
+
if json_content:
|
|
77
|
+
sdk_args_list.append("-p")
|
|
78
|
+
|
|
79
|
+
# json decode and encode is required to remove spaces from the passed params
|
|
80
|
+
# because the bash shell file that forwards argument is not quoted ($@)
|
|
81
|
+
# which causes it to treat spaces anywhere as being a different argv
|
|
82
|
+
params_decoded = json.loads(json_content)
|
|
83
|
+
compact_json = json.dumps({'params': params_decoded}, separators=[',', ':'])
|
|
84
|
+
# now that all non important spaces are removed, replace spaces
|
|
85
|
+
# inside (keys and) values with unicode code-point which is luckily accepted by shell.
|
|
86
|
+
# hack: if the shell cannot handle space char, sneak the space with a makeup.
|
|
87
|
+
compact_json = compact_json.replace(' ', '\\u0020')
|
|
88
|
+
|
|
89
|
+
tmp_json_file = '_gcli_internal_params_tmp.json'
|
|
90
|
+
# write the compacted json, to tmp/ directory whcih will get mounted
|
|
91
|
+
# to docker container. Params are written to temporary file and not passed
|
|
92
|
+
# in the shell due to quoting issues.
|
|
93
|
+
write_text(path.join(self.tmp_path, tmp_json_file), compact_json)
|
|
94
|
+
pipe_params = f"$(</tmp/{tmp_json_file})"
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
custom_shell_file = '_gcli_internal_shell_file.sh'
|
|
98
|
+
custom_shell_file_local = path.join(self.tmp_path, custom_shell_file)
|
|
99
|
+
|
|
100
|
+
if pre_entrypoint:
|
|
101
|
+
sh_contents.append(read_text(path.join(self.tmp_path, pre_entrypoint)))
|
|
102
|
+
|
|
103
|
+
sdk_args = ' '.join(sdk_args_list)
|
|
104
|
+
original_entrypoints = get_docker_entrypoints(image_name=docker_image_name)
|
|
105
|
+
sh_contents.append(f'source {original_entrypoints[0]} {sdk_args} {pipe_params}')
|
|
106
|
+
write_text(custom_shell_file_local, '\n'.join(sh_contents))
|
|
107
|
+
|
|
108
|
+
bash_cmd = f'source /tmp/{custom_shell_file}'
|
|
109
|
+
|
|
110
|
+
win_single = (platform.system() == 'Windows') and (self.is_multi_proc == '1')
|
|
111
|
+
commands = [
|
|
112
|
+
f'docker run {"-i" if win_single else ""} -t --network="host" --rm',
|
|
113
|
+
f'-v {self.base_path}:/home/grepsr/vortex-plugins/{path.basename(self.base_path)}',
|
|
114
|
+
f'-v {self.tmp_path}:/tmp',
|
|
115
|
+
f'-e APP_ENV={self.config["app_env"]}',
|
|
116
|
+
" ".join(env_vars),
|
|
117
|
+
f'--entrypoint=""' if not only_service else '',
|
|
118
|
+
docker_image_name,
|
|
119
|
+
f'bash -ci "{bash_cmd}"' if not only_service else sdk_args,
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
command = ' '.join([command for command in commands if command])
|
|
123
|
+
# print(command, flush=True)
|
|
124
|
+
system(command)
|
|
125
|
+
|
|
126
|
+
def test_script_php_next(self):
|
|
127
|
+
|
|
128
|
+
command = f'''docker run -t -i --network="host" --rm \
|
|
129
|
+
-v {self.base_path}:/home/grepsr/vortex-backend-next/scraper-plugins \
|
|
130
|
+
-v {self.tmp_path}:/tmp \
|
|
131
|
+
-e APP_ENV={self.config['app_env']} \
|
|
132
|
+
{self.type_config['sdk_image']} -s {self.plugin_name} '''
|
|
133
|
+
system(command)
|
|
134
|
+
|
|
135
|
+
def test_script_local(self):
|
|
136
|
+
pass
|
grepsrcli/core/utils.py
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import subprocess
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from os import listdir, path, mkdir
|
|
5
|
+
import re
|
|
6
|
+
from jinja2 import Template
|
|
7
|
+
from .config import load_config
|
|
8
|
+
from terminaltables import SingleTable
|
|
9
|
+
from .message_log import Log
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_plugin_path(plugin_name, type='php', all_types=False):
|
|
13
|
+
""" returns the path of the plugin if plugin name exist
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
plugin_name (str): name of the service plugin .
|
|
17
|
+
type (str, optional): php|py|node. Defaults to 'php'.
|
|
18
|
+
all_types: retuns the path regardless the plugin types i.e php, py and node
|
|
19
|
+
"""
|
|
20
|
+
config = load_config('config.yml')
|
|
21
|
+
if all_types == False:
|
|
22
|
+
if type == 'php':
|
|
23
|
+
if _generate_plugin_path(plugin_name, config['php']['paths']):
|
|
24
|
+
return _generate_plugin_path(plugin_name, config['php']['paths'])
|
|
25
|
+
else:
|
|
26
|
+
return False
|
|
27
|
+
elif type == 'node':
|
|
28
|
+
if _generate_plugin_path(plugin_name, config['node']['paths']):
|
|
29
|
+
return _generate_plugin_path(plugin_name, config['node']['paths'])
|
|
30
|
+
else:
|
|
31
|
+
return False
|
|
32
|
+
elif type == 'py':
|
|
33
|
+
if _generate_plugin_path(plugin_name, config['python']['paths']):
|
|
34
|
+
return _generate_plugin_path(plugin_name, config['python']['paths'])
|
|
35
|
+
else:
|
|
36
|
+
return False
|
|
37
|
+
elif type == 'php_next':
|
|
38
|
+
if _generate_plugin_path(plugin_name, config['php_next']['paths']):
|
|
39
|
+
return _generate_plugin_path(plugin_name, config['php_next']['paths'])
|
|
40
|
+
else:
|
|
41
|
+
return False
|
|
42
|
+
elif type == 'node_next':
|
|
43
|
+
if _generate_plugin_path(plugin_name, config['node_next']['paths']):
|
|
44
|
+
return _generate_plugin_path(plugin_name, config['node_next']['paths'])
|
|
45
|
+
else:
|
|
46
|
+
return False
|
|
47
|
+
else:
|
|
48
|
+
Log.error("Invalid params for type.")
|
|
49
|
+
return False
|
|
50
|
+
else:
|
|
51
|
+
# todo: do not hardcode this
|
|
52
|
+
base_paths_list = [
|
|
53
|
+
config['php']['paths'] if 'php' in config else None,
|
|
54
|
+
config['node']['paths'] if 'node' in config else None,
|
|
55
|
+
config['python']['paths'] if 'python' in config else None,
|
|
56
|
+
config['php_next']['paths'] if 'php_next' in config else None,
|
|
57
|
+
config['node_next']['paths'] if 'node_next' in config else None
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
for base_paths in base_paths_list:
|
|
61
|
+
if base_paths is not None and _generate_plugin_path(plugin_name, base_paths):
|
|
62
|
+
return _generate_plugin_path(plugin_name, base_paths)
|
|
63
|
+
Log.error("Plugin name not found.")
|
|
64
|
+
return False
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def create_boilerplate(folder_path, boilerplate, data, extention,):
|
|
68
|
+
""" to create boilerplate for a given path
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
folder_path (str): the destination path
|
|
72
|
+
boilerplate (str): the name of the boilerplate
|
|
73
|
+
data (dict): the data to be rendered
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
service_name = path.basename(folder_path)
|
|
77
|
+
try:
|
|
78
|
+
mkdir(folder_path)
|
|
79
|
+
except OSError as err:
|
|
80
|
+
Log.error(err)
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
dest_path = '{}/{}.{}'.format(
|
|
84
|
+
folder_path, service_name, extention)
|
|
85
|
+
|
|
86
|
+
render_boilerplate(boilerplate=boilerplate, data=data,
|
|
87
|
+
destination_path=dest_path)
|
|
88
|
+
|
|
89
|
+
Log.info(
|
|
90
|
+
'Plugin created at {}'.format(dest_path))
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def show_schema(base_path):
|
|
94
|
+
""" this method will show the schema of a plugin if it has schema.json
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
base_path (str): the base path of the plugin eg: /home/vtx-services/aaa_com/
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
schema_path = base_path + '/schema.json'
|
|
101
|
+
if path.exists(schema_path):
|
|
102
|
+
try:
|
|
103
|
+
with open(schema_path, 'r') as f:
|
|
104
|
+
schema = f.read()
|
|
105
|
+
schema = json.loads(schema)
|
|
106
|
+
for page in schema.keys():
|
|
107
|
+
Log.standout(f"Schema for Page: {page}")
|
|
108
|
+
schema_heading = ['field', 'type', 'pattern']
|
|
109
|
+
table_data = [
|
|
110
|
+
schema_heading
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
for th, td in schema[page]['schema']['properties'].items():
|
|
114
|
+
if 'pattern' in td:
|
|
115
|
+
row = [th, td['type'], td['pattern']]
|
|
116
|
+
else:
|
|
117
|
+
row = [th, td['type'], '']
|
|
118
|
+
|
|
119
|
+
table_data.append(row)
|
|
120
|
+
|
|
121
|
+
print(SingleTable(table_data).table)
|
|
122
|
+
return True
|
|
123
|
+
except:
|
|
124
|
+
Log.warn("Schema Structured Incorrectly")
|
|
125
|
+
else:
|
|
126
|
+
Log.warn("Schema Not Found")
|
|
127
|
+
return False
|
|
128
|
+
|
|
129
|
+
COMMENT_BLOCK_PATTERN = re.compile(r"(\/\*\*.?\n)(.*?)(\s*\*\/\s*\n)", re.DOTALL)
|
|
130
|
+
CLASS_DECLARATION_PATTERN = re.compile(r'class\s+\w+\s+extends\s(\w+)\b', re.DOTALL)
|
|
131
|
+
|
|
132
|
+
def user_input(msg, default='Y'):
|
|
133
|
+
while True:
|
|
134
|
+
choice = input(msg + ' [Y/n]: ' if default == 'Y' else ' [y/N]: ').upper()
|
|
135
|
+
if choice == 'N':
|
|
136
|
+
return 'N'
|
|
137
|
+
elif choice == 'Y':
|
|
138
|
+
return 'Y'
|
|
139
|
+
elif choice == '':
|
|
140
|
+
return default
|
|
141
|
+
|
|
142
|
+
def list_dependencies(plugin_name):
|
|
143
|
+
"""list dependencies by listing all base_class"""
|
|
144
|
+
|
|
145
|
+
dependencies = set()
|
|
146
|
+
while True:
|
|
147
|
+
plugin_dir_path = get_plugin_path(plugin_name)
|
|
148
|
+
if not plugin_dir_path:
|
|
149
|
+
break
|
|
150
|
+
plugin_path = path.join(plugin_dir_path, plugin_name + '.php')
|
|
151
|
+
contents = read_text(plugin_path)
|
|
152
|
+
match = CLASS_DECLARATION_PATTERN.search(contents)
|
|
153
|
+
if not match:
|
|
154
|
+
break
|
|
155
|
+
base_class = match.group(1)
|
|
156
|
+
if base_class == 'Vtx_Service_Plugin':
|
|
157
|
+
break
|
|
158
|
+
plugin_name = base_class
|
|
159
|
+
dependencies.add(plugin_name)
|
|
160
|
+
return list(dependencies)
|
|
161
|
+
|
|
162
|
+
def insert_all_chained_dependencies(plugin_name):
|
|
163
|
+
"""add dependencies if the dependencies follow service_code/service_code.php pattern, however deep"""
|
|
164
|
+
|
|
165
|
+
dependencies = list_dependencies(plugin_name)
|
|
166
|
+
plugin_dir_path = get_plugin_path(plugin_name)
|
|
167
|
+
if not plugin_dir_path:
|
|
168
|
+
return
|
|
169
|
+
plugin_path = path.join(plugin_dir_path, plugin_name + '.php')
|
|
170
|
+
contents = read_text(plugin_path)
|
|
171
|
+
mappings = get_comment_block(contents)
|
|
172
|
+
if 'Dependencies' not in mappings:
|
|
173
|
+
mappings['Dependencies'] = ''
|
|
174
|
+
original_deps = []
|
|
175
|
+
else:
|
|
176
|
+
original_deps = mappings['Dependencies'].split(',')
|
|
177
|
+
|
|
178
|
+
mappings['Dependencies'] = ','.join(list(set(dependencies + original_deps)))
|
|
179
|
+
contents = set_comment_block(contents, mappings)
|
|
180
|
+
write_text(plugin_path, contents)
|
|
181
|
+
return True
|
|
182
|
+
|
|
183
|
+
def set_comment_block(script, mappings):
|
|
184
|
+
comment_block = ' * ' + '\n * '.join([f'{k}: {v}' for k, v in mappings.items()])
|
|
185
|
+
subbed = COMMENT_BLOCK_PATTERN.sub('\\1' + comment_block + '\\3', script)
|
|
186
|
+
return subbed
|
|
187
|
+
|
|
188
|
+
def get_comment_block(script):
|
|
189
|
+
match = COMMENT_BLOCK_PATTERN.search(script)
|
|
190
|
+
if not match:
|
|
191
|
+
return {}
|
|
192
|
+
doc = match.group(2)
|
|
193
|
+
lines = doc.splitlines()
|
|
194
|
+
mappings = {}
|
|
195
|
+
for line in lines:
|
|
196
|
+
splitted = line.lstrip(' *').split(':', 1)
|
|
197
|
+
if len(splitted) != 2:
|
|
198
|
+
continue
|
|
199
|
+
k, v = splitted
|
|
200
|
+
mappings[k.strip()] = v.strip()
|
|
201
|
+
return mappings
|
|
202
|
+
|
|
203
|
+
def get_plugin_info(plugin_path):
|
|
204
|
+
""" get plugin's info like service_name,pid,description from the plugin's base folder.
|
|
205
|
+
It does so by reading the file and looking at the info from plugin which is commented
|
|
206
|
+
at the beginning.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
plugin_path (str): the path of the directory where we find the plugin.
|
|
210
|
+
"""
|
|
211
|
+
plugin_file_path = listdir(plugin_path)
|
|
212
|
+
|
|
213
|
+
files = [f for f in plugin_file_path]
|
|
214
|
+
plugin_file = ''
|
|
215
|
+
for file in files:
|
|
216
|
+
if path.basename(plugin_path) in file:
|
|
217
|
+
plugin_file = file
|
|
218
|
+
break
|
|
219
|
+
|
|
220
|
+
if plugin_file:
|
|
221
|
+
try:
|
|
222
|
+
with open(path.join(plugin_path, plugin_file)) as f:
|
|
223
|
+
script = f.read()
|
|
224
|
+
match = CLASS_DECLARATION_PATTERN.search(script)
|
|
225
|
+
base_class = None
|
|
226
|
+
if match:
|
|
227
|
+
base_class = match[1]
|
|
228
|
+
|
|
229
|
+
# TODO: use tree-sitter to parse php file.
|
|
230
|
+
mappings = {k.upper(): v for k, v in get_comment_block(script).items()}
|
|
231
|
+
if not mappings:
|
|
232
|
+
return {}
|
|
233
|
+
dependencies_str = mappings.get('DEPENDENCIES', '')
|
|
234
|
+
dependencies = [d.strip() for d in dependencies_str.split(',')]
|
|
235
|
+
pid_line = mappings.get('PID', '')
|
|
236
|
+
ssv = pid_line.split(' ')
|
|
237
|
+
pid = ssv[0].strip()
|
|
238
|
+
|
|
239
|
+
pid_forced = False
|
|
240
|
+
if len(ssv) > 1:
|
|
241
|
+
pid_forced = ssv[1].strip() == 'force'
|
|
242
|
+
|
|
243
|
+
# TODO: to parse php import mechanisms like require_once, include_once, etc. for dependencies
|
|
244
|
+
return {
|
|
245
|
+
'pid': pid,
|
|
246
|
+
'pid_forced': pid_forced,
|
|
247
|
+
'report_name': mappings.get('NAME'),
|
|
248
|
+
'description': mappings.get('DESCRIPTION'),
|
|
249
|
+
'dependencies': dependencies,
|
|
250
|
+
'base_class': base_class
|
|
251
|
+
}
|
|
252
|
+
except KeyError as e:
|
|
253
|
+
pass
|
|
254
|
+
return {}
|
|
255
|
+
|
|
256
|
+
def render_boilerplate(boilerplate, data, destination_path):
|
|
257
|
+
"""parse boilerplate from template directory to start a project
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
boilerplate (str): name of boilerplate template
|
|
261
|
+
data (dict): input for the boilerplate
|
|
262
|
+
destination_path: the final path where the final content needs to be saved
|
|
263
|
+
"""
|
|
264
|
+
|
|
265
|
+
template_dir = Path(__file__).parent.parent.absolute()
|
|
266
|
+
template_file = '{}/templates/{}'.format(template_dir, boilerplate)
|
|
267
|
+
with open(template_file) as file:
|
|
268
|
+
template = Template(file.read())
|
|
269
|
+
with open(destination_path, 'w') as dest_file:
|
|
270
|
+
dest_file.write(template.render(data))
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _generate_plugin_path(plugin_name, paths):
|
|
274
|
+
|
|
275
|
+
for service_path in paths:
|
|
276
|
+
plugin_path = path.join(service_path, plugin_name)
|
|
277
|
+
plugin_path = path.expanduser(plugin_path)
|
|
278
|
+
if path.exists(plugin_path):
|
|
279
|
+
return plugin_path
|
|
280
|
+
return False
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def get_docker_entrypoints(image_name):
|
|
284
|
+
o = subprocess.run(f'docker inspect {image_name}', shell=True, capture_output=True, text=True)
|
|
285
|
+
try:
|
|
286
|
+
image_info = json.loads(o.stdout)
|
|
287
|
+
except json.JSONDecodeError:
|
|
288
|
+
raise Exception('Running docker inspect failed. Is SDK image correct?')
|
|
289
|
+
try:
|
|
290
|
+
return image_info[0]['Config']['Entrypoint']
|
|
291
|
+
except IndexError:
|
|
292
|
+
raise Exception('Docker inspect failed. Is Docker Running? Is SDK image available?')
|
|
293
|
+
|
|
294
|
+
def write_text(file_path, contents):
|
|
295
|
+
"helper function to write text to file with compatible encoding and no newline magic"
|
|
296
|
+
|
|
297
|
+
# turn off universal-newline mode for less surprises.
|
|
298
|
+
with open(file_path, 'w', encoding='UTF-8', newline='') as fd:
|
|
299
|
+
fd.write(contents)
|
|
300
|
+
|
|
301
|
+
def read_text(file_path):
|
|
302
|
+
"helper function to read text from file with compatible encoding and no newline magic"
|
|
303
|
+
|
|
304
|
+
# turn off universal-newline mode for less surprises.
|
|
305
|
+
with open(file_path, 'r', encoding='UTF-8', newline='') as fd:
|
|
306
|
+
contents = fd.read()
|
|
307
|
+
|
|
308
|
+
return contents
|
|
File without changes
|
grepsrcli/main.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from cement import App, TestApp, init_defaults
|
|
2
|
+
from cement.core.exc import CaughtSignal
|
|
3
|
+
from .core.exc import GrepsrCliError
|
|
4
|
+
from .controllers.base import Base
|
|
5
|
+
from .controllers.crawler import Crawler, CrawlerBase
|
|
6
|
+
from .controllers.report import Report, ReportBase
|
|
7
|
+
from .controllers.generate import Generate, GenerateBase
|
|
8
|
+
|
|
9
|
+
# configuration defaults
|
|
10
|
+
CONFIG = init_defaults('grepsrcli')
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class GrepsrCli(App):
|
|
14
|
+
"""Grepsr Cli primary application."""
|
|
15
|
+
|
|
16
|
+
class Meta:
|
|
17
|
+
label = 'gcli'
|
|
18
|
+
|
|
19
|
+
# configuration defaults
|
|
20
|
+
config_defaults = CONFIG
|
|
21
|
+
|
|
22
|
+
# call sys.exit() on close
|
|
23
|
+
exit_on_close = True
|
|
24
|
+
|
|
25
|
+
# load additional framework extensions
|
|
26
|
+
extensions = [
|
|
27
|
+
'yaml',
|
|
28
|
+
'colorlog',
|
|
29
|
+
'jinja2',
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
# configuration handler
|
|
33
|
+
config_handler = 'yaml'
|
|
34
|
+
|
|
35
|
+
# configuration file suffix
|
|
36
|
+
config_file_suffix = '.yml'
|
|
37
|
+
|
|
38
|
+
# set the log handler
|
|
39
|
+
log_handler = 'colorlog'
|
|
40
|
+
|
|
41
|
+
# set the output handler
|
|
42
|
+
output_handler = 'jinja2'
|
|
43
|
+
|
|
44
|
+
# register handlers
|
|
45
|
+
handlers = [
|
|
46
|
+
Base,
|
|
47
|
+
CrawlerBase,
|
|
48
|
+
Crawler,
|
|
49
|
+
ReportBase,
|
|
50
|
+
Report,
|
|
51
|
+
GenerateBase,
|
|
52
|
+
Generate
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class GrepsrCliTest(TestApp, GrepsrCli):
|
|
57
|
+
"""A sub-class of GrepsrCli that is better suited for testing."""
|
|
58
|
+
|
|
59
|
+
class Meta:
|
|
60
|
+
label = 'grepsrcli'
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def main():
|
|
64
|
+
with GrepsrCli() as app:
|
|
65
|
+
try:
|
|
66
|
+
app.run()
|
|
67
|
+
|
|
68
|
+
except AssertionError as e:
|
|
69
|
+
print('AssertionError > %s' % e.args[0])
|
|
70
|
+
app.exit_code = 1
|
|
71
|
+
|
|
72
|
+
if app.debug is True:
|
|
73
|
+
import traceback
|
|
74
|
+
traceback.print_exc()
|
|
75
|
+
|
|
76
|
+
except GrepsrCliError as e:
|
|
77
|
+
print('GrepsrCliError > %s' % e.args[0])
|
|
78
|
+
app.exit_code = 1
|
|
79
|
+
|
|
80
|
+
if app.debug is True:
|
|
81
|
+
import traceback
|
|
82
|
+
traceback.print_exc()
|
|
83
|
+
|
|
84
|
+
except CaughtSignal as e:
|
|
85
|
+
# Default Cement signals are SIGINT and SIGTERM, exit 0 (non-error)
|
|
86
|
+
print('\n%s' % e, flush=True)
|
|
87
|
+
app.exit_code = 0
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
if __name__ == '__main__':
|
|
91
|
+
main()
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
{% for p in paths %}
|
|
3
|
+
f{{p.ind}}=$(find {{p.dest_path}} -mindepth 1 -type d -printf "%f ")
|
|
4
|
+
{% endfor %}
|
|
5
|
+
|
|
6
|
+
_gcliComplete()
|
|
7
|
+
{
|
|
8
|
+
local cur=${COMP_WORDS[COMP_CWORD]}
|
|
9
|
+
COMPREPLY=( $(compgen -W "{% for p in paths %} $f{{p.ind}} {% endfor %}" -- $cur) )
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
complete -F _gcliComplete gcli
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/bin/zsh
|
|
2
|
+
|
|
3
|
+
# install findutils if not already installed
|
|
4
|
+
if brew ls --versions findutils > /dev/null; then echo "installed" > /dev/null; else brew install findutils; fi
|
|
5
|
+
|
|
6
|
+
# load the modules for bash autompletion
|
|
7
|
+
autoload -U +X compinit && compinit
|
|
8
|
+
autoload -U +X bashcompinit && bashcompinit
|
|
9
|
+
|
|
10
|
+
{% for p in paths %}
|
|
11
|
+
f{{p.ind}}=$(gfind {{p.dest_path}} -mindepth 1 -type d -printf "%f ")
|
|
12
|
+
{% endfor %}
|
|
13
|
+
|
|
14
|
+
_gcliComplete()
|
|
15
|
+
{
|
|
16
|
+
local cur=${COMP_WORDS[COMP_CWORD]}
|
|
17
|
+
COMPREPLY=( $(compgen -W "{% for p in paths %} $f{{p.ind}} {% endfor %}" -- $cur) )
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
complete -F _gcliComplete gcli
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"repositories": [{
|
|
3
|
+
"type": "package",
|
|
4
|
+
"package": {
|
|
5
|
+
"name": "{{plugin_name}}/{{plugin_name}}",
|
|
6
|
+
"version": "{{version}}",
|
|
7
|
+
"dist": {
|
|
8
|
+
"url": "{{presigned_url}}",
|
|
9
|
+
"type": "tar"
|
|
10
|
+
},
|
|
11
|
+
"autoload": {
|
|
12
|
+
"classmap": [
|
|
13
|
+
"/"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}],
|
|
18
|
+
"require": {
|
|
19
|
+
"{{plugin_name}}/{{plugin_name}}": "{{version}}"
|
|
20
|
+
}
|
|
21
|
+
}
|