forked from ctrl_packages/epics_launcher
326 lines
11 KiB
Python
Executable File
326 lines
11 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
import sys
|
|
import os
|
|
import re
|
|
import json
|
|
import codecs
|
|
import argparse
|
|
import pyparsing
|
|
|
|
|
|
class LauncherMenuModel(object):
|
|
""" TODO: Docs
|
|
"""
|
|
|
|
# Regular expression to split the tcl configuration
|
|
# lines into individual parameters
|
|
#regex_split = re.compile('{([^\}]+)}[ \t]*')
|
|
expr_split = pyparsing.nestedExpr('{', '}')
|
|
regex_type = re.compile('([^ ]+)[ \t]*')
|
|
|
|
# Translation table for character replacement
|
|
translate_table = dict((ord(char), u'') for char in u'\\\n')
|
|
|
|
def __init__(self, dir_path, file_path):
|
|
|
|
self._dir_path = dir_path
|
|
self._file_path = file_path
|
|
self._path = os.path.join(self._dir_path, self._file_path)
|
|
|
|
self._title = None
|
|
self._file_choice = None
|
|
|
|
self._menu_items = list()
|
|
self._json_config = dict()
|
|
self._file_list = list()
|
|
|
|
self._line_number = 0
|
|
self._parse()
|
|
|
|
def _parse(self):
|
|
with codecs.open(self._path, encoding='ISO-8859-1') as tickle_file:
|
|
parse_line = ''
|
|
|
|
for line in tickle_file:
|
|
line = line.lstrip()
|
|
line = line.rstrip('\n')
|
|
|
|
# Track the current line number for logging output
|
|
self._line_number += 1
|
|
|
|
# Skip over empty lines
|
|
if not line:
|
|
continue
|
|
|
|
# Tickle has the option of multi-line-split
|
|
# configuration lines using \ character
|
|
if '\\' in line:
|
|
# Remove '\' character and newlines
|
|
line = line.translate(LauncherMenuModel.translate_table)
|
|
parse_line += line
|
|
else:
|
|
parse_line += line
|
|
# Skip over comment lines
|
|
if line[0] != '#':
|
|
self._parse_line(parse_line)
|
|
parse_line = ''
|
|
|
|
def _concatenate(self, item_list, level=0):
|
|
new_item_list = list()
|
|
for item in item_list:
|
|
if isinstance(item, list):
|
|
new_item_list.append(self._concatenate(item, level+1))
|
|
else:
|
|
new_item_list.append(item)
|
|
|
|
if level > 0:
|
|
return '{' + ' '.join(new_item_list) + '}'
|
|
else:
|
|
return ' '.join(new_item_list)
|
|
|
|
def _parse_line(self, line):
|
|
|
|
line = '{' + line + '}'
|
|
items = LauncherMenuModel.expr_split.parseString(line).asList()[0]
|
|
|
|
command = items[0]
|
|
items.pop(0)
|
|
|
|
params = list()
|
|
for item in items:
|
|
if isinstance(item, list):
|
|
params.append(self._concatenate(item))
|
|
|
|
# print command
|
|
# print params
|
|
# print '-----------------------'
|
|
# return
|
|
|
|
# Remove empty strings in the parameter list
|
|
# for element in params:
|
|
# if not element:
|
|
# params.remove(element)
|
|
|
|
# command = re.split(LauncherMenuModel.regex_type, params[0], 2)
|
|
|
|
# Remove empty strings in the parameter list
|
|
# for element in command:
|
|
# if not element:
|
|
# command.remove(element)
|
|
|
|
element = dict()
|
|
|
|
# Remove first parameter since it is parsed
|
|
# and stored in the command variable
|
|
#params.remove(params[0])
|
|
|
|
# if len(params) == 0:
|
|
# print 'Wrn: No parameters passed in file "%s", line %d' \
|
|
# % (self._file_path, self._line_number)
|
|
|
|
# Configure the title of the main menu
|
|
if command[0] == '@main-title':
|
|
self._json_config['menu-title'] = params[0]
|
|
|
|
if len(params) > 0:
|
|
print ('Inf: Skipping additional parameters in '
|
|
'file "%s", line line %d') \
|
|
% (self._file_path, self._line_number)
|
|
|
|
# Add the file choice element to the configuration list
|
|
elif command[0] == '@FileChoice':
|
|
file_choice = list()
|
|
file_choice.append(dict([('text', params[0]),
|
|
('file', command[1]+'.json')]))
|
|
self._json_config['file-choice'] = file_choice
|
|
|
|
if len(params) > 1:
|
|
print ('Inf: Skipping additional parameters in '
|
|
'file "%s", line line %d') \
|
|
% (self._file_path, self._line_number)
|
|
|
|
# The command dictates that a separator is added
|
|
elif command[0] == '@separator':
|
|
element['type'] = 'separator'
|
|
|
|
if len(params) > 0:
|
|
print ('Inf: Skipping additional parameters in '
|
|
'file "%s", line line %d') \
|
|
% (self._file_path, self._line_number)
|
|
|
|
# The commands translates into the title element
|
|
elif command[0] == '@title':
|
|
element['type'] = 'title'
|
|
element['text'] = params[0]
|
|
|
|
if len(params) > 1:
|
|
print ('Inf: Skipping additional parameters in '
|
|
'file "%s", line line %d') \
|
|
% (self._file_path, self._line_number)
|
|
|
|
# The command loads a new menu from another file
|
|
elif command[0] == '>launcher':
|
|
element['type'] = 'menu'
|
|
element['text'] = params[0]
|
|
element['file'] = command[1] + '.json'
|
|
|
|
if len(params) > 1:
|
|
print ('Inf: Skipping additional parameters in '
|
|
'file "%s", line line %d') \
|
|
% (self._file_path, self._line_number)
|
|
|
|
# Track all additional files that need to be parsed
|
|
self._file_list.append(command[1] + '.config')
|
|
|
|
# Skip over lines where the command starts with a hash (comment)
|
|
elif command[0] == '#':
|
|
print 'Inf: Skipping line %d in file "%s" - comment' \
|
|
% (self._line_number, self._file_path)
|
|
|
|
# If nothing else this is a command
|
|
else:
|
|
cmd_text = self._concatenate(command)
|
|
# Replace tabulators with spaces
|
|
cmd_text = cmd_text.replace('\t', ' ')
|
|
|
|
element['type'] = 'cmd'
|
|
element['text'] = params[0].replace('"', r'\"')
|
|
element['param'] = cmd_text
|
|
|
|
# Add the element dictionary to the list of menu items
|
|
if len(element) > 0:
|
|
self._menu_items.append(element)
|
|
|
|
def to_json(self, out_path, overwrite=False):
|
|
split = os.path.splitext(self._file_path)
|
|
if not split[1]:
|
|
print 'Err: Unable to parse extension from file name: %s' \
|
|
% self._file_path
|
|
return
|
|
|
|
out_file = os.path.join(out_path, split[0] + '.json')
|
|
print 'Inf: Writing file: %s' % out_file
|
|
|
|
if os.path.isdir(out_file):
|
|
print 'Err: Output file "%s" is a directory!' % out_file
|
|
return
|
|
|
|
if os.path.isfile(out_file):
|
|
if not overwrite:
|
|
print 'Wrn: Output file "%s" already exists!' \
|
|
% out_file
|
|
|
|
user_input = ''
|
|
while True:
|
|
user_input = raw_input('Overwrite? [y/N]:')
|
|
if user_input == 'y' or user_input == 'Y':
|
|
break
|
|
elif (user_input == 'n' or
|
|
user_input == 'N' or
|
|
not user_input):
|
|
return
|
|
|
|
# Set the item list to the menu key in the top dictionary
|
|
self._json_config['menu'] = self._menu_items
|
|
|
|
with codecs.open(out_file, mode='w', encoding='utf-8') \
|
|
as output_file:
|
|
|
|
json.dump(self._json_config, output_file, indent=4)
|
|
output_file.close()
|
|
|
|
def get_file_list(self):
|
|
return self._file_list
|
|
|
|
|
|
class LauncherMenuModelParser(object):
|
|
|
|
def __init__(self, input_file, output_path, overwrite=False):
|
|
|
|
# Split path into filename and directory path
|
|
input_file_split = os.path.split(input_file)
|
|
|
|
self._input_file_path = input_file_split[0]
|
|
self._input_file_name = input_file_split[1]
|
|
self._output_path = output_path
|
|
self._overwrite = overwrite
|
|
|
|
self._input_files = dict()
|
|
|
|
self._input_files[self._input_file_name] = 'No'
|
|
|
|
# Parse requested files
|
|
def parse(self, single=False):
|
|
|
|
finished = False
|
|
|
|
while not finished:
|
|
|
|
input_name = None
|
|
|
|
# Check if we parsed all files and get the next
|
|
# in line to be parsed
|
|
for key, value in self._input_files.iteritems():
|
|
if value == 'No':
|
|
input_name = key
|
|
break
|
|
|
|
# If we parsed all of them, stop
|
|
if not input_name:
|
|
finished = True
|
|
continue
|
|
|
|
# Parse the current file
|
|
menu_model = LauncherMenuModel(self._input_file_path,
|
|
input_name)
|
|
|
|
# Output the configuration to the json output file
|
|
menu_model.to_json(self._output_path, self._overwrite)
|
|
|
|
# If we do not want to parse any additional files, stop
|
|
if(single):
|
|
break
|
|
|
|
# Tag the current file as checked
|
|
self._input_files[input_name] = 'Yes'
|
|
|
|
# Get list of all depending files that have been
|
|
# detected during parsing of the configuration
|
|
file_list = menu_model.get_file_list()
|
|
|
|
# Add any files not yet present in the dictionary to it
|
|
for input_file in file_list:
|
|
if input_file not in self._input_files.keys():
|
|
self._input_files[input_file] = 'No'
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
args_pars = argparse.ArgumentParser()
|
|
args_pars.add_argument('inputfile',
|
|
help='Tickle configuration script to be converted.')
|
|
args_pars.add_argument('outputfolder',
|
|
help='Folder where the converted json file \
|
|
will be stored.')
|
|
args_pars.add_argument('-y', '--yes', action='store_true',
|
|
help='Overwrite the output file.')
|
|
args_pars.add_argument('-s', '--single', action='store_true',
|
|
help='Convert only a single file.')
|
|
|
|
args = args_pars.parse_args()
|
|
|
|
tickle_path = os.path.normpath(args.inputfile)
|
|
output_path = os.path.normpath(args.outputfolder)
|
|
|
|
if not os.path.isfile(tickle_path):
|
|
print 'Tickle path "' + tickle_path + '" is not a regular file!'
|
|
sys.exit(-1)
|
|
|
|
if not os.path.isdir(output_path):
|
|
print 'Output path "' + output_path + '" is not a directory!'
|
|
sys.exit(-1)
|
|
|
|
parser = LauncherMenuModelParser(tickle_path, output_path, args.yes)
|
|
parser.parse(args.single)
|