-
Notifications
You must be signed in to change notification settings - Fork 0
/
cli.py
executable file
·276 lines (241 loc) · 10.1 KB
/
cli.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
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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
#!/usr/bin/env python
import click
import os
import data_structs as structs
from Parsers.Marc_XML.Marc_XML_Parser import Marc_XML_Parser
from funcs import func_get, func_set
from rename_files import rename_files
from config import Config
actions = ['get', 'rename']
plugin_folder = os.path.join(os.path.dirname(__file__), 'commands')
#====================================================#
# Purpose: To lazily loads the "plugins" from the #
# commands folder for Click use (ex. #
# rename, etc...) #
# Properties: None #
# Methods: list_commands - List the available #
# "plugins" #
# get_command - Get the desired "plugin" #
# Superclass: Click.MultiCommand #
#====================================================#
class MyCLI(click.MultiCommand):
#----------------------------------------------------#
# Purpose: To list the available "plugins" #
# Parameters: self (implicit) - The instance of the #
# object the function #
# is invoked on #
# ctx - The Click context thats being #
# used #
# Return: A (sorted) list of the available "plugins" #
#----------------------------------------------------#
def list_commands(self, ctx):
# Initialize the return list
rv = []
# Loop over the files in the plugin directory
for filename in os.listdir(plugin_folder):
# Ensure the file is a Python file
if filename.endswith('.py'):
# Add the file to the list
rv.append(filename[:-3])
# Sort the list
rv.sort()
# Return the list
return rv
#----------------------------------------------------#
# Purpose: Get the command for the given "plugin" #
# Parameters: self (implicit) - The instance of the #
# object the function #
# is invoked on #
# ctx - The Click context thats being #
# used #
# name - The name of the "plugin" to get #
# the command from #
# Return: function - The function to call to run the #
# command #
#----------------------------------------------------#
def get_command(self, ctx, name):
# Initialize the return dictionary
ns = {}
# Add the name of the command/"plugin" the user wants to use to
# the end of the filepath for the "plugin"/commands folder
fn = os.path.join(plugin_folder, name + '.py')
# Open the file
with open(fn) as f:
# Compile and evaluate the file
code = compile(f.read(), fn, 'exec')
eval(code, ns, ns)
# Return a pointer to the newly loaded function
return ns['cli']
#----------------------------------------------------#
# Purpose: Provide the CLI for the generating of the #
# dictiomary #
# Parameters: N/A #
# Return: N/A #
#----------------------------------------------------#
def generate_dictionary_cli(dictionary_format):
# Setup the callback variables
parser_type = {'Marc XML':Marc_XML_Parser}
options = []
callbacks = {}
for k,v in parser_type.iteritems():
options.append(k)
callbacks[k] = v
# Prompt the use to see what kind of Metadata file
# they'd like to parse to generate the dictionary
# After some development this might include types like
# Marc, Marc XML, ONIX, etc...
type = click.prompt('What type of metadata to parse', type=click.Choice(options))
# Now prompt the user what they want the dictionary file
# to be called this is partly in case they ever want to
# reuse that dictionary file
dictionary = click.prompt('Where (filename) should the file be stored?')
# Initialize the proper parser and parse_to_file
parser = callbacks[type]()
output = parser.parse_to_file(dictionary, format=dictionary_format)
# For the returned output lets parse it and display it
# to the user so that they have som e idea of what is
# happening
#for parsed_file, parsed_values in output.iteritems():
# for record in parsed_values:
# if isinstance(record, dict):
# for k,v in record.iteritems():
# click.echo('Parsed the value ' + unicode(v) + ' for the records ' + k + ' from ' + parsed_file)
# else:
# print str(record) + ' isn\'t a dictionary'
return dictionary
#----------------------------------------------------#
# Purpose:
# Parameters:
# Return:
#----------------------------------------------------#
def get_records_from_dictionary(dictionary):
# Check if the dictionary was defined
if dictionary is None:
# Ask if the user wants to generate a dictionary
generate_dictionary = click.confirm('Do you neeed to generate a dictionary?')
format_options = ['json', 'tabs', 'csv']
dictionary_format = click.prompt('What format to store the dictionary in?', type=click.Choice(format_options))
# Check what response was given
if generate_dictionary:
dictionary = generate_dictionary_cli(dictionary_format)
else:
# Because the user didn't specify a dictionary at
# runtime and has said no to generating one we need to
# ask what file they'd like to use given that we do
# NEED one
dictionary = click.prompt('Filename of the dictionary file to use?')
# Acknowledge the user input so that they know whats
# happening
click.echo('Alright. Using %s as the dictionary' % dictionary)
else:
if dictionary == 'Marc_XML':
parser = Marc_XML_Parser()
output = parser.parse()
dictionary_format = None
# Load in the dictionary (wheither just generated or not)
if not '.' in dictionary:
if dictionary_format == 'json':
dictionary += '.json'
elif dictionary_format == 'tabs':
dictionary += '.txt'
elif dictionary_format == 'csv':
dictionary += '.csv'
with open(dictionary) as f:
if dictionary_format is None:
if dictionary.endswith('.json'):
dictionary_format = 'json'
elif dictionary.endswith('.txt'):
dictionary_format = 'tabs'
elif dictionary.endswith('.csv'):
dictionary_format = 'csv'
if dictionary_format == 'json':
result = structs.json.load(f)
records = structs.records_from_json(result)
elif dictionary_format == 'tabs':
header_line = f.readline()
lines = []
curr_line = f.readline().decode('utf-8')
while curr_line != '':
lines.append(curr_line)
curr_line = f.readline().decode('utf-8')
records = structs.records_from_tab_seperated(header_line, lines)
elif dictionary_format == 'csv':
header_line = f.readline()
lines = []
curr_line = f.readline().decode('utf-8')
while curr_line != '':
lines.append(curr_line)
curr_line = f.readline().decode('utf-8')
records = structs.records_from_csv(header_line, lines)
return records
#----------------------------------------------------#
# Purpose: Provide the CLI for the program including #
# triggers for applicable inputs #
# Parameters: dictionary - The filename of the #
# datastore/look up table #
# dictionary to use #
# action - The action to be taken #
# (automatically) by the script #
# output_level - The level of output to #
# provide to the user #
# Return: N/A #
#----------------------------------------------------#
@click.command(cls=MyCLI, invoke_without_command=True)
@click.option('--dictionary', '-d', default=None, help='The filename that you want to use as the datastore dictionary/look up table')
@click.option('--action', '-a', default=None, type=click.Choice(actions), help='The action you want the script to perform')
@click.option('--verbose', '-v', 'output_level', flag_value='verbose', default=True, help='Provide output to the user')
@click.option('--quiet', '-q', 'output_level', flag_value='quiet', help='Provide minimal output to the user')
@click.pass_context
def main(ctx, dictionary, action, output_level):
ctx.obj = {}
ctx.obj['configs'] = Config()
records = get_records_from_dictionary(dictionary)
#for record in records:
# print unicode(record)
# Check if a (sub)command was specified
if ctx.invoked_subcommand is None:
actions = ctx.command.list_commands(ctx)
if action is None:
# Prompt the user to see what they want to do
action = click.prompt('What would you like to do?', type=click.Choice(actions))
# Check if the action choosen needs to be acompanied by some
# other arguments
if action in actions:
# Check if the function being called is spcifically
# rename
if action == 'rename':
# Set the records extra parameter to supply for
# rename callback
ctx.obj['records'] = records
patterns = []
use_pattern = True
while use_pattern:
# Ask if the user would like to use a pattern
# for the rename function
use_pattern = click.confirm('Would you like to specify a file pattern?')
# If they do want to
if use_pattern:
patterns.append(click.prompt('What file pattern(s) would you like to use?'))
# Check if the list is empty
if patterns:
# If the list isn't empty then we need
# to assign it to the context objec
# don't bother otherwise
ctx.obj['patterns'] = patterns
# Invoke the desired callback with the additional
# arguments
ctx.invoke(ctx.command.get_command(ctx, action))
else:
# Invoke the desired callback
ctx.invoke(ctx.command.get_command(ctx, action))
else:
if ctx.invoked_subcommand == 'rename':
ctx.obj['records'] = records
#----------------------------------------------------#
# Purpose: To provide the entry point for the CLI #
# Parameters (Command Line) - See main (Click) #
# function for details #
# Return: N/A #
#----------------------------------------------------#
if __name__ == '__main__':
main()