mirror of
https://github.com/slsdetectorgroup/slsDetectorPackage.git
synced 2025-06-23 10:07:59 +02:00
commands code generation (#871)
* commands code generation (#803) * commands code generation for only frames command * fix cmake file and add Caller files * working exptime, fully extended commands file and its variants * start adding template commands * add INT_CMD_VEC_ID template * add list command, generate multiple bins, format code * reach 208 commands using the cpp macros * add tests for command parser * start adding tests for commands parser * fix typo to use commands.yaml * add more tests for command_parser * add all template functions (up to 218 commands) * finish template functions and add more CmdProxy.cpp functions (250+) * 257 commands * 300 commands the rest are very special commands * add special commands without generation * separate special functions from generated c++ file * implementing one command for put and get (buggy) * add infer action in a separate file * generate header for special commands from yaml * allow only 0 or 1 for bool inputs * group all commands in gen_commands.py * add help to gen_commands.py * add autocomplete bash script * autocompletion: add support for module levels and help * remove debugging line * add autocompletion, help to commands, change int [0,1] to bool * copy tests for Caller.cpp. Tests pass * update with the new developer branch changes * fix errors after merging (there is problems with tests) * fixed port/stopport in yaml (intput typo), added '_caller' to the test dac and test on chip dac command in global test for cmdcaller * undo previous test simulator debug change * add documentation for the generated code * reducing the comment to be replaced in length so formatting does not split into 2 lines * removed formatting specific style of C++11 in gen_commands.py to keep with the top level clang format of the project * regeneratign code for commands * automation generated * Redirect deprecated commands (#872) * working implementation, need to fix dac * fixed deprecation redirect for dac command * Detector specific autocomplete (#873) * working implementation, need to fix dac * fixed deprecation redirect for dac command * detector specific completion for dac * added autocomplete using detector specific * fixed error when autocompleting partial words * Generate commands/fix commands (#875) * fix vm_a, im_a etc have deg Celsius suffix, also help missing or changed in some places * dac: require det id for all, arg0 to be printed at output, help for onchip dac and dac, onchipdac: spacing * getscan detid and blocking trigger help * udp_Dstlist det_id fixed, but rx_id invalid * cmdApp in line with cmdLineApp (missing version, receiver_id, not creating det object in help action * added set_command to differentiate between check_det_id and require_det_id (mixed up), args: -1 needs to check for at least one argument * reordering * reordering and checked till integer_command_hex * fixed a lot more commands * fix caller tests for eiger * changes to tests after Bechir left * changing .cmd to .cmdcall for the caller commands * fixed tests for caller, still warning for setexptime about cast input * autocomplete ran * add moench test * regenerating autocomplete and commands * fixing other things from merge conflicts (renaming slsDetectorDefs to defs in commands.yaml) * formatting * added code injection to help (#876) * updated 3 commands to have get output that can be put into put (#877) * updated some commands to have get output that can be put into put * fix tests for clkdiv * adding help to free (#878) * removing old commands and renaming them, (also making it work for parameters command as it was still calling cmdproxy) (#879) * More helpful error messages for unsupported actions (#880) * removing old commands and renaming them, (also making it work for parameters command as it was still calling cmdproxy) * Added specific help for unsupported actions * fixed a vetofile get special exception message. more specific warning for special exception message instead of no function warning * added condition checking true in exceptions for special message --------- Co-authored-by: Bechir Brahem <bachbrahem@gmail.com> Co-authored-by: Erik Frojdh <erik.frojdh@gmail.com> Co-authored-by: Dhanya Thattil <dhanya.thattil@psi.ch>
This commit is contained in:
10
slsDetectorSoftware/generator/Caller.in.cpp
Normal file
10
slsDetectorSoftware/generator/Caller.in.cpp
Normal file
@ -0,0 +1,10 @@
|
||||
#include "Caller.h"
|
||||
#include "sls/logger.h"
|
||||
#include "sls/string_utils.h"
|
||||
#include <iostream>
|
||||
|
||||
namespace sls {
|
||||
|
||||
// THIS COMMENT TO BE REPLACED BY THE ACTUAL CODE
|
||||
|
||||
}
|
88
slsDetectorSoftware/generator/Caller.in.h
Normal file
88
slsDetectorSoftware/generator/Caller.in.h
Normal file
@ -0,0 +1,88 @@
|
||||
// This file is used as input to generate the caller class
|
||||
|
||||
#include "CmdParser.h"
|
||||
#include "HelpDacs.h"
|
||||
#include "sls/Detector.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
namespace sls {
|
||||
|
||||
class Caller {
|
||||
public:
|
||||
Caller(Detector *ptr) : det(ptr) {}
|
||||
void call(const std::string &command,
|
||||
const std::vector<std::string> &arguments, int detector_id,
|
||||
int action, std::ostream &os = std::cout, int receiver_id = -1);
|
||||
|
||||
IpAddr getDstIpFromAuto();
|
||||
IpAddr getSrcIpFromAuto();
|
||||
UdpDestination getUdpEntry();
|
||||
void GetLevelAndUpdateArgIndex(int action,
|
||||
std::string levelSeparatedCommand,
|
||||
int &level, int &iArg, size_t nGetArgs,
|
||||
size_t nPutArgs);
|
||||
void WrongNumberOfParameters(size_t expected);
|
||||
|
||||
template <typename V> std::string OutStringHex(const V &value) {
|
||||
if (value.equal())
|
||||
return ToStringHex(value.front());
|
||||
return ToStringHex(value);
|
||||
}
|
||||
|
||||
template <typename V> std::string OutStringHex(const V &value, int width) {
|
||||
if (value.equal())
|
||||
return ToStringHex(value.front(), width);
|
||||
return ToStringHex(value, width);
|
||||
}
|
||||
|
||||
template <typename V> std::string OutString(const Result<V> &value) {
|
||||
if (value.equal())
|
||||
return ToString(value.front());
|
||||
return ToString(value);
|
||||
}
|
||||
|
||||
template <typename V> std::string OutString(const V &value) {
|
||||
return ToString(value);
|
||||
}
|
||||
|
||||
template <typename V>
|
||||
std::string OutString(const V &value, const std::string &unit) {
|
||||
if (value.equal())
|
||||
return ToString(value.front(), unit);
|
||||
return ToString(value, unit);
|
||||
}
|
||||
|
||||
std::vector<std::string> getAllCommands();
|
||||
std::string list(int action);
|
||||
|
||||
// THIS COMMENT TO BE REPLACED BY THE ACTUAL CODE (1)
|
||||
|
||||
std::vector<std::string> args;
|
||||
std::string cmd;
|
||||
Detector *det;
|
||||
int det_id{-1};
|
||||
int rx_id{-1};
|
||||
|
||||
private:
|
||||
bool ReplaceIfDepreciated(std::string &command);
|
||||
using FunctionMap = std::map<std::string, std::string (Caller::*)(int)>;
|
||||
using StringMap = std::map<std::string, std::string>;
|
||||
Detector *ptr; // pointer to the detector that executes the command
|
||||
|
||||
FunctionMap functions{
|
||||
{"list", &Caller::list},
|
||||
|
||||
// THIS COMMENT TO BE REPLACED BY THE ACTUAL CODE (2)
|
||||
|
||||
};
|
||||
|
||||
StringMap depreciated_functions{
|
||||
|
||||
// THIS COMMENT TO BE REPLACED BY THE ACTUAL CODE (3)
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace sls
|
236
slsDetectorSoftware/generator/autocomplete/autocomplete.py
Normal file
236
slsDetectorSoftware/generator/autocomplete/autocomplete.py
Normal file
@ -0,0 +1,236 @@
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
# command to generate the ast
|
||||
# clang version: 14.0.0-1ubuntu1.1
|
||||
# clang++ -Xclang -ast-dump=json -Xclang -ast-dump-filter -Xclang StringTo -c ToString.cpp -I ../include/ -std=gnu++11
|
||||
#
|
||||
import yaml
|
||||
|
||||
AUTOCOMPLETE_PATH = Path(__file__).parent
|
||||
DUMP_PATH = AUTOCOMPLETE_PATH / 'dump.json'
|
||||
FIXED_PATH = AUTOCOMPLETE_PATH / 'fixed.json'
|
||||
|
||||
type_values = {
|
||||
'special::mv': ["mv", "mV"],
|
||||
"special::deg": ["deg"],
|
||||
"special::time_unit": ["s", "ms", "us", "ns"],
|
||||
"special::hard": ["hard"],
|
||||
"special::force-delete-normal-file": ["--force-delete-normal-file"],
|
||||
"special::currentSourceFix": ["fix", "nofix"],
|
||||
"special::currentSourceLow": ["normal", "low"],
|
||||
"special::path": [],
|
||||
"special::pedestal_parameters" : ["", "0"]
|
||||
|
||||
}
|
||||
|
||||
|
||||
def get_types(arg_types):
|
||||
ret = set()
|
||||
for arg_type in arg_types:
|
||||
if type_info(arg_type) == 'base':
|
||||
if arg_type == 'bool':
|
||||
ret = ret.union(["0", "1"])
|
||||
else:
|
||||
tmp = [not_list for not_list in type_values[arg_type] if not isinstance(not_list, list)]
|
||||
ret = ret.union(tmp)
|
||||
|
||||
#Intercept the options and in case detector specific options appear replace the
|
||||
#list of options with a command line call that fetches them
|
||||
#TODO! Rename detg
|
||||
if "defs::dacIndex" in arg_types:
|
||||
return "`detg daclist | sed -e 's/.*\[\(.*\)\].*/\\1/' | sed 's/,//g'`"
|
||||
elif "defs::detectorSettings" in arg_types:
|
||||
return "`detg settingslist | sed -e 's/.*\[\(.*\)\].*/\\1/' | sed 's/,//g'`"
|
||||
elif "defs::timingMode" in arg_types:
|
||||
return "`detg timinglist | sed -e 's/.*\[\(.*\)\].*/\\1/' | sed 's/,//g'`"
|
||||
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def type_info(type_name):
|
||||
if type_name.startswith('defs::') or type_name.startswith('slsDetectorDefs::'):
|
||||
return 'enum'
|
||||
if type_name.startswith('special::'):
|
||||
return 'special'
|
||||
return 'base'
|
||||
|
||||
|
||||
def get_enum(function):
|
||||
return function['type']['qualType'].split(' ')[0]
|
||||
|
||||
|
||||
def get_literal(ifstmt):
|
||||
stringliteral = []
|
||||
expression = ifstmt['inner'][0]
|
||||
if expression['kind'] == 'BinaryOperator':
|
||||
if expression['opcode'] == '!=':
|
||||
return None, None
|
||||
for cxxOperatorCall in expression['inner']:
|
||||
if cxxOperatorCall['kind'] == 'CXXOperatorCallExpr':
|
||||
implicitCastExpr = cxxOperatorCall['inner'][2]
|
||||
stringliteral.append(implicitCastExpr['inner'][0]['value'][1:-1])
|
||||
else:
|
||||
cxxOperatorCall = expression
|
||||
implicitCastExpr = cxxOperatorCall['inner'][2]
|
||||
stringliteral = implicitCastExpr['inner'][0]['value'][1:-1]
|
||||
|
||||
retstmt = get_object_by_kind(ifstmt['inner'], 'ReturnStmt')
|
||||
declrefexpt = get_object_by_kind(retstmt['inner'], 'DeclRefExpr')
|
||||
enum_val = declrefexpt["referencedDecl"]["name"]
|
||||
|
||||
return enum_val, stringliteral
|
||||
|
||||
|
||||
def get_object_by_kind(inner, kind, position=1):
|
||||
for obj in inner:
|
||||
if obj['kind'] == kind:
|
||||
position -= 1
|
||||
if position == 0:
|
||||
return obj
|
||||
return None
|
||||
|
||||
|
||||
def generate_type_values():
|
||||
functions = json.loads(FIXED_PATH.read_text())
|
||||
for function in functions:
|
||||
if function['kind'] != 'FunctionDecl' or function['name'] != 'StringTo':
|
||||
continue
|
||||
enum = get_enum(function)
|
||||
|
||||
if not enum.startswith('defs::'):
|
||||
continue
|
||||
# if enum != 'defs::dacIndex':
|
||||
# continue
|
||||
if not function['loc']['file'].endswith('ToString.cpp'):
|
||||
continue
|
||||
|
||||
compound_stmt = get_object_by_kind(function['inner'], 'CompoundStmt')
|
||||
|
||||
for ifstmt in compound_stmt['inner']:
|
||||
if ifstmt['kind'] != 'IfStmt':
|
||||
continue
|
||||
enum_val, stringliteral = get_literal(ifstmt)
|
||||
if enum_val is None:
|
||||
continue
|
||||
|
||||
if enum not in type_values or type_values[enum] is None:
|
||||
type_values[enum] = []
|
||||
type_values[enum].append(stringliteral)
|
||||
items = list(type_values.items())
|
||||
for key, val in items:
|
||||
if key.startswith('defs::'):
|
||||
new_key = key.split('::')[1]
|
||||
new_key = 'slsDetectorDefs::' + new_key
|
||||
type_values[new_key] = val
|
||||
elif key.startswith('slsDetectorDefs::'):
|
||||
new_key = key.split('::')[1]
|
||||
new_key = 'defs::' + new_key
|
||||
type_values[new_key] = val
|
||||
|
||||
return json.dumps(type_values, indent=2)
|
||||
|
||||
|
||||
def fix_json():
|
||||
with DUMP_PATH.open('r') as f:
|
||||
tmp = '[\n'
|
||||
for line in f.read().split('\n'):
|
||||
if line.startswith('}'):
|
||||
tmp += line + ',\n'
|
||||
else:
|
||||
tmp += line + '\n'
|
||||
tmp = tmp[:-3] + '\n]'
|
||||
with FIXED_PATH.open('w') as f:
|
||||
f.write(tmp)
|
||||
|
||||
|
||||
def generate_bash_autocomplete(output_path=Path(__file__).parent / 'bash_autocomplete.sh', input_path=Path(__file__).parent / 'bash_autocomplete.in.sh'):
|
||||
generate_type_values()
|
||||
output_file = output_path.open('w')
|
||||
template_file = input_path.open('r')
|
||||
|
||||
def writeline(line):
|
||||
output_file.write(line + '\n')
|
||||
|
||||
class if_block:
|
||||
def __init__(self, condition):
|
||||
self.condition = condition
|
||||
|
||||
def __enter__(self):
|
||||
output_file.write('if [[ ' + self.condition + ' ]]; then\n')
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
output_file.write('fi\n')
|
||||
|
||||
class function:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def __enter__(self):
|
||||
output_file.write(self.name + '() {\n')
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
output_file.write('}\n')
|
||||
|
||||
command_path = Path(__file__).parent.parent / 'extended_commands.yaml'
|
||||
commands = yaml.unsafe_load(command_path.open('r'))
|
||||
|
||||
for line in template_file:
|
||||
if '-- THIS LINE WILL BE REPLACED WITH GENERATED CODE --' not in line:
|
||||
output_file.write(line)
|
||||
continue
|
||||
writeline(f'local SLS_COMMANDS=" {" ".join(commands.keys())} "')
|
||||
# generate functions
|
||||
for command_name, command in commands.items():
|
||||
# added for debugging
|
||||
if command_name == 'xxxexptime':
|
||||
continue
|
||||
with function('__' + command_name):
|
||||
writeline('FCN_RETURN=""')
|
||||
|
||||
actions = ['GET', 'PUT']
|
||||
for action in actions:
|
||||
if action in command['actions'] and 'args' in command['actions'][action]:
|
||||
args = command['actions'][action]['args']
|
||||
possible_argc = {}
|
||||
for arg in args:
|
||||
if arg['argc'] == 0:
|
||||
pass
|
||||
for i in range(arg['argc']):
|
||||
if i + 1 not in possible_argc:
|
||||
possible_argc[i + 1] = []
|
||||
possible_argc[i + 1].append(arg['arg_types'][i])
|
||||
if possible_argc:
|
||||
with if_block(f'${{IS_GET}} -eq {"1" if action == "GET" else "0"}'):
|
||||
for argc in possible_argc:
|
||||
with if_block(f'"${{cword}}" == "{argc + 1}"'):
|
||||
if "defs::detectorSettings" in possible_argc[argc]:
|
||||
print(argc, command_name, possible_argc[argc])
|
||||
choices = get_types(possible_argc[argc])
|
||||
|
||||
#check if we got choices back or a bash command
|
||||
if isinstance(choices, (list,set)):
|
||||
writeline(f'FCN_RETURN="{" ".join(sorted(choices))}"')
|
||||
else:
|
||||
writeline(f'FCN_RETURN="{choices}"')
|
||||
if 'special::path' in possible_argc[argc]:
|
||||
writeline('IS_PATH=1')
|
||||
|
||||
writeline('return 0')
|
||||
|
||||
|
||||
|
||||
output_file.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description='use parsed c++ code to generate autocomplete snippets')
|
||||
parser.add_argument('-f', '--fix', action='store_true', help='fix the parsed ast to make it loadable')
|
||||
# parser.add_argument('-p', '--path', type=str, help='output path to the fixed ast', default='ast.json')
|
||||
args = parser.parse_known_args()
|
||||
if args[0].fix:
|
||||
fix_json()
|
||||
ret = generate_type_values()
|
||||
print(ret)
|
@ -0,0 +1,164 @@
|
||||
# GENERATED FILE - DO NOT EDIT
|
||||
# ANY CHANGES TO THIS FILE WILL BE OVERWRITTEN
|
||||
|
||||
_sd() {
|
||||
|
||||
# Taken from https://github.com/scop/bash-completion/blob/15b74b1050333f425877a7cbd99af2998b95c476/bash_completion#L770C12-L770C12
|
||||
# Reassemble command line words, excluding specified characters from the
|
||||
# list of word completion separators (COMP_WORDBREAKS).
|
||||
# @param $1 chars Characters out of $COMP_WORDBREAKS which should
|
||||
# NOT be considered word breaks. This is useful for things like scp where
|
||||
# we want to return host:path and not only path, so we would pass the
|
||||
# colon (:) as $1 here.
|
||||
# @param $2 words Name of variable to return words to
|
||||
# @param $3 cword Name of variable to return cword to
|
||||
#
|
||||
_comp__reassemble_words()
|
||||
{
|
||||
local exclude="" i j line ref
|
||||
# Exclude word separator characters?
|
||||
if [[ $1 ]]; then
|
||||
# Yes, exclude word separator characters;
|
||||
# Exclude only those characters, which were really included
|
||||
exclude="[${1//[^$COMP_WORDBREAKS]/}]"
|
||||
fi
|
||||
|
||||
# Default to cword unchanged
|
||||
printf -v "$3" %s "$COMP_CWORD"
|
||||
# Are characters excluded which were former included?
|
||||
if [[ $exclude ]]; then
|
||||
# Yes, list of word completion separators has shrunk;
|
||||
line=$COMP_LINE
|
||||
# Re-assemble words to complete
|
||||
for ((i = 0, j = 0; i < ${#COMP_WORDS[@]}; i++, j++)); do
|
||||
# Is current word not word 0 (the command itself) and is word not
|
||||
# empty and is word made up of just word separator characters to
|
||||
# be excluded and is current word not preceded by whitespace in
|
||||
# original line?
|
||||
while [[ $i -gt 0 && ${COMP_WORDS[i]} == +($exclude) ]]; do
|
||||
# Is word separator not preceded by whitespace in original line
|
||||
# and are we not going to append to word 0 (the command
|
||||
# itself), then append to current word.
|
||||
[[ $line != [[:blank:]]* ]] && ((j >= 2)) && ((j--))
|
||||
# Append word separator to current or new word
|
||||
ref="$2[$j]"
|
||||
printf -v "$ref" %s "${!ref-}${COMP_WORDS[i]}"
|
||||
# Indicate new cword
|
||||
((i == COMP_CWORD)) && printf -v "$3" %s "$j"
|
||||
# Remove optional whitespace + word separator from line copy
|
||||
line=${line#*"${COMP_WORDS[i]}"}
|
||||
# Indicate next word if available, else end *both* while and
|
||||
# for loop
|
||||
if ((i < ${#COMP_WORDS[@]} - 1)); then
|
||||
((i++))
|
||||
else
|
||||
break 2
|
||||
fi
|
||||
# Start new word if word separator in original line is
|
||||
# followed by whitespace.
|
||||
[[ $line == [[:blank:]]* ]] && ((j++))
|
||||
done
|
||||
# Append word to current word
|
||||
ref="$2[$j]"
|
||||
printf -v "$ref" %s "${!ref-}${COMP_WORDS[i]}"
|
||||
# Remove optional whitespace + word from line copy
|
||||
line=${line#*"${COMP_WORDS[i]}"}
|
||||
# Indicate new cword
|
||||
((i == COMP_CWORD)) && printf -v "$3" %s "$j"
|
||||
done
|
||||
((i == COMP_CWORD)) && printf -v "$3" %s "$j"
|
||||
else
|
||||
# No, list of word completions separators hasn't changed;
|
||||
for i in "${!COMP_WORDS[@]}"; do
|
||||
printf -v "$2[i]" %s "${COMP_WORDS[i]}"
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
local FCN_RETURN=""
|
||||
local IS_PATH=0
|
||||
|
||||
|
||||
# -- THIS LINE WILL BE REPLACED WITH GENERATED CODE --
|
||||
|
||||
|
||||
COMPREPLY=()
|
||||
local OPTIONS_NEW=""
|
||||
|
||||
# check if bash or zsh
|
||||
# _get_comp_words_by_ref is a bash built-in function, we check if it exists
|
||||
declare -Ff _get_comp_words_by_ref > /dev/null && IS_BASH=1 || IS_BASH=0
|
||||
|
||||
|
||||
# bash interprets the colon character : as a special character and splits the argument in two
|
||||
# different than what zsh does
|
||||
# https://stackoverflow.com/a/3224910
|
||||
# https://stackoverflow.com/a/12495727
|
||||
local cur
|
||||
local cword words=()
|
||||
_comp__reassemble_words ":" words cword
|
||||
cur=${words[cword]}
|
||||
|
||||
# check the action (get or put)
|
||||
case "${words[0]}" in
|
||||
"sls_detector_get" | "g" | "detg")
|
||||
local IS_GET=1
|
||||
;;
|
||||
*)
|
||||
local IS_GET=0
|
||||
;;
|
||||
esac
|
||||
|
||||
# if no command is written, autocomplete with the commands
|
||||
if [[ ${cword} -eq 1 ]]; then
|
||||
# local SLS_COMMANDS="trimbits exptime"
|
||||
local SLS_COMMANDS_NEW=""
|
||||
|
||||
case "$cur" in
|
||||
[0-9]*:*)
|
||||
local suggestions=($(compgen -W "${SLS_COMMANDS}" -- "${cur#*:}"))
|
||||
COMPREPLY=( ${suggestions[*]} )
|
||||
;;
|
||||
[0-9]*)
|
||||
COMPREPLY=()
|
||||
;;
|
||||
*)
|
||||
COMPREPLY=( $( compgen -W "$SLS_COMMANDS -h" -- "$cur" ) );;
|
||||
|
||||
esac
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ ${cword} -eq 2 ]] && [[ ${words[1]} == "-h" ]]; then
|
||||
COMPREPLY=( $( compgen -W "$SLS_COMMANDS" -- "$cur" ) )
|
||||
return 0
|
||||
fi
|
||||
|
||||
# if a command is written, autocomplete with the options
|
||||
# call the function for the command
|
||||
|
||||
if [[ "$SLS_COMMANDS" == *"${words[1]##*:}"* ]]; then
|
||||
__"${words[1]##*:}"
|
||||
fi
|
||||
|
||||
# if IS_PATH is activated, autocomplete with the path
|
||||
if [[ ${IS_PATH} -eq 1 ]]; then
|
||||
COMPREPLY=($(compgen -f -- "${cur}"))
|
||||
return 0
|
||||
fi
|
||||
|
||||
# autocomplete with the options
|
||||
COMPREPLY=($(compgen -W "${FCN_RETURN}" -- "${cur}"))
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
complete -F _sd -o filenames sls_detector_get
|
||||
complete -F _sd -o filenames g
|
||||
complete -F _sd -o filenames detg
|
||||
|
||||
complete -F _sd -o filenames sls_detector_put
|
||||
complete -F _sd -o filenames p
|
||||
complete -F _sd -o filenames detp
|
3252
slsDetectorSoftware/generator/autocomplete/bash_autocomplete.sh
Normal file
3252
slsDetectorSoftware/generator/autocomplete/bash_autocomplete.sh
Normal file
File diff suppressed because it is too large
Load Diff
62035
slsDetectorSoftware/generator/autocomplete/dump.json
Normal file
62035
slsDetectorSoftware/generator/autocomplete/dump.json
Normal file
File diff suppressed because it is too large
Load Diff
62037
slsDetectorSoftware/generator/autocomplete/fixed.json
Normal file
62037
slsDetectorSoftware/generator/autocomplete/fixed.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,171 @@
|
||||
#### simpler version of autocomplete.sh to understand the logic
|
||||
#### each command has its own function when called it will produce the possible values for autocompletion
|
||||
|
||||
|
||||
_sd() {
|
||||
|
||||
# Reassemble command line words, excluding specified characters from the
|
||||
# list of word completion separators (COMP_WORDBREAKS).
|
||||
# @param $1 chars Characters out of $COMP_WORDBREAKS which should
|
||||
# NOT be considered word breaks. This is useful for things like scp where
|
||||
# we want to return host:path and not only path, so we would pass the
|
||||
# colon (:) as $1 here.
|
||||
# @param $2 words Name of variable to return words to
|
||||
# @param $3 cword Name of variable to return cword to
|
||||
#
|
||||
_comp__reassemble_words()
|
||||
{
|
||||
local exclude="" i j line ref
|
||||
# Exclude word separator characters?
|
||||
if [[ $1 ]]; then
|
||||
# Yes, exclude word separator characters;
|
||||
# Exclude only those characters, which were really included
|
||||
exclude="[${1//[^$COMP_WORDBREAKS]/}]"
|
||||
fi
|
||||
|
||||
# Default to cword unchanged
|
||||
printf -v "$3" %s "$COMP_CWORD"
|
||||
# Are characters excluded which were former included?
|
||||
if [[ $exclude ]]; then
|
||||
# Yes, list of word completion separators has shrunk;
|
||||
line=$COMP_LINE
|
||||
# Re-assemble words to complete
|
||||
for ((i = 0, j = 0; i < ${#COMP_WORDS[@]}; i++, j++)); do
|
||||
# Is current word not word 0 (the command itself) and is word not
|
||||
# empty and is word made up of just word separator characters to
|
||||
# be excluded and is current word not preceded by whitespace in
|
||||
# original line?
|
||||
while [[ $i -gt 0 && ${COMP_WORDS[i]} == +($exclude) ]]; do
|
||||
# Is word separator not preceded by whitespace in original line
|
||||
# and are we not going to append to word 0 (the command
|
||||
# itself), then append to current word.
|
||||
[[ $line != [[:blank:]]* ]] && ((j >= 2)) && ((j--))
|
||||
# Append word separator to current or new word
|
||||
ref="$2[$j]"
|
||||
printf -v "$ref" %s "${!ref-}${COMP_WORDS[i]}"
|
||||
# Indicate new cword
|
||||
((i == COMP_CWORD)) && printf -v "$3" %s "$j"
|
||||
# Remove optional whitespace + word separator from line copy
|
||||
line=${line#*"${COMP_WORDS[i]}"}
|
||||
# Indicate next word if available, else end *both* while and
|
||||
# for loop
|
||||
if ((i < ${#COMP_WORDS[@]} - 1)); then
|
||||
((i++))
|
||||
else
|
||||
break 2
|
||||
fi
|
||||
# Start new word if word separator in original line is
|
||||
# followed by whitespace.
|
||||
[[ $line == [[:blank:]]* ]] && ((j++))
|
||||
done
|
||||
# Append word to current word
|
||||
ref="$2[$j]"
|
||||
printf -v "$ref" %s "${!ref-}${COMP_WORDS[i]}"
|
||||
# Remove optional whitespace + word from line copy
|
||||
line=${line#*"${COMP_WORDS[i]}"}
|
||||
# Indicate new cword
|
||||
((i == COMP_CWORD)) && printf -v "$3" %s "$j"
|
||||
done
|
||||
((i == COMP_CWORD)) && printf -v "$3" %s "$j"
|
||||
else
|
||||
# No, list of word completions separators hasn't changed;
|
||||
for i in "${!COMP_WORDS[@]}"; do
|
||||
printf -v "$2[i]" %s "${COMP_WORDS[i]}"
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
|
||||
__exptime(){
|
||||
if [ "${IS_GET}" == "1" ]; then
|
||||
if [ "${cword}" == "2" ]; then
|
||||
FCN_RETURN="s ms us ns"
|
||||
fi
|
||||
else
|
||||
if [ "${cword}" == "2" ]; then
|
||||
FCN_RETURN=""
|
||||
fi
|
||||
|
||||
if [ "${cword}" == "3" ]; then
|
||||
FCN_RETURN="s ms us ns"
|
||||
fi
|
||||
|
||||
fi
|
||||
}
|
||||
|
||||
# trimbits will activate IS_PATH and signal that its input is a path
|
||||
__trimbits(){
|
||||
if [ "${IS_GET}" == "1" ]; then
|
||||
if [ "${cword}" == "2" ]; then
|
||||
FCN_RETURN=""
|
||||
IS_PATH=1
|
||||
fi
|
||||
else
|
||||
if [ "${cword}" == "2" ]; then
|
||||
FCN_RETURN=""
|
||||
IS_PATH=1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
local cword words=()
|
||||
_comp__reassemble_words ":" words cword
|
||||
local FCN_RETURN=""
|
||||
local IS_PATH=0
|
||||
COMPREPLY=()
|
||||
local OPTIONS_NEW=""
|
||||
# _get_comp_words_by_ref -n : cur
|
||||
local cur=${words[cword]}
|
||||
# check the action (get or put)
|
||||
if [ "${words[0]}" == "sls_detector_get" ]; then
|
||||
local IS_GET=1
|
||||
else
|
||||
local IS_GET=0
|
||||
fi
|
||||
|
||||
|
||||
# if no command is written, autocomplete with the commands
|
||||
if [[ ${cword} -eq 1 ]]; then
|
||||
local SLS_COMMANDS="trimbits exptime"
|
||||
local SLS_COMMANDS_NEW=""
|
||||
|
||||
case "$cur" in
|
||||
[0-9]*:*)
|
||||
local suggestions=($(compgen -W "${SLS_COMMANDS}" -- "${cur#*:}"))
|
||||
COMPREPLY=( ${suggestions[*]} )
|
||||
;;
|
||||
[0-9]*)
|
||||
COMPREPLY=()
|
||||
;;
|
||||
*)
|
||||
COMPREPLY=( $( compgen -W "$SLS_COMMANDS -h" -- "$cur" ) );;
|
||||
|
||||
esac
|
||||
return 0
|
||||
fi
|
||||
|
||||
# if a command is written, autocomplete with the options
|
||||
# call the function for the command
|
||||
__"${words[1]##*:}"
|
||||
|
||||
# if IS_PATH is activated, autocomplete with the path
|
||||
if [[ ${IS_PATH} -eq 1 ]]; then
|
||||
COMPREPLY=($(compgen -f -- "${cur}"))
|
||||
return 0
|
||||
fi
|
||||
|
||||
# autocomplete with the options
|
||||
COMPREPLY=($(compgen -W "${FCN_RETURN}" -- "${cur}"))
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
complete -F _sd -o filenames sls_detector_get
|
||||
complete -F _sd -o filenames g
|
||||
complete -F _sd -o filenames p
|
||||
complete -F _sd -o filenames detg
|
||||
complete -F _sd -o filenames detp
|
||||
|
||||
complete -F _sd -o filenames sls_detector_put
|
@ -0,0 +1,74 @@
|
||||
# GENERATED FILE - DO NOT EDIT
|
||||
# ANY CHANGES TO THIS FILE WILL BE OVERWRITTEN
|
||||
|
||||
_sd() {
|
||||
|
||||
|
||||
# -- THIS LINE WILL BE REPLACED WITH GENERATED CODE --
|
||||
|
||||
|
||||
local FCN_RETURN=""
|
||||
local IS_PATH=0
|
||||
COMPREPLY=()
|
||||
local OPTIONS_NEW=""
|
||||
words=("${COMP_WORDS[@]}")
|
||||
cword=$COMP_CWORD
|
||||
|
||||
local cur=${words[cword]}
|
||||
# check the action (get or put)
|
||||
case "${words[0]}" in
|
||||
"sls_detector_get" | "g" | "detg")
|
||||
local IS_GET=1
|
||||
;;
|
||||
*)
|
||||
local IS_GET=0
|
||||
;;
|
||||
esac
|
||||
|
||||
# if no command is written, autocomplete with the commands
|
||||
if [[ ${cword} -eq 1 ]]; then
|
||||
|
||||
case "$cur" in
|
||||
[0-9]*)
|
||||
for i in $SLS_COMMANDS; do
|
||||
SLS_COMMANDS_NEW="${SLS_COMMANDS_NEW} ${cur%%:*}:$i"
|
||||
done
|
||||
COMPREPLY=( $( compgen -W "${SLS_COMMANDS_NEW}" -- "$cur" ) );;
|
||||
*)
|
||||
COMPREPLY=( $( compgen -W "$SLS_COMMANDS -h" -- "$cur" ) );;
|
||||
esac
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ ${cword} -eq 2 ]] && [[ ${words[1]} == "-h" ]]; then
|
||||
COMPREPLY=( $( compgen -W "$SLS_COMMANDS" -- "$cur" ) )
|
||||
return 0
|
||||
fi
|
||||
|
||||
# if a command is written, autocomplete with the options
|
||||
# call the function for the command
|
||||
|
||||
if [[ "$SLS_COMMANDS" == *"${words[1]##*:}"* ]]; then
|
||||
__"${words[1]##*:}"
|
||||
fi
|
||||
|
||||
# if IS_PATH is activated, autocomplete with the path
|
||||
if [[ ${IS_PATH} -eq 1 ]]; then
|
||||
COMPREPLY=($(compgen -f -- "${cur}"))
|
||||
return 0
|
||||
fi
|
||||
|
||||
# autocomplete with the options
|
||||
COMPREPLY=($(compgen -W "${FCN_RETURN}" -- "${cur}"))
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
complete -F _sd -o filenames sls_detector_get
|
||||
complete -F _sd -o filenames g
|
||||
complete -F _sd -o filenames detg
|
||||
|
||||
complete -F _sd -o filenames sls_detector_put
|
||||
complete -F _sd -o filenames p
|
||||
complete -F _sd -o filenames detp
|
3162
slsDetectorSoftware/generator/autocomplete/zsh_autocomplete.sh
Normal file
3162
slsDetectorSoftware/generator/autocomplete/zsh_autocomplete.sh
Normal file
File diff suppressed because it is too large
Load Diff
4264
slsDetectorSoftware/generator/commands.yaml
Normal file
4264
slsDetectorSoftware/generator/commands.yaml
Normal file
File diff suppressed because it is too large
Load Diff
379
slsDetectorSoftware/generator/commands_parser/commands_parser.py
Normal file
379
slsDetectorSoftware/generator/commands_parser/commands_parser.py
Normal file
@ -0,0 +1,379 @@
|
||||
import copy
|
||||
import logging
|
||||
import yaml
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class CommandParser:
|
||||
def __init__(
|
||||
self,
|
||||
commands_file: Path = Path(__file__).parent.parent / 'commands.yaml',
|
||||
output_file: Path = Path(__file__).parent.parent / 'extended_commands.yaml'
|
||||
):
|
||||
|
||||
self.output_file = output_file
|
||||
self.commands_file = commands_file
|
||||
self.fp = self.commands_file.open('r')
|
||||
self.simple_commands = yaml.unsafe_load(self.fp)
|
||||
self.extended_commands = {}
|
||||
self.argc_set = set()
|
||||
self.logger = logging.getLogger('command_parser')
|
||||
self.__current_action: str = ''
|
||||
FORMAT = '[%(levelname)s] %(message)s'
|
||||
logging.basicConfig(format=FORMAT, level=logging.INFO)
|
||||
|
||||
self.propagate_config = {
|
||||
'require_det_id': False,
|
||||
'convert_det_id': True,
|
||||
'input': [],
|
||||
'input_types': [],
|
||||
'function': '',
|
||||
'output': [],
|
||||
'cast_input': [],
|
||||
'check_det_id': False,
|
||||
'arg_types': [],
|
||||
# 'store_result_in_t': False, # always true in GET action
|
||||
}
|
||||
self.default_config = {
|
||||
'infer_action': True,
|
||||
'help': '',
|
||||
'actions': {}
|
||||
}
|
||||
|
||||
def _verify_argument(self, arg, infer_action, command_name, action):
|
||||
if arg['function'] == '' and 'ctb_output_list' not in arg:
|
||||
special_exception_message_list = ["Cannot put", "Cannot get"]
|
||||
if 'exceptions' in arg and arg['exceptions'][0]['condition'] == 'true' and any(ele in arg['exceptions'][0]['message'] for ele in special_exception_message_list):
|
||||
self.logger.warning(f"{command_name} has a special exception message for {action}.")
|
||||
else:
|
||||
self.logger.warning(f"{command_name} [{action}] does not have a function")
|
||||
if len(arg['input_types']) != len(arg['input']):
|
||||
raise ValueError(f'Argument {arg} does not have the correct number of inputs')
|
||||
if 'separate_time_units' in arg:
|
||||
|
||||
if arg['separate_time_units']['input'] == "":
|
||||
raise ValueError(f'Argument {arg} does not have the correct number of inputs for separate_time_units')
|
||||
if len(arg['separate_time_units']['output']) != 2:
|
||||
raise ValueError(f'Argument {arg} does not have the correct number of outputs for separate_time_units')
|
||||
if 'convert_to_time' in arg:
|
||||
if len(arg['convert_to_time']['input']) != 2:
|
||||
raise ValueError(f'Argument {arg} does not have the correct number of inputs for convert_to_time')
|
||||
if len(arg['convert_to_time']['output']) == "":
|
||||
raise ValueError(f'Argument {arg} does not have the correct number of outputs for convert_to_time')
|
||||
# if infer_action:
|
||||
# if arg['argc'] in self.argc_set:
|
||||
# raise ValueError(f'Argument {arg} has a duplicate argc')
|
||||
# self.argc_set.add(arg['argc'])
|
||||
|
||||
def verify_format(self):
|
||||
# todo verify detectors
|
||||
# todo verify circular inheritance
|
||||
# todo verify child commands (those that inherit)
|
||||
# todo verify that there is no wrongly typed parameters
|
||||
# todo verify that the same number of input_types and input are given
|
||||
# todo verify that each argument has argc (error can happen when inheriting)
|
||||
for command_name, command in self.simple_commands.items():
|
||||
if 'inherit_actions' in command or 'template' in command and command[
|
||||
'template'] or 'is_description' in command and command['is_description']:
|
||||
continue
|
||||
self.argc_set = set()
|
||||
if 'infer_action' not in command:
|
||||
command['infer_action'] = True
|
||||
if 'actions' not in command:
|
||||
raise ValueError(f'Command {command_name} does not have any actions')
|
||||
for action, action_params in command['actions'].items():
|
||||
if 'argc' in action_params:
|
||||
if 'args' in action_params:
|
||||
raise ValueError(f'Action {action} has both argc and args')
|
||||
arg = {**self.propagate_config, **action_params}
|
||||
self._verify_argument(arg, command['infer_action'], command_name, action)
|
||||
elif 'args' in action_params:
|
||||
if type(action_params['args']) is not list:
|
||||
raise ValueError(f'Action {action} args is not a list')
|
||||
if len(action_params['args']) == 0:
|
||||
raise ValueError(f'Action {action} args is empty')
|
||||
action_args = {**self.propagate_config, **action_params}
|
||||
del action_args['args']
|
||||
for arg in action_params['args']:
|
||||
arg = {**action_args, **arg}
|
||||
self._verify_argument(arg, command['infer_action'], command_name, action)
|
||||
self.logger.info('Commands file is valid ✅️')
|
||||
return True
|
||||
|
||||
def _parse_inherited_command(self, parent, command, simple_parent):
|
||||
"""
|
||||
parse a command that inherits from parent command
|
||||
:param parent: parsed parent command
|
||||
:param command: the current command
|
||||
:param simple_parent: unparsed parent command
|
||||
:return: parsed command
|
||||
"""
|
||||
# deepcopy parent and command to avoid modifying the originals
|
||||
command = copy.deepcopy(command)
|
||||
config = copy.deepcopy(parent)
|
||||
# add help
|
||||
if 'help' in command:
|
||||
config['help'] = command['help']
|
||||
if 'actions' not in command:
|
||||
return config
|
||||
for action, command_params in command['actions'].items():
|
||||
self.__current_action = action
|
||||
if action not in config['actions']:
|
||||
# todo: handle this case
|
||||
pass
|
||||
parent_params = config['actions'][action]
|
||||
if 'args' in command_params:
|
||||
# child has args => inherit action level params from parent + override with child args + use child's
|
||||
# action level params
|
||||
context = {**self.propagate_config, **simple_parent['actions'][action], **command_params}
|
||||
config['actions'][action]['args'] = self.parse_action(context, command_params['args'])
|
||||
elif 'argc' in command_params:
|
||||
# child has action level args (argc)
|
||||
context = {**self.propagate_config, **simple_parent['actions'][action], **command_params}
|
||||
config['actions'][action]['args'] = self.parse_action(context, [])
|
||||
else:
|
||||
# child does not have args => use parent's action level params + override with child's action level
|
||||
if 'args' in parent_params:
|
||||
config['actions'][action]['args'] = self.parse_action({}, parent_params['args'], command_params)
|
||||
|
||||
if 'detectors' in command_params:
|
||||
if command_params['detectors'] is None:
|
||||
# if child has an empty detector section, then delete the parent's detector section
|
||||
del config['actions'][action]['detectors']
|
||||
continue
|
||||
|
||||
for detector_name, detector_params in command_params['detectors'].items():
|
||||
if 'detectors' not in config['actions'][action]:
|
||||
config['actions'][action]['detectors'] = {}
|
||||
config_detector = config['actions'][action]['detectors']
|
||||
if 'detectors' not in parent_params or detector_name not in parent_params['detectors']:
|
||||
if 'args' in detector_params:
|
||||
# if child has detector args and parent does not have detectors
|
||||
# => use child's detector args
|
||||
context = {**self.propagate_config, **simple_parent['actions'][action], **detector_params}
|
||||
config_detector[detector_name] = self.parse_action(context, detector_params['args'])
|
||||
elif 'args' in parent_params:
|
||||
# if child does not have detector args and parent does not have detectors
|
||||
# => use the child's action args
|
||||
context = {**self.propagate_config, **simple_parent['actions'][action]}
|
||||
config_detector[detector_name] = self.parse_action(context,
|
||||
config['actions'][action]['args'],
|
||||
detector_params)
|
||||
elif detector_name in parent_params['detectors']:
|
||||
|
||||
if 'args' in detector_params:
|
||||
# child and parent have the same detector and child has detector args
|
||||
# => use child's detector args
|
||||
context = {
|
||||
**self.propagate_config,
|
||||
**simple_parent['actions'][action],
|
||||
**simple_parent['actions'][action]['detectors'][detector_name],
|
||||
|
||||
}
|
||||
config_detector[detector_name] = self.parse_action(context, detector_params['args'])
|
||||
else:
|
||||
# child and parent have the same detector and child does not have detector args
|
||||
# => use parent's detector args
|
||||
priority_context = {**command_params, **detector_params}
|
||||
config_detector[detector_name] = self.parse_action(
|
||||
{},
|
||||
parent_params['detectors'][detector_name],
|
||||
priority_context
|
||||
)
|
||||
|
||||
else:
|
||||
pass
|
||||
|
||||
return config
|
||||
|
||||
def _parse_command(self, command):
|
||||
"""
|
||||
logic function for parse_command.
|
||||
This function is recursive
|
||||
:return: parsed command
|
||||
"""
|
||||
config = self.default_config.copy()
|
||||
config.update(command)
|
||||
config['actions'] = {}
|
||||
|
||||
# check if command inherits from another command
|
||||
if 'inherit_actions' in command:
|
||||
if command['inherit_actions'] in self.extended_commands:
|
||||
# if parent command has already been parsed, use that
|
||||
parent = self.extended_commands[command['inherit_actions']]
|
||||
else:
|
||||
# if parent command has not been parsed, parse it
|
||||
parent = self.parse_command(command['inherit_actions'])
|
||||
# parse the current command and merge it with the parent command
|
||||
config = self._parse_inherited_command(parent, command, self.simple_commands[command['inherit_actions']])
|
||||
return config
|
||||
|
||||
if 'actions' not in command:
|
||||
return config
|
||||
|
||||
for action, action_params in command['actions'].items():
|
||||
self.__current_action = action
|
||||
|
||||
config['actions'][action] = {}
|
||||
config_action = config['actions'][action]
|
||||
# the context in the current command and the current action
|
||||
action_context = {**self.propagate_config, **action_params}
|
||||
if 'args' not in action_params:
|
||||
# parse the action with the action context
|
||||
if action_params.keys() != {'detectors'}:
|
||||
config_action['args'] = self.parse_action(action_context, [])
|
||||
else:
|
||||
config_action['args'] = self.parse_action(action_context, action_params['args'])
|
||||
|
||||
# check if the action has detectors
|
||||
if 'detectors' in action_params:
|
||||
config_action['detectors'] = {}
|
||||
for detector_name, detector_params in action_params['detectors'].items():
|
||||
# get the context for the detector and merge it with the action context
|
||||
detector_context = {**action_context, **detector_params}
|
||||
|
||||
# if the detector does not have args, then use the action args
|
||||
# otherwise, use the detector args and override the action args
|
||||
tmp_args = []
|
||||
if 'args' not in detector_params:
|
||||
if 'args' in config_action:
|
||||
tmp_args = config_action['args']
|
||||
else:
|
||||
tmp_args = detector_params['args']
|
||||
detector_params['args'] = tmp_args
|
||||
|
||||
# parse the action with the detector context
|
||||
config_action['detectors'][detector_name] = self.parse_action(detector_context,
|
||||
tmp_args,
|
||||
detector_params)
|
||||
return config
|
||||
|
||||
def sanitize_argument(func):
|
||||
def f(self, action_context, args_old, priority_context={}):
|
||||
args = func(self, action_context, args_old, priority_context)
|
||||
for i, arg in enumerate(args):
|
||||
if 'args' in arg:
|
||||
del arg['args']
|
||||
if 'detectors' in arg:
|
||||
del arg['detectors']
|
||||
if not arg['cast_input']:
|
||||
# if the cast_input is empty, then set it to False
|
||||
arg['cast_input'] = [False] * len(arg['input'])
|
||||
|
||||
elif len(arg['cast_input']) != len(arg['input']):
|
||||
# if the cast_input is not the same length as the input, then set it to False
|
||||
arg['cast_input'] = [False] * len(arg['input'])
|
||||
self.logger.warning(f'cast_input for {arg["function"]} '
|
||||
f'with argc: {arg["argc"]} has different length than input')
|
||||
if 'store_result_in_t' not in arg:
|
||||
if self.__current_action == 'GET':
|
||||
arg['store_result_in_t'] = True
|
||||
else:
|
||||
arg['store_result_in_t'] = False
|
||||
return args
|
||||
|
||||
return f
|
||||
|
||||
@sanitize_argument
|
||||
def parse_action(self, action_context, args, priority_context={}):
|
||||
"""
|
||||
parse an action
|
||||
:param action_context: context of the action
|
||||
:param args: arguments to be used in the action
|
||||
:param priority_context: context that should override the arguments params
|
||||
:return: parsed action
|
||||
"""
|
||||
|
||||
def add_cast_input(argument):
|
||||
return argument
|
||||
|
||||
# deepcopy action_context to avoid modifying the original
|
||||
action_context = {**self.propagate_config, **copy.deepcopy(action_context)}
|
||||
priority_context = copy.deepcopy(priority_context)
|
||||
|
||||
if 'detectors' in action_context:
|
||||
del action_context['detectors']
|
||||
if 'detectors' in priority_context:
|
||||
del priority_context['detectors']
|
||||
|
||||
if args == []:
|
||||
# if there are no arguments, then the action has only one argument
|
||||
context = {**action_context, **priority_context}
|
||||
return [add_cast_input(context)]
|
||||
|
||||
ret_args = []
|
||||
if 'args' in action_context:
|
||||
del action_context['args']
|
||||
if 'args' in priority_context:
|
||||
del priority_context['args']
|
||||
|
||||
# if there are arguments, then merge them with the action context and priority context
|
||||
for arg in args:
|
||||
arg = {**action_context, **arg, **priority_context}
|
||||
ret_args.append(add_cast_input(arg))
|
||||
return ret_args
|
||||
|
||||
def parse_command(self, command_name):
|
||||
"""
|
||||
parse a single command
|
||||
This function is recursive
|
||||
|
||||
:param command_name: name of the command to parse
|
||||
:return: the parsed command
|
||||
"""
|
||||
command = self.simple_commands[command_name]
|
||||
parsed_command = self._parse_command(command)
|
||||
if 'function_alias' not in command:
|
||||
if 'command_name' in command:
|
||||
parsed_command['function_alias'] = command['command_name']
|
||||
else:
|
||||
parsed_command['function_alias'] = command_name
|
||||
|
||||
if 'command_name' not in command:
|
||||
parsed_command['command_name'] = command_name
|
||||
|
||||
if 'template' in command and command['template']:
|
||||
return parsed_command
|
||||
self.extended_commands[command_name] = parsed_command
|
||||
return self.extended_commands[command_name]
|
||||
|
||||
def parse_all_commands(self):
|
||||
"""
|
||||
iterate over all commands in yaml file and parse them
|
||||
:return: None
|
||||
"""
|
||||
|
||||
for command_name in self.simple_commands:
|
||||
# todo remove this (added for debugging)
|
||||
if command_name != 'xtiming':
|
||||
self.parse_command(command_name)
|
||||
|
||||
# post-process the parsed commands
|
||||
self.post_process_all_commands()
|
||||
|
||||
yaml.Dumper.ignore_aliases = lambda *args: True
|
||||
self.logger.info(f'parsed {len(self.extended_commands)} commands')
|
||||
yaml.dump(self.extended_commands, self.output_file.open('w'), default_flow_style=False)
|
||||
|
||||
def post_process_all_commands(self):
|
||||
for command_name, command in self.extended_commands.items():
|
||||
if 'is_description' in command and command['is_description']:
|
||||
continue
|
||||
for action_name, action, in command['actions'].items():
|
||||
for arg in action['args']:
|
||||
if arg['argc'] == 0:
|
||||
arg['arg_types'] = []
|
||||
continue
|
||||
if arg['argc'] == -1:
|
||||
pass
|
||||
if arg['arg_types'] == []:
|
||||
arg['arg_types'] = arg['input_types']
|
||||
|
||||
|
||||
# command_parser = CommandParser(Path(
|
||||
# '/afs/psi.ch/user/b/braham_b/github/slsDetectorPackage/slsDetectorSoftware/generator/tests/command_parser/data/detectors.yaml'))
|
||||
command_parser = CommandParser()
|
||||
|
||||
if __name__ == '__main__':
|
||||
command_parser.verify_format()
|
||||
command_parser.parse_all_commands()
|
282
slsDetectorSoftware/generator/cpp_codegen/codegen.py
Normal file
282
slsDetectorSoftware/generator/cpp_codegen/codegen.py
Normal file
@ -0,0 +1,282 @@
|
||||
class CodeGenerator:
|
||||
def __init__(self):
|
||||
self.file = None
|
||||
self.actions_dict = {
|
||||
'GET': 'slsDetectorDefs::GET_ACTION',
|
||||
'PUT': 'slsDetectorDefs::PUT_ACTION',
|
||||
'READOUT': 'slsDetectorDefs::READOUT_ACTION',
|
||||
'HELP': 'slsDetectorDefs::HELP_ACTION',
|
||||
}
|
||||
self.template_file = None
|
||||
|
||||
def open(self, path):
|
||||
self.file = path.open('w')
|
||||
|
||||
def close(self):
|
||||
self.file.close()
|
||||
self.file = None
|
||||
|
||||
def write_line(self, line):
|
||||
self.file.write(line + '\n')
|
||||
|
||||
def write(self, text):
|
||||
self.file.write(text)
|
||||
|
||||
def write_opening(self, path):
|
||||
"""Write the opening file for the caller.cpp file"""
|
||||
self.template_file = path.open('r')
|
||||
for line in self.template_file:
|
||||
if "THIS COMMENT TO BE REPLACED BY THE ACTUAL CODE" in line:
|
||||
return
|
||||
self.file.write(line)
|
||||
|
||||
def write_closing(self):
|
||||
"""Write the closing file for the caller.cpp file"""
|
||||
for line in self.template_file.readlines():
|
||||
self.file.write(line)
|
||||
self.template_file.close()
|
||||
|
||||
def write_header(self, in_path, out_path, commands, deprecated_commands):
|
||||
"""Write the header file for the caller.h file"""
|
||||
with out_path.open('w') as fp:
|
||||
with in_path.open('r') as fp2:
|
||||
for line in fp2:
|
||||
if "THIS COMMENT TO BE REPLACED BY THE ACTUAL CODE (1)" in line:
|
||||
for command_name, command in commands.items():
|
||||
if 'duplicate_function' in command and command['duplicate_function']:
|
||||
continue
|
||||
fp.write(f'std::string {command["function_alias"]}(int action);\n')
|
||||
continue
|
||||
if "THIS COMMENT TO BE REPLACED BY THE ACTUAL CODE (2)" in line:
|
||||
map_string = ''
|
||||
for command_name, command in commands.items():
|
||||
map_string += f'{{"{command_name}", &Caller::{command["function_alias"]}}},'
|
||||
fp.write(map_string[:-1] + '\n')
|
||||
continue
|
||||
|
||||
if "THIS COMMENT TO BE REPLACED BY THE ACTUAL CODE (3)" in line:
|
||||
for key, value in deprecated_commands.items():
|
||||
fp.write(f'{{"{key}", "{value}"}},\n')
|
||||
continue
|
||||
|
||||
fp.write(line)
|
||||
|
||||
def write_infer_header(self, in_path, out_path, commands):
|
||||
"""Write the header file for the inferAction.h file"""
|
||||
with out_path.open('w+') as fp:
|
||||
with in_path.open('r') as fp2:
|
||||
for line in fp2:
|
||||
if "THIS COMMENT TO BE REPLACED BY THE ACTUAL CODE (1) - DO NOT REMOVE" in line:
|
||||
for command_name, command in commands.items():
|
||||
if 'duplicate_function' in command and command['duplicate_function']:
|
||||
continue
|
||||
|
||||
fp.write(f'int {command["function_alias"]}();\n')
|
||||
continue
|
||||
if "THIS COMMENT TO BE REPLACED BY THE ACTUAL CODE (2) - DO NOT REMOVE" in line:
|
||||
map_string = ''
|
||||
for command_name, command in commands.items():
|
||||
map_string += f'{{"{command_name}", &InferAction::{command["function_alias"]}}},'
|
||||
fp.write(map_string[:-1] + '\n')
|
||||
continue
|
||||
fp.write(line)
|
||||
|
||||
def write_infer_cpp(self, in_path, out_path, commands, non_dist, type_dist):
|
||||
"""Write the source file for the inferAction.cpp file"""
|
||||
with in_path.open('r') as fp2:
|
||||
for line in fp2:
|
||||
if "THIS COMMENT TO BE REPLACED BY THE ACTUAL CODE (1) - DO NOT REMOVE" in line:
|
||||
for command_name, command in commands.items():
|
||||
if 'duplicate_function' in command and command['duplicate_function']:
|
||||
continue
|
||||
|
||||
with function('int', f"InferAction::{command['function_alias']}", []) as f:
|
||||
if (command_name, -1) in non_dist| type_dist:
|
||||
self.write_line(
|
||||
f'throw RuntimeError("det is disabled for command: {command_name}. Use detg or detp");')
|
||||
elif not command['infer_action']:
|
||||
self.write_line('throw RuntimeError("infer_action is disabled");')
|
||||
else:
|
||||
checked_argcs = set()
|
||||
for action, action_params in command['actions'].items():
|
||||
for arg in action_params['args']:
|
||||
if arg['argc'] in checked_argcs:
|
||||
continue
|
||||
checked_argcs.add(arg['argc'])
|
||||
with if_block(f'args.size() == {arg["argc"]}'):
|
||||
# check if this argc is not distinguishable
|
||||
if (command_name, arg["argc"]) in non_dist | type_dist:
|
||||
self.write_line(
|
||||
f'throw RuntimeError("det is disabled for command: {command_name} with number of arguments {arg["argc"]}. Use detg or detp");')
|
||||
else:
|
||||
self.write_line(f'return {self.actions_dict[action]};')
|
||||
with else_block():
|
||||
self.write_line(
|
||||
'throw RuntimeError("Could not infer action: Wrong number of arguments");')
|
||||
continue
|
||||
self.write_line(line)
|
||||
|
||||
def write_check_arg(self):
|
||||
pass
|
||||
|
||||
def write_arg(self, args, action, command_name):
|
||||
for arg in args:
|
||||
if arg['argc'] != -1:
|
||||
if_block(f'args.size() == {arg["argc"]}',).__enter__()
|
||||
if 'pattern_command' in arg and arg['pattern_command']:
|
||||
self.write_line(f'int level = -1, iArg = 0, '
|
||||
f'nGetArgs = {arg["pattern_command"]["nGetArgs"]},'
|
||||
f' nPutArgs = {arg["pattern_command"]["nPutArgs"]};\nGetLevelAndUpdateArgIndex(action, '
|
||||
f'"{arg["pattern_command"]["command_name"]}", level, iArg, nGetArgs,nPutArgs);'
|
||||
)
|
||||
|
||||
if 'extra_variables' in arg:
|
||||
for var in arg['extra_variables']:
|
||||
codegen.write_line(f'{var["type"]} {var["name"]} = {var["value"]};')
|
||||
if 'separate_time_units' in arg and arg['separate_time_units']:
|
||||
self.write_line(f'std::string tmp_time({arg["separate_time_units"]["input"]});')
|
||||
self.write_line(f'std::string {arg["separate_time_units"]["output"][1]}'
|
||||
f' = RemoveUnit(tmp_time);')
|
||||
self.write_line(f'auto {arg["separate_time_units"]["output"][0]} = '
|
||||
f'StringTo < time::ns > (tmp_time,'
|
||||
f' {arg["separate_time_units"]["output"][1]});')
|
||||
if 'convert_to_time' in arg and arg['convert_to_time']:
|
||||
self.write_line(f'auto {arg["convert_to_time"]["output"]} = '
|
||||
f'StringTo < time::ns > ({", ".join(arg["convert_to_time"]["input"])});')
|
||||
input_arguments = []
|
||||
if 'exceptions' in arg:
|
||||
for exception in arg['exceptions']:
|
||||
self.write_line(f'if ({exception["condition"]}) {{ throw RuntimeError({exception["message"]}); }}')
|
||||
if 'check_det_id' in arg and arg['check_det_id']:
|
||||
self.write_line(
|
||||
f'if (det_id != -1) {{ throw RuntimeError("Cannot execute {command_name} at module level"); }} '
|
||||
)
|
||||
# only used for 3 commands :(
|
||||
if 'ctb_output_list' in arg:
|
||||
self.write_line(f"""
|
||||
std::string suffix = " {arg['ctb_output_list']['suffix']}";
|
||||
auto t = det->{arg['ctb_output_list']['GETFCNLIST']}();""")
|
||||
if arg['ctb_output_list']['GETFCNNAME'] != '':
|
||||
self.write_line(f"""
|
||||
auto names = det->{arg['ctb_output_list']['GETFCNNAME']}();
|
||||
auto name_it = names.begin();""")
|
||||
self.write_line("os << '[';")
|
||||
with if_block(f't.size() > 0'):
|
||||
self.write_line(f"""
|
||||
auto it = t.cbegin();
|
||||
os << ToString({arg['ctb_output_list']['printable_name']}) << ' ';
|
||||
os << OutString(det->{arg['ctb_output_list']['GETFCN']}(*it++, std::vector<int>{{det_id}}))<< suffix;
|
||||
while (it != t.cend()) {{
|
||||
os << ", " << ToString({arg['ctb_output_list']['printable_name']}) << ' ';
|
||||
os << OutString(det->{arg['ctb_output_list']['GETFCN']}(*it++, std::vector<int>{{det_id}}))<< suffix;
|
||||
}}
|
||||
""")
|
||||
self.write_line('os << "]\\n";')
|
||||
if arg['argc'] != -1:
|
||||
if_block().__exit__()
|
||||
|
||||
return
|
||||
|
||||
for i in range(len(arg['input'])):
|
||||
if arg['cast_input'][i]:
|
||||
self.write_line(
|
||||
f'auto arg{i} = StringTo<{arg["input_types"][i]}>({arg["input"][i]});')
|
||||
input_arguments.append(f'arg{i}')
|
||||
else:
|
||||
input_arguments.append(arg["input"][i])
|
||||
if 'require_det_id' in arg and arg['require_det_id']:
|
||||
if 'convert_det_id' in arg and arg['convert_det_id']:
|
||||
input_arguments.append("std::vector<int>{ det_id }")
|
||||
else:
|
||||
input_arguments.append("det_id")
|
||||
|
||||
input_arguments = ", ".join(input_arguments)
|
||||
# call function
|
||||
if arg["function"]:
|
||||
if arg['store_result_in_t']:
|
||||
self.write_line(f'auto t = det->{arg["function"]}({input_arguments});')
|
||||
else:
|
||||
self.write_line(f'det->{arg["function"]}({input_arguments});')
|
||||
else:
|
||||
pass #We have no function so skip block
|
||||
|
||||
output_args = []
|
||||
for output in arg['output']:
|
||||
output_args.append(output)
|
||||
if len(output_args) > 0:
|
||||
self.write_line(f"os << {'<< '.join(output_args)} << '\\n';")
|
||||
|
||||
if arg['argc'] != -1:
|
||||
if_block().__exit__()
|
||||
|
||||
|
||||
class if_block:
|
||||
def __init__(self, condition="", elseif=False):
|
||||
self.condition = condition
|
||||
self.elseif = elseif
|
||||
self.block = False
|
||||
|
||||
def __enter__(self):
|
||||
if self.elseif:
|
||||
codegen.write_line('else if (' + self.condition + ') {')
|
||||
else:
|
||||
codegen.write_line('if (' + self.condition + ') {')
|
||||
if self.block:
|
||||
codegen.write_line('{\n')
|
||||
|
||||
def __exit__(self, *args):
|
||||
codegen.write_line('}')
|
||||
if self.block:
|
||||
codegen.write_line('}')
|
||||
codegen.write_line('')
|
||||
|
||||
|
||||
class else_block:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def __enter__(self):
|
||||
codegen.write_line('else {')
|
||||
codegen.write_line('')
|
||||
|
||||
def __exit__(self, *args):
|
||||
codegen.write_line('}')
|
||||
codegen.write_line('')
|
||||
|
||||
|
||||
class for_block:
|
||||
def __init__(self, condition):
|
||||
self.condition = condition
|
||||
|
||||
def __enter__(self):
|
||||
codegen.write_line('for (' + self.condition + ') {')
|
||||
codegen.write_line('')
|
||||
|
||||
def __exit__(self, *args):
|
||||
codegen.write_line('}')
|
||||
codegen.write_line('')
|
||||
|
||||
|
||||
class function:
|
||||
def __init__(self, return_type, name, args: list[tuple[str, str]]):
|
||||
self.name = name
|
||||
self.args = args
|
||||
self.return_type = return_type
|
||||
|
||||
def __enter__(self):
|
||||
s = ""
|
||||
for arg in self.args:
|
||||
arg_type, arg_name = arg
|
||||
s += arg_type + ' ' + arg_name + ', '
|
||||
s = s[:-2]
|
||||
codegen.write_line(self.return_type + ' ' + self.name +
|
||||
f'({s}) {{')
|
||||
codegen.write_line('')
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
codegen.write_line('}')
|
||||
codegen.write_line('')
|
||||
|
||||
|
||||
codegen = CodeGenerator()
|
176
slsDetectorSoftware/generator/deprecated_commands.yaml
Normal file
176
slsDetectorSoftware/generator/deprecated_commands.yaml
Normal file
@ -0,0 +1,176 @@
|
||||
#configuration
|
||||
detectorversion: firmwareversion
|
||||
softwareversion: detectorserverversion
|
||||
receiverversion: rx_version
|
||||
detectornumber: serialnumber
|
||||
thisversion: clientversion
|
||||
detsizechan: detsize
|
||||
trimdir: settingspath
|
||||
settingsdir: settingspath
|
||||
flippeddatax: fliprows
|
||||
|
||||
#acquisition parameters
|
||||
cycles: triggers
|
||||
cyclesl: triggersl
|
||||
clkdivider: readoutspeed
|
||||
speed: readoutspeed
|
||||
vhighvoltage: highvoltage
|
||||
digitest: imagetest
|
||||
filter: filterresistor
|
||||
readnlines: readnrows
|
||||
|
||||
# temperature
|
||||
|
||||
# super old dacs
|
||||
vtr: vtrim
|
||||
vrf: vrpreamp
|
||||
vrs: vrshaper
|
||||
vcall: vcal
|
||||
vis: vishaper
|
||||
vshaper: vrshaper
|
||||
vpreamp: vrpreamp
|
||||
vshaperneg: vrshaper_n
|
||||
viinsh: vishaper
|
||||
vpl: vcal_n
|
||||
vph: vcal_p
|
||||
|
||||
# dacs
|
||||
vthreshold: dac
|
||||
vsvp: dac
|
||||
vsvn: dac
|
||||
vtrim: dac
|
||||
vrpreamp: dac
|
||||
vrshaper: dac
|
||||
vtgstv: dac
|
||||
vcmp_ll: dac
|
||||
vcmp_lr: dac
|
||||
vcal: dac
|
||||
vcmp_rl: dac
|
||||
vcmp_rr: dac
|
||||
rxb_rb: dac
|
||||
rxb_lb: dac
|
||||
vcp: dac
|
||||
vcn: dac
|
||||
vishaper: dac
|
||||
iodelay: dac
|
||||
vref_ds: dac
|
||||
vcascn_pb: dac
|
||||
vcascp_pb: dac
|
||||
vout_cm: dac
|
||||
vcasc_out: dac
|
||||
vin_cm: dac
|
||||
vref_comp: dac
|
||||
ib_test_c: dac
|
||||
vrshaper_n: dac
|
||||
vipre: dac
|
||||
vdcsh: dac
|
||||
vth1: dac
|
||||
vth2: dac
|
||||
vth3: dac
|
||||
vcal_n: dac
|
||||
vcal_p: dac
|
||||
vcassh: dac
|
||||
vcas: dac
|
||||
vicin: dac
|
||||
vipre_out: dac
|
||||
vref_h_adc: dac
|
||||
vb_comp_fe: dac
|
||||
vb_comp_adc: dac
|
||||
vcom_cds: dac
|
||||
vref_rstore: dac
|
||||
vb_opa_1st: dac
|
||||
vref_comp_fe: dac
|
||||
vcom_adc1: dac
|
||||
vref_prech: dac
|
||||
vref_l_adc: dac
|
||||
vref_cds: dac
|
||||
vb_cs: dac
|
||||
vb_opa_fd: dac
|
||||
vcom_adc2: dac
|
||||
vb_ds: dac
|
||||
vb_comp: dac
|
||||
vb_pixbuf: dac
|
||||
vin_com: dac
|
||||
vdd_prot: dac
|
||||
vbp_colbuf: dac
|
||||
vb_sda: dac
|
||||
vcasc_sfp: dac
|
||||
vipre_cds: dac
|
||||
ibias_sfp: dac
|
||||
|
||||
defaultdacs: resetdacs
|
||||
|
||||
#acquisition
|
||||
busy: clearbusy
|
||||
receiver: rx_status
|
||||
framescaught: rx_framescaught
|
||||
startingfnum: nextframenumber
|
||||
|
||||
#Network Configuration (Detector<->Receiver)
|
||||
detectorip: udp_srcip
|
||||
detectorip2: udp_srcip2
|
||||
detectormac: udp_srcmac
|
||||
detectormac2: udp_srcmac2
|
||||
rx_udpip: udp_dstip
|
||||
rx_udpip2: udp_dstip2
|
||||
rx_udpmac: udp_dstmac
|
||||
rx_udpmac2: udp_dstmac2
|
||||
rx_udpport: udp_dstport
|
||||
rx_udpport2: udp_dstport2
|
||||
flowcontrol_10g: flowcontrol10g
|
||||
txndelay_frame: txdelay_frame
|
||||
txndelay_left: txdelay_left
|
||||
txndelay_right: txdelay_right
|
||||
|
||||
#Receiver Config
|
||||
r_silent: rx_silent
|
||||
r_discardpolicy: rx_discardpolicy
|
||||
r_padding: rx_padding
|
||||
r_lock: rx_lock
|
||||
r_lastclient: rx_lastclient
|
||||
|
||||
#File
|
||||
fileformat: fformat
|
||||
outdir: fpath
|
||||
index: findex
|
||||
enablefwrite: fwrite
|
||||
masterfile: fmaster
|
||||
overwrite: foverwrite
|
||||
r_framesperfile: rx_framesperfile
|
||||
|
||||
#ZMQ Streaming Parameters (Receiver<->Client)
|
||||
r_readfreq: rx_zmqfreq
|
||||
rx_readfreq: rx_zmqfreq
|
||||
rx_datastream: rx_zmqstream
|
||||
|
||||
#Eiger Specific
|
||||
resmat: partialreset
|
||||
|
||||
#Jungfrau Specific
|
||||
storagecells: extrastoragecells
|
||||
auto_comp_disable: autocompdisable
|
||||
comp_disable_time: compdisabletime
|
||||
|
||||
#Gotthard Specific
|
||||
#Gotthard2 Specific
|
||||
#Mythen3 Specific
|
||||
#CTB Specific
|
||||
adc: slowadc
|
||||
flags: romode
|
||||
i_a: im_a
|
||||
i_b: im_b
|
||||
i_c: im_c
|
||||
i_d: im_d
|
||||
i_io: im_io
|
||||
|
||||
#Pattern
|
||||
#Moench
|
||||
|
||||
#Advanced
|
||||
copydetectorserver: updatedetectorserver
|
||||
|
||||
#Insignificant
|
||||
nframes: framecounter
|
||||
now: runtime
|
||||
timestamp: frametime
|
||||
frameindex: rx_frameindex
|
13726
slsDetectorSoftware/generator/extended_commands.yaml
Normal file
13726
slsDetectorSoftware/generator/extended_commands.yaml
Normal file
File diff suppressed because it is too large
Load Diff
276
slsDetectorSoftware/generator/gen_commands.py
Normal file
276
slsDetectorSoftware/generator/gen_commands.py
Normal file
@ -0,0 +1,276 @@
|
||||
import argparse
|
||||
import os
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
from autocomplete.autocomplete import type_info
|
||||
from cpp_codegen.codegen import codegen, if_block, for_block, function, else_block
|
||||
from infer_action.check_infer import check_infer
|
||||
from autocomplete.autocomplete import type_values
|
||||
|
||||
GEN_PATH = Path(__file__).parent
|
||||
|
||||
COMMANDS_PATH = GEN_PATH / 'extended_commands.yaml'
|
||||
DEPRECATED_COMMANDS_PATH = GEN_PATH / 'deprecated_commands.yaml'
|
||||
CPP_INPUT_PATH = GEN_PATH / 'Caller.in.cpp'
|
||||
HEADER_INPUT_PATH = GEN_PATH / 'Caller.in.h'
|
||||
CPP_OUTPUT_PATH = GEN_PATH.parent / 'src' / 'Caller.cpp'
|
||||
HEADER_OUTPUT_PATH = GEN_PATH.parent / 'src' / 'Caller.h'
|
||||
|
||||
INFER_HEADER_INPUT_PATH = GEN_PATH / 'inferAction.in.h'
|
||||
INFER_CPP_INPUT_PATH = GEN_PATH / 'inferAction.in.cpp'
|
||||
INFER_HEADER_OUTPUT_PATH = GEN_PATH.parent / 'src' / 'inferAction.h'
|
||||
INFER_CPP_OUTPUT_PATH = GEN_PATH.parent / 'src' / 'inferAction.cpp'
|
||||
|
||||
|
||||
def generate(
|
||||
commands_path=COMMANDS_PATH,
|
||||
cpp_input_path=CPP_INPUT_PATH,
|
||||
header_input_path=HEADER_INPUT_PATH,
|
||||
cpp_output_path=CPP_OUTPUT_PATH,
|
||||
header_output_path=HEADER_OUTPUT_PATH,
|
||||
infer_header_input_path=INFER_HEADER_INPUT_PATH,
|
||||
infer_cpp_input_path=INFER_CPP_INPUT_PATH,
|
||||
infer_header_output_path=INFER_HEADER_OUTPUT_PATH,
|
||||
infer_cpp_output_path=INFER_CPP_OUTPUT_PATH,
|
||||
|
||||
):
|
||||
commands_config = yaml.unsafe_load(commands_path.open('r'))
|
||||
deprecated_commands_config = yaml.unsafe_load(DEPRECATED_COMMANDS_PATH.open('r'))
|
||||
type_dist, non_dist = check_infer(commands=commands_config)
|
||||
|
||||
codegen.open(cpp_output_path)
|
||||
# write call function
|
||||
codegen.write_opening(cpp_input_path)
|
||||
|
||||
# iterate over the commands and generate code for each
|
||||
print(f"[X] found {len(commands_config)} commands")
|
||||
print('[*] generating code for commands')
|
||||
for command_name, command in commands_config.items():
|
||||
if 'is_description' in command and command['is_description']:
|
||||
continue
|
||||
with function('std::string', 'Caller::' + command['function_alias'], [('int', 'action')]) as fn:
|
||||
codegen.write_line('std::ostringstream os;')
|
||||
|
||||
# print help
|
||||
codegen.write_line('// print help')
|
||||
with if_block('action == slsDetectorDefs::HELP_ACTION'):
|
||||
if command["help"].startswith('code:'):
|
||||
codegen.write_line(command["help"].strip('code:'))
|
||||
else:
|
||||
codegen.write_line(f'os << "Command: {command_name}" << std::endl;')
|
||||
codegen.write_line(f'os << R"V0G0N({command["help"]} )V0G0N" << std::endl;')
|
||||
codegen.write_line('return os.str();')
|
||||
|
||||
# check if action and arguments are valid
|
||||
|
||||
codegen.write_line('// check if action and arguments are valid')
|
||||
first = True
|
||||
for action, action_params in command['actions'].items():
|
||||
|
||||
with if_block(f'action == {codegen.actions_dict[action]}', elseif=not first):
|
||||
|
||||
check_argc = True
|
||||
for arg in action_params['args']:
|
||||
if arg['argc'] == -1:
|
||||
check_argc = False
|
||||
break
|
||||
# check number of arguments
|
||||
condition = "1" if check_argc else "0"
|
||||
|
||||
if check_argc:
|
||||
for arg in action_params['args']:
|
||||
condition += f' && args.size() != {arg["argc"]}'
|
||||
|
||||
with if_block(condition):
|
||||
codegen.write_line(f'throw RuntimeError("Wrong number of arguments for action {action}");')
|
||||
|
||||
for arg in action_params['args']:
|
||||
if not check_argc:
|
||||
continue
|
||||
with if_block(f'args.size() == {arg["argc"]}'):
|
||||
# check argument types
|
||||
if 'extra_variables' in arg:
|
||||
for var in arg['extra_variables']:
|
||||
codegen.write_line(f'{var["type"]} {var["name"]} = {var["value"]};')
|
||||
|
||||
if 'separate_time_units' in arg and arg['separate_time_units']:
|
||||
codegen.write_line(f'try {{')
|
||||
# TODO: refactor this repeating code
|
||||
codegen.write_line(f'std::string tmp_time({arg["separate_time_units"]["input"]});')
|
||||
codegen.write_line(f'std::string {arg["separate_time_units"]["output"][1]}'
|
||||
f' = RemoveUnit(tmp_time);')
|
||||
codegen.write_line(f'auto {arg["separate_time_units"]["output"][0]} = '
|
||||
f'StringTo < time::ns > (tmp_time,'
|
||||
f' {arg["separate_time_units"]["output"][1]});')
|
||||
codegen.write_line(
|
||||
f'}} catch (...) {{ throw RuntimeError("Could not convert argument to time::ns");}}')
|
||||
|
||||
elif 'convert_to_time' in arg and arg['convert_to_time']:
|
||||
codegen.write_line(f'try {{')
|
||||
|
||||
codegen.write_line(
|
||||
f'StringTo < time::ns > ({", ".join(arg["convert_to_time"]["input"])});')
|
||||
codegen.write_line(
|
||||
f'}} catch (...) {{ throw RuntimeError("Could not convert arguments to time::ns");}}')
|
||||
|
||||
for i in range(len(arg['input'])):
|
||||
if not arg['cast_input'][i]:
|
||||
continue
|
||||
codegen.write_line(f'try {{')
|
||||
codegen.write_line(f'StringTo<{arg["input_types"][i]}>({arg["input"][i]});')
|
||||
codegen.write_line(f'}} catch (...) {{')
|
||||
codegen.write_line(
|
||||
f' throw RuntimeError("Could not convert argument {i} to {arg["input_types"][i]}");')
|
||||
codegen.write_line(f'}}')
|
||||
first = False
|
||||
with else_block():
|
||||
codegen.write_line(
|
||||
f'throw RuntimeError("INTERNAL ERROR: Invalid action: supported actions are {list(command["actions"].keys())}");')
|
||||
|
||||
# generate code for each action
|
||||
codegen.write_line('// generate code for each action')
|
||||
for action, action_params in command['actions'].items():
|
||||
if 'detectors' in action_params:
|
||||
codegen.write_line('auto detector_type = det->getDetectorType().squash();')
|
||||
|
||||
with if_block(f'action == {codegen.actions_dict[action]}'):
|
||||
if 'detectors' in action_params:
|
||||
first = True
|
||||
for detector, detector_params in action_params['detectors'].items():
|
||||
with if_block(f'detector_type == defs::{detector}', elseif=not first):
|
||||
codegen.write_arg(detector_params, action, command_name)
|
||||
|
||||
else_block().__enter__()
|
||||
|
||||
if not action_params:
|
||||
codegen.write_line(f'throw RuntimeError("detector not supported for action: {action}");')
|
||||
else:
|
||||
tmp_args = []
|
||||
if 'args' in action_params:
|
||||
tmp_args = action_params['args']
|
||||
codegen.write_arg(tmp_args, action, command_name)
|
||||
|
||||
if 'detectors' in action_params:
|
||||
else_block().__exit__()
|
||||
|
||||
codegen.write_line('return os.str();')
|
||||
|
||||
# close sls namespace
|
||||
codegen.write_closing()
|
||||
codegen.close()
|
||||
print('[X] .cpp code generated')
|
||||
deprecated_commands = []
|
||||
codegen.write_header(header_input_path, header_output_path, commands_config, deprecated_commands_config)
|
||||
print('[X] header code generated')
|
||||
|
||||
|
||||
codegen.write_infer_header(infer_header_input_path, infer_header_output_path, commands_config) #TODO: add deprecated commands
|
||||
print('[X] infer header code generated')
|
||||
codegen.open(infer_cpp_output_path)
|
||||
|
||||
codegen.write_infer_cpp(infer_cpp_input_path, infer_cpp_output_path, commands_config, non_dist, type_dist)
|
||||
codegen.close()
|
||||
print('[X] infer cpp code generated')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(
|
||||
description='generate c++ code for cli commands from the commands.yaml file',
|
||||
)
|
||||
parser.add_argument('-f', '--format', action='store_true', default=False, dest='format',
|
||||
help='format header and cpp file using clang-format')
|
||||
parser.add_argument('-p', '--parse', action='store_true', default=False, dest='parse',
|
||||
help='parse the commands.yaml file into extended_commands.yaml')
|
||||
parser.add_argument('-c', '--check', action='store_true', default=False, dest='check',
|
||||
help='check missing commands')
|
||||
parser.add_argument('-g', '--generate', action='store_true', default=False, dest='generate', help='generate code (C++ or bash if -a is used)')
|
||||
parser.add_argument('-a', '--autocomplete', action='store_true', default=False, dest='autocomplete',
|
||||
help='print bash autocomplete values')
|
||||
cli_args = parser.parse_args()
|
||||
|
||||
if cli_args.autocomplete:
|
||||
from autocomplete.autocomplete import generate_type_values, generate_bash_autocomplete
|
||||
if cli_args.generate:
|
||||
generate_bash_autocomplete()
|
||||
print('[X] bash autocomplete generated')
|
||||
generate_bash_autocomplete(
|
||||
output_path=Path(__file__).parent / 'autocomplete' / 'zsh_autocomplete.sh',
|
||||
input_path=Path(__file__).parent / 'autocomplete' / 'zsh_autocomplete.in.sh'
|
||||
)
|
||||
print('[X] zsh autocomplete generated')
|
||||
exit(0)
|
||||
else:
|
||||
ret = generate_type_values()
|
||||
print(ret)
|
||||
exit(0)
|
||||
|
||||
if cli_args.check:
|
||||
from commands_parser.commands_parser import command_parser
|
||||
|
||||
commands_config = yaml.unsafe_load(COMMANDS_PATH.open('r'))
|
||||
|
||||
# infer action based on number of arguments and types
|
||||
type_dist, non_dist = check_infer(commands=commands_config)
|
||||
|
||||
command_parser.verify_format()
|
||||
command_parser.parse_all_commands()
|
||||
# generate list of commands found in sls_detector_get
|
||||
glist_path = GEN_PATH / 'glist'
|
||||
ret = subprocess.run([f"sls_detector_get list | tail -n +2 | sort > {glist_path.absolute()}"], shell=True,
|
||||
capture_output=True, check=True)
|
||||
if ret.stderr != b'':
|
||||
print('[!] glist generation failed and glist not found')
|
||||
exit(1)
|
||||
|
||||
if not COMMANDS_PATH.exists():
|
||||
print('[!] extended_commands.yaml not found')
|
||||
exit(1)
|
||||
detglist = set(command['command_name'] for __, command in commands_config.items())
|
||||
detglist.add('free')
|
||||
detglist.add('list')
|
||||
|
||||
g_path = GEN_PATH / 'glist'
|
||||
if not g_path.exists():
|
||||
print('[!] glist not found')
|
||||
exit(1)
|
||||
glist = set(g_path.read_text().split('\n'))
|
||||
if "" in glist:
|
||||
glist.remove("")
|
||||
if "" in detglist:
|
||||
detglist.remove("")
|
||||
|
||||
not_found = set()
|
||||
for command in glist:
|
||||
if command not in detglist:
|
||||
not_found.add(command)
|
||||
print()
|
||||
if len(not_found) > 0:
|
||||
print(f'[!] found {len(not_found)} missing')
|
||||
print(f"not_found: {not_found}")
|
||||
else:
|
||||
print(f'[X] found no missing commands')
|
||||
|
||||
for command in detglist:
|
||||
if command not in glist:
|
||||
print(f'[!] command {command} found in commands.yaml but not found in g list')
|
||||
|
||||
exit(0)
|
||||
|
||||
if cli_args.parse:
|
||||
from commands_parser.commands_parser import command_parser
|
||||
|
||||
command_parser.verify_format()
|
||||
command_parser.parse_all_commands()
|
||||
|
||||
if cli_args.generate:
|
||||
generate()
|
||||
|
||||
if cli_args.format:
|
||||
files = [CPP_OUTPUT_PATH, HEADER_OUTPUT_PATH, INFER_HEADER_OUTPUT_PATH, INFER_CPP_OUTPUT_PATH]
|
||||
for file in files:
|
||||
os.system(f'clang-format -i {file.absolute()}')
|
||||
#os.system(f'clang-format -i --style="{{Standard: C++11}}" {file.absolute()}')
|
||||
print('[X] code formatted')
|
19
slsDetectorSoftware/generator/inferAction.in.cpp
Normal file
19
slsDetectorSoftware/generator/inferAction.in.cpp
Normal file
@ -0,0 +1,19 @@
|
||||
#include "inferAction.h"
|
||||
#include "sls/sls_detector_defs.h"
|
||||
namespace sls {
|
||||
|
||||
int InferAction::infer(sls::CmdParser &parser, std::ostream &os) {
|
||||
args = parser.arguments();
|
||||
cmd = parser.command();
|
||||
auto it = functions.find(parser.command());
|
||||
if (it != functions.end()) {
|
||||
return ((*this).*(it->second))();
|
||||
} else {
|
||||
throw RuntimeError("det not implemented for command: " +
|
||||
parser.command());
|
||||
}
|
||||
}
|
||||
|
||||
// THIS COMMENT TO BE REPLACED BY THE ACTUAL CODE (1) - DO NOT REMOVE
|
||||
|
||||
} // namespace sls
|
30
slsDetectorSoftware/generator/inferAction.in.h
Normal file
30
slsDetectorSoftware/generator/inferAction.in.h
Normal file
@ -0,0 +1,30 @@
|
||||
#include "CmdParser.h"
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
namespace sls {
|
||||
class InferAction {
|
||||
public:
|
||||
InferAction() {}
|
||||
int infer(sls::CmdParser &parser, std::ostream &os = std::cout);
|
||||
std::vector<std::string> args;
|
||||
std::string cmd;
|
||||
|
||||
// generated functions
|
||||
// THIS COMMENT TO BE REPLACED BY THE ACTUAL CODE (1) - DO NOT REMOVE
|
||||
// int frames();
|
||||
|
||||
private:
|
||||
using FunctionMap = std::map<std::string, int (InferAction::*)()>;
|
||||
FunctionMap functions{
|
||||
// generated functions
|
||||
|
||||
// THIS COMMENT TO BE REPLACED BY THE ACTUAL CODE (2) - DO NOT REMOVE
|
||||
|
||||
// {"frames",&InferAction::frames}
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace sls
|
64
slsDetectorSoftware/generator/infer_action/check_infer.py
Normal file
64
slsDetectorSoftware/generator/infer_action/check_infer.py
Normal file
@ -0,0 +1,64 @@
|
||||
from pathlib import Path
|
||||
|
||||
import argparse
|
||||
import yaml
|
||||
|
||||
|
||||
def check_infer(EXTENDED_COMMANDS_PATH=Path(__file__).parent.parent / "extended_commands.yaml", commands=None):
|
||||
if commands is None:
|
||||
# load yaml file
|
||||
with EXTENDED_COMMANDS_PATH.open('r') as f:
|
||||
commands = yaml.safe_load(f)
|
||||
|
||||
type_distinguishable = {}
|
||||
non_distinguishable = {}
|
||||
|
||||
for command_name, command in commands.items():
|
||||
# todo: remove this (added for debug)
|
||||
# if command_name != 'badchannels':
|
||||
# continue
|
||||
if len(command["actions"]) == 1:
|
||||
action = list(command["actions"].items())[0][1]
|
||||
for arg in action['args']:
|
||||
if arg['argc'] == -1:
|
||||
non_distinguishable[(command_name, arg['argc'])] = ([], arg['arg_types'])
|
||||
continue
|
||||
|
||||
|
||||
get_argcs = {}
|
||||
get_args = command['actions']['GET']['args']
|
||||
for arg in get_args:
|
||||
if arg['argc'] != -1:
|
||||
get_argcs[arg["argc"]] = arg['arg_types']
|
||||
else:
|
||||
non_distinguishable[(command_name, arg['argc'])] = ([], arg['arg_types'])
|
||||
put_args = command['actions']['PUT']['args']
|
||||
for arg in put_args:
|
||||
if arg['argc'] == -1:
|
||||
non_distinguishable[(command_name, arg['argc'])] = ([], arg['arg_types'])
|
||||
elif arg['argc'] in get_argcs:
|
||||
if arg['arg_types'] != get_argcs[arg['argc']]:
|
||||
type_distinguishable[(command_name, arg['argc'])] = (get_argcs[arg['argc']], arg['arg_types'])
|
||||
else:
|
||||
non_distinguishable[(command_name, arg['argc'])] = (get_argcs[arg['argc']], arg['arg_types'])
|
||||
|
||||
return type_distinguishable, non_distinguishable
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
argparse = argparse.ArgumentParser()
|
||||
argparse.add_argument("--print", choices=['all', 'type', 'impossible'], default="all", help="command name")
|
||||
args = argparse.parse_args()
|
||||
|
||||
type_distinguishable, non_distinguishable = check_infer()
|
||||
if args.print == 'type' or args.print == 'all':
|
||||
print("type distinguishable:")
|
||||
print("command_name: argc get_arg_type put_arg_type\n")
|
||||
for (command_name, argc), (get_arg_types, put_arg_types) in type_distinguishable.items():
|
||||
print(f"{command_name}: {argc} {get_arg_types} {put_arg_types}")
|
||||
|
||||
if args.print == 'impossible' or args.print == 'all':
|
||||
print("\n\nimpossible to distinguish:")
|
||||
print("command_name: argc get_arg_type put_arg_type")
|
||||
for (command_name, argc), (get_arg_types, put_arg_types) in non_distinguishable.items():
|
||||
print(f"{command_name}: {argc} {get_arg_types} {put_arg_types}")
|
288
slsDetectorSoftware/generator/readme.md
Normal file
288
slsDetectorSoftware/generator/readme.md
Normal file
@ -0,0 +1,288 @@
|
||||
# Generator
|
||||
used to generate C++ cli commands. and bash autocompletion scripts.
|
||||
## Autocomplete
|
||||
|
||||
### Overview
|
||||
|
||||
Looks through the `dump.json` file for the different values of an enum and stores them in the dictionary `type_values`.
|
||||
```sh
|
||||
# To print the different values for enums
|
||||
python gen_commands.py -a
|
||||
```
|
||||
|
||||
also the autocomplete.py generates shell autocompletion scripts for both bash and zsh. It uses the template file `bash_autocomplete.in.sh` and adds the necessary code in an output file `bash_autocomplete.sh` (same for zsh).
|
||||
To use the bash autocompletion the `bash_autocomplete.sh` must be sourced.
|
||||
|
||||
```sh
|
||||
source bash_autocomplete.sh
|
||||
# g <Tab><Tab> will give the list of all commands
|
||||
# p 0:<Tab><Tab> will also give the list of all commands
|
||||
# g exp<Tab> will autocomplete to g exptime
|
||||
# g exptime <Tab><Tab> will return "s ms us ns"
|
||||
# p timing <Tab><Tab> will return "auto,burst_trigger,gating..."
|
||||
```
|
||||
|
||||
**Note:**
|
||||
The dump.json is the AST of the file `slsDetectorPackage/slsSupportLib/src/ToString.cpp`.
|
||||
|
||||
```sh
|
||||
# to generate the dump.json file
|
||||
cd slsSupportLib/src/ToString.cpp
|
||||
clang++ -Xclang -ast-dump=json -Xclang -ast-dump-filter -Xclang StringTo -c ToString.cpp -I ../include/ -std=gnu++11
|
||||
# clang version used: 14.0.0-1ubuntu1.1
|
||||
```
|
||||
|
||||
the `dump.json` file produced by clang is not a correct json file because we used the `-ast-dump-filter`. autocomplete.py can be used to fix the format of `dump.json` and produce a new file called `fixed.json` that is json format.
|
||||
```
|
||||
# to convert dump.json into correct json format.
|
||||
python autocomplete.py -f # produces the file fixed.json
|
||||
```
|
||||
|
||||
### Code components
|
||||
|
||||
- `type_values` is a dictionary that contains the different values for commands args. It is populated with enum values that stard with defs:: or slsDetectorDefs:: (eg. defs::burstMode). Also it contains values added manually such as "mv,mV" and those start with special::
|
||||
- `generate_type_values` parses the AST file to find the part that contains if statements. and extracts the different possible values for an enum.
|
||||
- `generate_bash_autocomplete` generates autocompletion scripts for bash or zsh. the difference between zsh and bash scripts can be found in the template files. (bash handles 0:<Tab><Tab> completion differently than zsh more details can be found in the comments of the template files )
|
||||
- `fix_json` fixes the file 'autocomplete/dump.json' and outputs a new corrected file in 'autocomplete/fixed.json'
|
||||
|
||||
## Command Parser
|
||||
|
||||
Definitely the most important component of all the generator module.
|
||||
|
||||
command_parser exist to keep the commands.yaml file concise and easy to read and produce a complete version of the commands.yaml for the code generator to work on.
|
||||
|
||||
The goal is that the code generator works on a version of commands.yaml that is easy to iterate over and generate code.
|
||||
```
|
||||
# complete version
|
||||
some_command:
|
||||
help: "do this"
|
||||
infer_action: true
|
||||
actions:
|
||||
GET:
|
||||
args:
|
||||
- argc: 0
|
||||
function: "getCommand"
|
||||
...
|
||||
- argc: 1
|
||||
function: "getCommand"
|
||||
...
|
||||
detectors:
|
||||
MYTHEN3:
|
||||
- argc: 0
|
||||
function: "getCommandMythen"
|
||||
...
|
||||
PUT:
|
||||
args:
|
||||
- argc: 2
|
||||
function: "setCommand"
|
||||
...
|
||||
- argc: 3
|
||||
function: "setCommand"
|
||||
...
|
||||
|
||||
```
|
||||
|
||||
the complete vesion can only have `args` or `detectors` field inside an action (GET or PUT). **Each element in the args array have a different argc**. and in each element in the args array we can find all the information needed to generate the code for that one argc case. for example in the code `if(action == 'GET')` and `if (args.size() == 1)` then we can generate the code for that one case independetly.
|
||||
|
||||
|
||||
commands.yaml has a lot on ~inheritance~. examples show best what it is:
|
||||
|
||||
> fields insides an action will be passed to args and detectors
|
||||
|
||||
> the extended args for default actions will be used for detectors
|
||||
|
||||
> any field can be overriden
|
||||
|
||||
```
|
||||
resetdacs:
|
||||
help: "[(optional) hard] ..."
|
||||
actions:
|
||||
PUT:
|
||||
function: resetToDefaultDacs
|
||||
require_det_id: true
|
||||
output: [ '"successful"' ]
|
||||
input_types: [ bool ]
|
||||
args:
|
||||
- argc: 1
|
||||
arg_types: [ special::hard ]
|
||||
input: [ '"1"' ]
|
||||
- argc: 0
|
||||
input: [ '"0"' ]
|
||||
|
||||
# this will be converted to
|
||||
|
||||
resetdacs:
|
||||
actions:
|
||||
PUT:
|
||||
args:
|
||||
- arg_types:
|
||||
- special::hard
|
||||
argc: 1
|
||||
cast_input:
|
||||
- false
|
||||
check_det_id: false
|
||||
convert_det_id: true
|
||||
function: resetToDefaultDacs
|
||||
input:
|
||||
- '"1"'
|
||||
input_types:
|
||||
- bool
|
||||
output:
|
||||
- '"successful"'
|
||||
require_det_id: true
|
||||
store_result_in_t: false
|
||||
- arg_types: []
|
||||
argc: 0
|
||||
cast_input:
|
||||
- false
|
||||
check_det_id: false
|
||||
convert_det_id: true
|
||||
function: resetToDefaultDacs
|
||||
input:
|
||||
- '"0"'
|
||||
input_types:
|
||||
- bool
|
||||
output:
|
||||
- '"successful"'
|
||||
require_det_id: true
|
||||
store_result_in_t: false
|
||||
command_name: resetdacs
|
||||
function_alias: resetdacs
|
||||
help: "[(optional) hard] ..."
|
||||
infer_action: true
|
||||
|
||||
```
|
||||
|
||||
command_parser does not have a specific schema for the commands.yaml this is by design so it can be very extensible and future-proof. This also can have problems when there is typos (writing intput instead of input...)
|
||||
|
||||
command_parser first verifies the commands.yaml and checks if there's some obvious problems in it.
|
||||
|
||||
templates found in commands.yaml were taken from the CmdProxy code they were added for debugging purposes when writing the generator.
|
||||
|
||||
|
||||
tricky things:
|
||||
--
|
||||
- if input array has n elements and cast_input array is empty. command_parser will fill it with n false values.
|
||||
- store_result_in_t will be added by default as true to GET action. but as false to PUT action. (unless it is written in the action)
|
||||
- infer_action by default is true
|
||||
- commands that have is_description true won't be verified
|
||||
- function_alias is the name of the function in the c++ code. by default it is the command name. errors came up with the command virtual as virtual is a reserved keyword in c++
|
||||
- command_name is the string of the command that will be typed in cli. (frames, exptime, ...). by default it is the command name. pattern is a special keyword in yaml. problems came up with the command pattern
|
||||
- arg_types is by default input_types unless otherwise specified
|
||||
- when the parent has specific detector behaviour and the child does not. writing an empty detector section in the action would not inherit any detector specific fields (check exptime1)
|
||||
- commands can inherit other commands (check exptime1 and exptime2)
|
||||
- argc: -1 means that the command has an unknown number of arguments
|
||||
|
||||
|
||||
### Code Walkthrough
|
||||
the code is well commented it is well explained in the script
|
||||
|
||||
### Tests
|
||||
tests for command_parser can be found in `generator/tests/command_parser/`
|
||||
```
|
||||
pip install -r requirements.txt
|
||||
python -m pytests
|
||||
```
|
||||
|
||||
verification is not well tested
|
||||
|
||||
|
||||
|
||||
## codegen
|
||||
|
||||
Now for C++ code generation. After parsing the commands.yaml file and producing the extended_commands.yaml `gen_commands.py` will iterate over the commands and generate `Caller.h`, `Caller.cpp`, `inferAction.cpp` and `inferAction.h` .
|
||||
|
||||
### infer action
|
||||
|
||||
the generated code will produce 5 new targets: "detg detp deta deth det"
|
||||
|
||||
`detg` will set the action as GET
|
||||
`detp` will the action as PUT
|
||||
|
||||
`det` will guess the action depending on the number of arguments
|
||||
|
||||
the codegen module will generate a function for every command that will return the action based on the number of arguments
|
||||
|
||||
```cpp
|
||||
int InferAction::activate() {
|
||||
|
||||
if (args.size() == 0) {
|
||||
return slsDetectorDefs::GET_ACTION;
|
||||
}
|
||||
|
||||
if (args.size() == 1) {
|
||||
return slsDetectorDefs::PUT_ACTION;
|
||||
} else {
|
||||
|
||||
throw RuntimeError("Could not infer action: Wrong number of arguments");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
the `inferAction` class will be called from `CmdApp.cpp` to infer the action and the command function will be called with the appropriate action.
|
||||
|
||||
some commands have the same number of argument count for both get and put. These commands can be found using the the `check_infer.py` script. in the generated code it will say that "det is disabled"
|
||||
```bash
|
||||
# to see these commands
|
||||
python infer_action/check_infer.py
|
||||
```
|
||||
|
||||
### Caller.cpp code
|
||||
|
||||
in this level we only use the extended_commands.yaml file.
|
||||
the `generate()` function in `gen_commands.py` will iterate over all of the commands and :
|
||||
- write the function signature
|
||||
- write the help
|
||||
- write c++ code to check the inputs: check argument count and check if we are able to convert the arguments into the required types
|
||||
- iterate over actions and arguments
|
||||
- iterate over the detectors and write code for each one of them (if mythen3 ... if eiger ... else default code... ) and call `codegen.write_arg()` to write the argument for a single argument
|
||||
|
||||
codegen.write_arg()
|
||||
-
|
||||
write_arg in codegen reads the argument fields and generate c++ code accordingly.
|
||||
|
||||
## fields explanations
|
||||
- arg_types:[array of types] it is only used for autocompletion no C++ code is dependent on it
|
||||
- is_description:[boolean] same as above
|
||||
- template:[boolean] only used in commands.yaml and it won't present in extended_commands.yaml. it is inspired by the CmdProxy.h code
|
||||
- help:[string] command help
|
||||
- input:[array of variable names] the input arguments that will be passed to the function
|
||||
- input_types:[array of types] the types of the input arguments given to the function
|
||||
- cast_input:[array of boolean] if true it will cast the corresponding input to the type in input_types
|
||||
- output:[array] outputs that will be printed (eg. ["123", "'a'"] will be os<<123<<'a')
|
||||
- function: the function that will be called
|
||||
- function_alias: the name of the function in the c++ code (more on it in tricky things)
|
||||
- command_name: the string of the command that will be typed in cli. (more on it in tricky things)
|
||||
- require_det_id: if true it will require a detector id to be passed as the last argument
|
||||
- check_det_id: if true it will check the detector id and throw an error if it is not valid
|
||||
- convert_det_id: if true it will convert the detector id to the correct type `std::vector<int>{ det_id }`
|
||||
- store_result_in_t: if true it will store the result of the function in the variable t (more on it in tricky things)
|
||||
- infer_action: if true it will infer the action (only if det is used)
|
||||
- detectors: the detectors that have specific behaviour
|
||||
- args: the arguments of the command
|
||||
- argc: the number of arguments
|
||||
- extra_variables[array]: each element takes three parameters: value, name, type and creates that variable in the beginning of the argument code
|
||||
- exceptions[array]: each element takes two parameters: condition, message
|
||||
- pattern_command: takes three arguments: nGetArgs, nPutArgs and command_name and it will write this code
|
||||
```cpp
|
||||
int level = -1, iArg = 0, nGetArgs = $nGetArgs$, nPutArgs = $nPutArgs$;
|
||||
GetLevelAndUpdateArgIndex(action, $command_name$, level, iArg, nGetArgs,nPutArgs);
|
||||
```
|
||||
- separate_time_units: takes three parameters: input, output[0], output[1] each one is a variable name
|
||||
```cpp
|
||||
std::string tmp_time($input$);
|
||||
std::string $output[1]$ = RemoveUnit(tmp_time);
|
||||
auto $output[0]$ = StringTo<time::ns>(tmp_time, $output[1]$);
|
||||
```
|
||||
- convert_to_time: takes three parameters: input[0], input[1], output
|
||||
```cpp
|
||||
auto output = StringTo<time::ns>(input[0], input[1]);
|
||||
```
|
||||
- ctb_output_list: **maybe it should be removed?** takes 5 parameters: GETFCNLIST, GETFCNNAME, GETFCN, suffix, printable_name
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
6
slsDetectorSoftware/generator/requirements.txt
Normal file
6
slsDetectorSoftware/generator/requirements.txt
Normal file
@ -0,0 +1,6 @@
|
||||
coverage==7.3.1
|
||||
iniconfig==2.0.0
|
||||
packaging==23.1
|
||||
pluggy==1.3.0
|
||||
pytest==7.4.2
|
||||
PyYAML==6.0.1
|
@ -0,0 +1,16 @@
|
||||
basic:
|
||||
infer_action: false
|
||||
help: "xx11"
|
||||
actions:
|
||||
GET:
|
||||
function: 'func1'
|
||||
output: [ OutString(t) ]
|
||||
args:
|
||||
- argc: 0
|
||||
PUT:
|
||||
function: 'func2'
|
||||
output: [ 'args.front()' ]
|
||||
input: [ 'args[0]' ]
|
||||
input_types: [ int ]
|
||||
cast_input: [ true ]
|
||||
argc: 1
|
@ -0,0 +1,66 @@
|
||||
---
|
||||
template:
|
||||
infer_action: false
|
||||
help: ""
|
||||
actions:
|
||||
GET:
|
||||
function: 'func1'
|
||||
args:
|
||||
- argc: 0
|
||||
output: [ OutString(t) ]
|
||||
PUT:
|
||||
function: 'func2'
|
||||
output: [ 'args.front()' ]
|
||||
input: [ 'args[0]' ]
|
||||
input_types: [ int ]
|
||||
cast_input: [ true ]
|
||||
argc: 1
|
||||
|
||||
basic:
|
||||
help: "xx11"
|
||||
inherit_actions: template
|
||||
actions:
|
||||
GET:
|
||||
function: 'x'
|
||||
argc: 2
|
||||
args:
|
||||
- check_det_id: true
|
||||
|
||||
|
||||
template2:
|
||||
infer_action: false
|
||||
template: true
|
||||
help: ""
|
||||
actions:
|
||||
GET:
|
||||
convert_to_time:
|
||||
input: [ 'args[0]', 'args[1]' ]
|
||||
output: converted_time
|
||||
separate_time_units:
|
||||
input: 'args[0]'
|
||||
output: [ converted_time, unit ]
|
||||
function: 'func1'
|
||||
output: [ OutString(t) ]
|
||||
args:
|
||||
- argc: 0
|
||||
- argc: 99
|
||||
PUT:
|
||||
function: funcTemplatePUT
|
||||
args:
|
||||
- argc: 19
|
||||
function: 'func19'
|
||||
- argc: 91
|
||||
|
||||
basic2:
|
||||
inherit_actions: template2
|
||||
actions:
|
||||
GET:
|
||||
function: 'x'
|
||||
argc: 2
|
||||
args:
|
||||
- check_det_id: true
|
||||
input: [ 'args[0]', a,b,c ]
|
||||
input_types: [ int, int, int, int ]
|
||||
PUT:
|
||||
function: 'y'
|
||||
|
@ -0,0 +1,208 @@
|
||||
basic:
|
||||
infer_action: false
|
||||
help: "xx11"
|
||||
actions:
|
||||
GET:
|
||||
function: 'func1'
|
||||
output: [ OutString(t) ]
|
||||
args:
|
||||
- argc: 0
|
||||
- argc : 1
|
||||
output: [ testytest ]
|
||||
detectors:
|
||||
MYTHEN3:
|
||||
function: 'do_mythen3'
|
||||
CHIPTESTBOARD:
|
||||
args:
|
||||
- argc: 55
|
||||
output: [ ctbOutput ]
|
||||
PUT:
|
||||
detectors:
|
||||
EIGER:
|
||||
function: 'do_eiger'
|
||||
argc: 99
|
||||
output: [ eigerOutput ]
|
||||
|
||||
|
||||
# classes of tests:
|
||||
# classes of template tests: has args or has detectors => 4 cases noted (0,0) ... (1,1)
|
||||
# classes of childs: has args or has detectors => 4 cases noted (0,0) ... (1,1)
|
||||
# => 16 cases
|
||||
# example: case_0111: template (0,1) and child (1,1)
|
||||
#################### exhaustive testing over chosen classes of tests
|
||||
|
||||
template_01:
|
||||
infer_action: true
|
||||
help: "vv12"
|
||||
template: true
|
||||
|
||||
actions:
|
||||
GET:
|
||||
detectors:
|
||||
MYTHEN3:
|
||||
function: 'do_mythen3'
|
||||
argc: 99
|
||||
CHIPTESTBOARD:
|
||||
function: 'do_ctb'
|
||||
argc: 98
|
||||
|
||||
case_0100:
|
||||
inherit_actions: template_01
|
||||
help: "0100"
|
||||
|
||||
case_0101:
|
||||
inherit_actions: template_01
|
||||
help: "0101"
|
||||
actions:
|
||||
GET:
|
||||
function: 'get_function'
|
||||
detectors:
|
||||
MYTHEN3:
|
||||
function: 'do_mythen23'
|
||||
argc: 420
|
||||
|
||||
case_0110:
|
||||
inherit_actions: template_01
|
||||
help: "0110"
|
||||
actions:
|
||||
GET:
|
||||
argc: 111
|
||||
function: 'get_function'
|
||||
|
||||
case_0110v2:
|
||||
inherit_actions: template_01
|
||||
help: "0110v2"
|
||||
actions:
|
||||
GET:
|
||||
args:
|
||||
- argc: 111
|
||||
function: 'get_function'
|
||||
|
||||
|
||||
case_0111:
|
||||
inherit_actions: template_01
|
||||
help: "0111"
|
||||
actions:
|
||||
GET:
|
||||
args:
|
||||
- argc: 111
|
||||
function: 'get_function'
|
||||
detectors:
|
||||
MYTHEN3:
|
||||
function: 'do_mythen23'
|
||||
argc: 420
|
||||
|
||||
##### cases 10** tests
|
||||
template_10:
|
||||
template: true
|
||||
actions:
|
||||
GET:
|
||||
args:
|
||||
- argc: 0
|
||||
- argc : 1
|
||||
output: [ testytest ]
|
||||
|
||||
case_1000:
|
||||
inherit_actions: template_10
|
||||
help: "1000"
|
||||
|
||||
case_1001:
|
||||
inherit_actions: template_10
|
||||
help: "1001"
|
||||
actions:
|
||||
GET:
|
||||
detectors:
|
||||
MYTHEN3:
|
||||
args:
|
||||
- function: 'do_mythen23'
|
||||
argc: 420
|
||||
- function: 'do_mythen3'
|
||||
argc: 99
|
||||
|
||||
|
||||
|
||||
case_1010:
|
||||
inherit_actions: template_10
|
||||
help: "1010"
|
||||
actions:
|
||||
GET:
|
||||
args:
|
||||
- argc: 111
|
||||
function: 'get_function'
|
||||
|
||||
case_1011:
|
||||
inherit_actions: template_10
|
||||
help: "1011"
|
||||
actions:
|
||||
GET:
|
||||
args:
|
||||
- argc: 111
|
||||
function: 'get_function'
|
||||
detectors:
|
||||
MYTHEN3:
|
||||
function: 'do_mythen23'
|
||||
|
||||
##### cases 11** tests
|
||||
template_11:
|
||||
template: true
|
||||
actions:
|
||||
GET:
|
||||
args:
|
||||
- argc: 0
|
||||
- argc : 1
|
||||
output: [ testytest ]
|
||||
detectors:
|
||||
EIGER:
|
||||
function: 'do_eiger'
|
||||
args:
|
||||
- argc: 99
|
||||
output: [ eigerOutput ]
|
||||
POTATO:
|
||||
function: 'do_potato'
|
||||
|
||||
|
||||
case_1100:
|
||||
inherit_actions: template_11
|
||||
help: "1100"
|
||||
|
||||
case_1101:
|
||||
inherit_actions: template_11
|
||||
help: "1101"
|
||||
actions:
|
||||
GET:
|
||||
detectors:
|
||||
MYTHEN3:
|
||||
function: 'do_mythen3'
|
||||
POTATO:
|
||||
function: 'do_potato'
|
||||
args:
|
||||
- argc: 101
|
||||
function: 'potato_function'
|
||||
- argc: 202
|
||||
|
||||
case_1110:
|
||||
inherit_actions: template_11
|
||||
help: "1110"
|
||||
actions:
|
||||
GET:
|
||||
argc: 77
|
||||
function: 'get_function'
|
||||
|
||||
|
||||
|
||||
case_1111:
|
||||
inherit_actions: template_11
|
||||
help: "1111"
|
||||
actions:
|
||||
GET:
|
||||
argc: 77
|
||||
function: 'get_function'
|
||||
detectors:
|
||||
MYTHEN3:
|
||||
function: 'do_mythen3'
|
||||
POTATO:
|
||||
function: 'do_potato'
|
||||
args:
|
||||
- argc: 101
|
||||
function: 'potato_function'
|
||||
- argc: 202
|
@ -0,0 +1,101 @@
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
from commands_parser.commands_parser import CommandParser
|
||||
|
||||
data_path = Path(__file__).parent.parent / "data"
|
||||
|
||||
|
||||
def test_basic_propagation(tmp_path):
|
||||
output_file = tmp_path / "basic.yaml"
|
||||
command_parser = CommandParser(commands_file=data_path / "basic.yaml", output_file=output_file)
|
||||
command_parser.verify_format()
|
||||
command_parser.parse_all_commands()
|
||||
|
||||
assert output_file.exists()
|
||||
command = yaml.unsafe_load(output_file.open('r'))['basic']
|
||||
assert command['help'] == "xx11"
|
||||
assert len(command['actions']) == 2
|
||||
# test 'GET' action
|
||||
assert 'args' in command['actions']['GET']
|
||||
assert len(command['actions']['GET'].keys()) == 1 # only 'args' key
|
||||
assert len(command['actions']['GET']['args']) == 1 # only one argument
|
||||
assert command['actions']['GET']['args'][0]['argc'] == 0
|
||||
assert command['actions']['GET']['args'][0]['function'] == 'func1'
|
||||
assert command['actions']['GET']['args'][0]['output'] == ['OutString(t)']
|
||||
assert command['actions']['GET']['args'][0]['input'] == []
|
||||
assert command['actions']['GET']['args'][0]['cast_input'] == []
|
||||
assert command['actions']['GET']['args'][0]['require_det_id'] is False
|
||||
# test PUT action
|
||||
assert 'args' in command['actions']['PUT']
|
||||
assert len(command['actions']['PUT'].keys()) == 1 # only 'args' key
|
||||
assert len(command['actions']['PUT']['args']) == 1 # only one argument
|
||||
assert command['actions']['PUT']['args'][0]['argc'] == 1
|
||||
assert command['actions']['PUT']['args'][0]['function'] == 'func2'
|
||||
assert command['actions']['PUT']['args'][0]['cast_input'] == [True]
|
||||
assert command['actions']['PUT']['args'][0]['output'] == ['args.front()']
|
||||
assert command['actions']['PUT']['args'][0]['input_types'] == ['int']
|
||||
assert command['actions']['PUT']['args'][0]['require_det_id'] is False
|
||||
|
||||
|
||||
def test_basic_inheritance(tmp_path):
|
||||
output_file = tmp_path / "basic_inheritance.yaml"
|
||||
command_parser = CommandParser(commands_file=data_path / "basic_inheritance.yaml", output_file=output_file)
|
||||
command_parser.verify_format()
|
||||
command_parser.parse_all_commands()
|
||||
assert output_file.exists()
|
||||
command = yaml.unsafe_load(output_file.open('r'))['basic']
|
||||
assert command['help'] == "xx11"
|
||||
assert command['actions'].keys() == {'GET', 'PUT'}
|
||||
# test 'GET' action
|
||||
assert 'args' in command['actions']['GET']
|
||||
assert command['actions']['GET'].keys() == {'args'} # only 'args' key
|
||||
assert len(command['actions']['GET']['args']) == 1 # only one argument
|
||||
assert command['actions']['GET']['args'][0]['argc'] == 2
|
||||
assert command['actions']['GET']['args'][0]['function'] == 'x'
|
||||
assert command['actions']['GET']['args'][0]['output'] == [] # test overwriting args when they are present in child
|
||||
assert command['actions']['GET']['args'][0]['input'] == []
|
||||
assert command['actions']['GET']['args'][0]['cast_input'] == []
|
||||
assert command['actions']['GET']['args'][0]['require_det_id'] is False
|
||||
# test PUT action
|
||||
assert 'args' in command['actions']['PUT']
|
||||
assert command['actions']['PUT'].keys() == {'args'} # only 'args' key
|
||||
assert len(command['actions']['PUT']['args']) == 1 # only one argument
|
||||
assert command['actions']['PUT']['args'][0]['argc'] == 1
|
||||
assert command['actions']['PUT']['args'][0]['function'] == 'func2'
|
||||
assert command['actions']['PUT']['args'][0]['cast_input'] == [True]
|
||||
assert command['actions']['PUT']['args'][0]['output'] == ['args.front()']
|
||||
assert command['actions']['PUT']['args'][0]['input_types'] == ['int']
|
||||
assert command['actions']['PUT']['args'][0]['require_det_id'] is False
|
||||
|
||||
|
||||
def test_basic_inheritance2(tmp_path):
|
||||
output_file = tmp_path / "basic_inheritance.yaml"
|
||||
command_parser = CommandParser(commands_file=data_path / "basic_inheritance.yaml", output_file=output_file)
|
||||
command_parser.verify_format()
|
||||
command_parser.parse_all_commands()
|
||||
assert output_file.exists()
|
||||
command = yaml.unsafe_load(output_file.open('r'))['basic2']
|
||||
# check GET
|
||||
assert len(command['actions']['GET']['args']) == 1
|
||||
assert command['actions']['GET'].keys() == {'args'}
|
||||
arg = command['actions']['GET']['args'][0]
|
||||
assert arg['argc'] == 2
|
||||
assert arg['output'] == ['OutString(t)']
|
||||
# check that length of cast input is equal to length of input_types and input
|
||||
assert len(arg['input']) == len(arg['input_types']) == len(arg['cast_input']) == 4
|
||||
assert arg['function'] == 'x'
|
||||
assert 'convert_to_time' in arg
|
||||
assert arg['convert_to_time'].keys() == {'input', 'output'}
|
||||
assert 'separate_time_units' in arg
|
||||
assert arg['separate_time_units'].keys() == {'input', 'output'}
|
||||
|
||||
# check PUT
|
||||
assert command['actions']['PUT'].keys() == {'args'}
|
||||
assert len(command['actions']['PUT']['args']) == 2
|
||||
assert command['actions']['PUT']['args'][0]['argc'] == 19
|
||||
assert command['actions']['PUT']['args'][0]['function'] == 'y'
|
||||
assert command['actions']['PUT']['args'][1]['argc'] == 91
|
||||
assert command['actions']['PUT']['args'][1]['function'] == 'y'
|
||||
|
@ -0,0 +1,309 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest as pytest
|
||||
import yaml
|
||||
|
||||
from commands_parser.commands_parser import CommandParser
|
||||
|
||||
data_path = Path(__file__).parent.parent / "data"
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def detector_file_commands(tmp_path):
|
||||
output_file = tmp_path / "detectors.yaml"
|
||||
command_parser = CommandParser(commands_file=data_path / "detectors.yaml", output_file=output_file)
|
||||
command_parser.verify_format()
|
||||
|
||||
def func(command):
|
||||
return command_parser.parse_command(command)
|
||||
|
||||
return func
|
||||
|
||||
|
||||
def test_basic_propagation(tmp_path, detector_file_commands):
|
||||
command = detector_file_commands('basic')
|
||||
|
||||
assert command['help'] == "xx11"
|
||||
|
||||
# GET
|
||||
assert command['actions']['GET'].keys() == {'detectors', 'args'}
|
||||
assert command['actions']['GET']['detectors'].keys() == {'MYTHEN3', 'CHIPTESTBOARD'}
|
||||
mythen = command['actions']['GET']['detectors']['MYTHEN3']
|
||||
assert len(mythen) == 2
|
||||
assert mythen[0]['argc'] == 0
|
||||
assert mythen[1]['argc'] == 1
|
||||
assert mythen[0]['function'] == mythen[1]['function'] == 'do_mythen3'
|
||||
assert mythen[0]['output'] == ['OutString(t)']
|
||||
assert mythen[1]['output'] == ['testytest']
|
||||
|
||||
ctb = command['actions']['GET']['detectors']['CHIPTESTBOARD']
|
||||
assert len(ctb) == 1
|
||||
assert ctb[0]['argc'] == 55
|
||||
assert ctb[0]['function'] == 'func1'
|
||||
|
||||
# PUT
|
||||
assert command['actions']['PUT'].keys() == {'detectors'}
|
||||
assert command['actions']['PUT']['detectors'].keys() == {'EIGER'}
|
||||
eiger = command['actions']['PUT']['detectors']['EIGER']
|
||||
assert len(eiger) == 1
|
||||
assert eiger[0]['argc'] == 99
|
||||
assert eiger[0]['function'] == 'do_eiger'
|
||||
assert eiger[0]['output'] == ['eigerOutput']
|
||||
|
||||
# 16 test cases for inheritance
|
||||
# 1st bit: parent has args
|
||||
# 2nd bit: parent has detectors
|
||||
# 3rd bit: child has args
|
||||
# 4th bit: child has detectors
|
||||
# each test case is a combination of the above bits
|
||||
# all the possible combinations are tested
|
||||
|
||||
def test_inheritance_0100(tmp_path, detector_file_commands):
|
||||
command = detector_file_commands('case_0100')
|
||||
assert command['help'] == "0100"
|
||||
assert 'actions' in command
|
||||
assert command['actions'].keys() == {'GET'}
|
||||
assert command['actions']['GET'].keys() == {'detectors'}
|
||||
assert command['actions']['GET']['detectors'].keys() == {'MYTHEN3', 'CHIPTESTBOARD'}
|
||||
mythen = command['actions']['GET']['detectors']['MYTHEN3']
|
||||
assert len(mythen) == 1
|
||||
assert mythen[0]['argc'] == 99
|
||||
assert mythen[0]['function'] == 'do_mythen3'
|
||||
|
||||
|
||||
def test_inheritance_0101(tmp_path, detector_file_commands):
|
||||
command = detector_file_commands('case_0101')
|
||||
assert command['help'] == "0101"
|
||||
assert 'actions' in command
|
||||
assert command['actions'].keys() == {'GET'}
|
||||
assert command['actions']['GET'].keys() == {'detectors'}
|
||||
assert command['actions']['GET']['detectors'].keys() == {'MYTHEN3', 'CHIPTESTBOARD'}
|
||||
mythen = command['actions']['GET']['detectors']['MYTHEN3']
|
||||
assert len(mythen) == 1
|
||||
assert mythen[0]['argc'] == 420
|
||||
assert mythen[0]['function'] == 'do_mythen23'
|
||||
ctb = command['actions']['GET']['detectors']['CHIPTESTBOARD']
|
||||
assert len(ctb) == 1
|
||||
assert ctb[0]['argc'] == 98
|
||||
assert ctb[0]['function'] == 'do_ctb'
|
||||
|
||||
|
||||
def test_inheritance_0110(tmp_path, detector_file_commands):
|
||||
command = detector_file_commands('case_0110')
|
||||
assert command['help'] == "0110"
|
||||
assert 'actions' in command
|
||||
assert command['actions'].keys() == {'GET'}
|
||||
assert command['actions']['GET'].keys() == {'args', 'detectors'}
|
||||
assert command['actions']['GET']['args'][0]['argc'] == 111
|
||||
mythen = command['actions']['GET']['detectors']['MYTHEN3']
|
||||
assert len(mythen) == 1
|
||||
assert mythen[0]['argc'] == 99
|
||||
assert mythen[0]['function'] == 'do_mythen3'
|
||||
ctb = command['actions']['GET']['detectors']['CHIPTESTBOARD']
|
||||
assert len(ctb) == 1
|
||||
assert ctb[0]['argc'] == 98
|
||||
assert ctb[0]['function'] == 'do_ctb'
|
||||
|
||||
|
||||
def test_inheritance_0110v2(tmp_path, detector_file_commands):
|
||||
command = detector_file_commands('case_0110v2')
|
||||
assert command['help'] == "0110v2"
|
||||
assert 'actions' in command
|
||||
assert command['actions'].keys() == {'GET'}
|
||||
assert command['actions']['GET'].keys() == {'args', 'detectors'}
|
||||
assert command['actions']['GET']['args'][0]['argc'] == 111
|
||||
mythen = command['actions']['GET']['detectors']['MYTHEN3']
|
||||
assert len(mythen) == 1
|
||||
assert mythen[0]['argc'] == 99
|
||||
assert mythen[0]['function'] == 'do_mythen3'
|
||||
ctb = command['actions']['GET']['detectors']['CHIPTESTBOARD']
|
||||
assert len(ctb) == 1
|
||||
assert ctb[0]['argc'] == 98
|
||||
assert ctb[0]['function'] == 'do_ctb'
|
||||
|
||||
|
||||
def test_inheritacnce_0111(tmp_path, detector_file_commands):
|
||||
command = detector_file_commands('case_0111')
|
||||
assert command['help'] == "0111"
|
||||
assert 'actions' in command
|
||||
assert command['actions'].keys() == {'GET'}
|
||||
assert command['actions']['GET'].keys() == {'args', 'detectors'}
|
||||
assert command['actions']['GET']['detectors'].keys() == {'MYTHEN3', 'CHIPTESTBOARD'}
|
||||
mythen = command['actions']['GET']['detectors']['MYTHEN3']
|
||||
assert len(mythen) == 1
|
||||
assert command['actions']['GET']['args'][0]['argc'] == 111
|
||||
assert len(mythen) == 1
|
||||
assert mythen[0]['argc'] == 420
|
||||
assert mythen[0]['function'] == 'do_mythen23'
|
||||
ctb = command['actions']['GET']['detectors']['CHIPTESTBOARD']
|
||||
assert len(ctb) == 1
|
||||
assert ctb[0]['argc'] == 98
|
||||
assert ctb[0]['function'] == 'do_ctb'
|
||||
|
||||
|
||||
# cases 1000, 1001, 1010, 1011
|
||||
def test_inheritance_1000(tmp_path, detector_file_commands):
|
||||
command = detector_file_commands('case_1000')
|
||||
assert command['help'] == "1000"
|
||||
assert 'actions' in command
|
||||
assert command['actions'].keys() == {'GET'}
|
||||
assert command['actions']['GET'].keys() == {'args'}
|
||||
assert len(command['actions']['GET']['args']) == 2
|
||||
assert command['actions']['GET']['args'][0]['argc'] == 0
|
||||
assert command['actions']['GET']['args'][0]['output'] == []
|
||||
|
||||
assert command['actions']['GET']['args'][1]['argc'] == 1
|
||||
assert command['actions']['GET']['args'][1]['output'] == ['testytest']
|
||||
|
||||
|
||||
def test_inheritance_1001(tmp_path, detector_file_commands):
|
||||
command = detector_file_commands('case_1001')
|
||||
assert command['help'] == "1001"
|
||||
assert 'actions' in command
|
||||
assert command['actions'].keys() == {'GET'}
|
||||
assert command['actions']['GET'].keys() == {'args', 'detectors'}
|
||||
assert len(command['actions']['GET']['args']) == 2
|
||||
assert command['actions']['GET']['args'][0]['argc'] == 0
|
||||
assert command['actions']['GET']['args'][0]['output'] == []
|
||||
assert command['actions']['GET']['args'][1]['argc'] == 1
|
||||
assert command['actions']['GET']['args'][1]['output'] == ['testytest']
|
||||
|
||||
assert command['actions']['GET']['detectors'].keys() == {'MYTHEN3'}
|
||||
assert len(command['actions']['GET']['detectors']['MYTHEN3']) == 2
|
||||
assert command['actions']['GET']['detectors']['MYTHEN3'][0]['argc'] == 420
|
||||
assert command['actions']['GET']['detectors']['MYTHEN3'][0]['function'] == 'do_mythen23'
|
||||
assert command['actions']['GET']['detectors']['MYTHEN3'][1]['argc'] == 99
|
||||
assert command['actions']['GET']['detectors']['MYTHEN3'][1]['function'] == 'do_mythen3'
|
||||
|
||||
|
||||
def test_inheritance_1010(tmp_path, detector_file_commands):
|
||||
command = detector_file_commands('case_1010')
|
||||
assert command['help'] == "1010"
|
||||
assert 'actions' in command
|
||||
assert command['actions'].keys() == {'GET'}
|
||||
assert command['actions']['GET'].keys() == {'args'}
|
||||
assert len(command['actions']['GET']['args']) == 1
|
||||
assert command['actions']['GET']['args'][0]['argc'] == 111
|
||||
assert command['actions']['GET']['args'][0]['function'] == 'get_function'
|
||||
|
||||
|
||||
def test_inheritance_1011(tmp_path, detector_file_commands):
|
||||
command = detector_file_commands('case_1011')
|
||||
assert command['help'] == "1011"
|
||||
assert 'actions' in command
|
||||
assert command['actions'].keys() == {'GET'}
|
||||
assert command['actions']['GET'].keys() == {'args', 'detectors'}
|
||||
assert len(command['actions']['GET']['args']) == 1
|
||||
assert command['actions']['GET']['args'][0]['argc'] == 111
|
||||
assert command['actions']['GET']['args'][0]['function'] == 'get_function'
|
||||
|
||||
assert command['actions']['GET']['detectors'].keys() == {'MYTHEN3'}
|
||||
assert len(command['actions']['GET']['detectors']['MYTHEN3']) == 1
|
||||
assert command['actions']['GET']['detectors']['MYTHEN3'][0]['argc'] == 111
|
||||
assert command['actions']['GET']['detectors']['MYTHEN3'][0]['function'] == 'do_mythen23'
|
||||
|
||||
|
||||
# cases 1100, 1101, 1110, 1111
|
||||
|
||||
def test_inheritance_1100(tmp_path, detector_file_commands):
|
||||
command = detector_file_commands('case_1100')
|
||||
assert command['help'] == "1100"
|
||||
assert 'actions' in command
|
||||
assert command['actions'].keys() == {'GET'}
|
||||
assert command['actions']['GET'].keys() == {'args', 'detectors'}
|
||||
assert len(command['actions']['GET']['args']) == 2
|
||||
assert command['actions']['GET']['args'][0]['argc'] == 0
|
||||
assert command['actions']['GET']['args'][0]['output'] == []
|
||||
|
||||
assert command['actions']['GET']['args'][1]['argc'] == 1
|
||||
assert command['actions']['GET']['args'][1]['output'] == ['testytest']
|
||||
|
||||
assert command['actions']['GET']['detectors'].keys() == {'EIGER', 'POTATO'}
|
||||
assert len(command['actions']['GET']['detectors']['EIGER']) == 1
|
||||
assert command['actions']['GET']['detectors']['EIGER'][0]['argc'] == 99
|
||||
assert command['actions']['GET']['detectors']['EIGER'][0]['function'] == 'do_eiger'
|
||||
assert command['actions']['GET']['detectors']['EIGER'][0]['output'] == ['eigerOutput']
|
||||
|
||||
assert len(command['actions']['GET']['detectors']['POTATO']) == 2
|
||||
assert command['actions']['GET']['detectors']['POTATO'][0]['argc'] == 0
|
||||
assert command['actions']['GET']['detectors']['POTATO'][0]['function'] == 'do_potato'
|
||||
|
||||
|
||||
def test_inheritance_1101(tmp_path, detector_file_commands):
|
||||
command = detector_file_commands('case_1101')
|
||||
assert command['help'] == "1101"
|
||||
assert 'actions' in command
|
||||
assert command['actions'].keys() == {'GET'}
|
||||
assert command['actions']['GET'].keys() == {'args', 'detectors'}
|
||||
assert len(command['actions']['GET']['args']) == 2
|
||||
assert command['actions']['GET']['args'][0]['argc'] == 0
|
||||
assert command['actions']['GET']['args'][0]['output'] == []
|
||||
assert command['actions']['GET']['args'][1]['argc'] == 1
|
||||
assert command['actions']['GET']['args'][1]['output'] == ['testytest']
|
||||
|
||||
assert command['actions']['GET']['detectors'].keys() == {'EIGER', 'MYTHEN3', 'POTATO'}
|
||||
assert len(command['actions']['GET']['detectors']['MYTHEN3']) == 2
|
||||
assert command['actions']['GET']['detectors']['MYTHEN3'][0]['argc'] == 0
|
||||
assert command['actions']['GET']['detectors']['MYTHEN3'][0]['function'] == 'do_mythen3'
|
||||
assert command['actions']['GET']['detectors']['MYTHEN3'][1]['argc'] == 1
|
||||
assert command['actions']['GET']['detectors']['MYTHEN3'][1]['function'] == 'do_mythen3'
|
||||
|
||||
assert len(command['actions']['GET']['detectors']['EIGER']) == 1
|
||||
assert command['actions']['GET']['detectors']['EIGER'][0]['argc'] == 99
|
||||
assert command['actions']['GET']['detectors']['EIGER'][0]['function'] == 'do_eiger'
|
||||
assert command['actions']['GET']['detectors']['EIGER'][0]['output'] == ['eigerOutput']
|
||||
|
||||
assert len(command['actions']['GET']['detectors']['POTATO']) == 2
|
||||
assert command['actions']['GET']['detectors']['POTATO'][0]['argc'] == 101
|
||||
assert command['actions']['GET']['detectors']['POTATO'][0]['function'] == 'potato_function'
|
||||
assert command['actions']['GET']['detectors']['POTATO'][1]['argc'] == 202
|
||||
assert command['actions']['GET']['detectors']['POTATO'][1]['function'] == 'do_potato'
|
||||
|
||||
|
||||
def test_inheritance_1110(tmp_path, detector_file_commands):
|
||||
command = detector_file_commands('case_1110')
|
||||
assert command['help'] == "1110"
|
||||
assert 'actions' in command
|
||||
assert command['actions'].keys() == {'GET'}
|
||||
assert command['actions']['GET'].keys() == {'args', 'detectors'}
|
||||
assert len(command['actions']['GET']['args']) == 1
|
||||
assert command['actions']['GET']['args'][0]['argc'] == 77
|
||||
assert command['actions']['GET']['args'][0]['function'] == 'get_function'
|
||||
|
||||
assert command['actions']['GET']['detectors'].keys() == {'EIGER', 'POTATO'}
|
||||
assert len(command['actions']['GET']['detectors']['EIGER']) == 1
|
||||
assert command['actions']['GET']['detectors']['EIGER'][0]['argc'] == 99
|
||||
assert command['actions']['GET']['detectors']['EIGER'][0]['function'] == 'do_eiger'
|
||||
|
||||
assert len(command['actions']['GET']['detectors']['POTATO']) == 2
|
||||
assert command['actions']['GET']['detectors']['POTATO'][0]['argc'] == 0
|
||||
assert command['actions']['GET']['detectors']['POTATO'][0]['function'] == 'do_potato'
|
||||
assert command['actions']['GET']['detectors']['POTATO'][1]['argc'] == 1
|
||||
assert command['actions']['GET']['detectors']['POTATO'][1]['function'] == 'do_potato'
|
||||
|
||||
|
||||
def test_inheritance_1111(tmp_path, detector_file_commands):
|
||||
command = detector_file_commands('case_1111')
|
||||
assert command['help'] == "1111"
|
||||
assert 'actions' in command
|
||||
assert command['actions'].keys() == {'GET'}
|
||||
assert command['actions']['GET'].keys() == {'args', 'detectors'}
|
||||
assert len(command['actions']['GET']['args']) == 1
|
||||
assert command['actions']['GET']['args'][0]['argc'] == 77
|
||||
assert command['actions']['GET']['args'][0]['function'] == 'get_function'
|
||||
|
||||
assert command['actions']['GET']['detectors'].keys() == {'EIGER', 'MYTHEN3', 'POTATO'}
|
||||
assert len(command['actions']['GET']['detectors']['MYTHEN3']) == 1
|
||||
assert command['actions']['GET']['detectors']['MYTHEN3'][0]['argc'] == 77
|
||||
assert command['actions']['GET']['detectors']['MYTHEN3'][0]['function'] == 'do_mythen3'
|
||||
|
||||
assert len(command['actions']['GET']['detectors']['EIGER']) == 1
|
||||
assert command['actions']['GET']['detectors']['EIGER'][0]['argc'] == 99
|
||||
assert command['actions']['GET']['detectors']['EIGER'][0]['function'] == 'do_eiger'
|
||||
|
||||
assert len(command['actions']['GET']['detectors']['POTATO']) == 2
|
||||
assert command['actions']['GET']['detectors']['POTATO'][0]['argc'] == 101
|
||||
assert command['actions']['GET']['detectors']['POTATO'][0]['function'] == 'potato_function'
|
||||
assert command['actions']['GET']['detectors']['POTATO'][1]['argc'] == 202
|
||||
assert command['actions']['GET']['detectors']['POTATO'][1]['function'] == 'do_potato'
|
@ -0,0 +1,29 @@
|
||||
from pathlib import Path
|
||||
from commands_parser.commands_parser import CommandParser
|
||||
import gen_commands
|
||||
|
||||
data_path = Path(__file__).parent.parent
|
||||
|
||||
|
||||
def test_parse_and_generate(tmp_path):
|
||||
"""
|
||||
tests that the parse and generate functions work without errors
|
||||
:param tmp_path:
|
||||
:return:
|
||||
"""
|
||||
output_file = tmp_path / "detectors.yaml"
|
||||
command_parser = CommandParser(commands_file=data_path / "commands.yaml", output_file=output_file)
|
||||
command_parser.verify_format()
|
||||
command_parser.parse_all_commands()
|
||||
assert output_file.exists()
|
||||
|
||||
GEN_PATH = Path(__file__).parent.parent
|
||||
gen_commands.generate(
|
||||
output_file,
|
||||
GEN_PATH / "Caller.in.cpp",
|
||||
GEN_PATH / "Caller.in.h",
|
||||
tmp_path / "Caller.cpp",
|
||||
tmp_path / "Caller.h",
|
||||
)
|
||||
assert (tmp_path / "Caller.cpp").exists()
|
||||
assert (tmp_path / "Caller.h").exists()
|
28
slsDetectorSoftware/generator/very_special_functions.txt
Normal file
28
slsDetectorSoftware/generator/very_special_functions.txt
Normal file
@ -0,0 +1,28 @@
|
||||
hostname (find +)
|
||||
acquire
|
||||
versions (maybe with few tweaks)
|
||||
threshold (+++++)
|
||||
trimen (maybe with few tweaks)
|
||||
badchannels (somewhat special)
|
||||
currentsource (special)
|
||||
dacvalues (can be done with the ctb_output_list)
|
||||
udp_srcip (could be done if I add condition functionality for logging)
|
||||
udp_srcip2 (same as above)
|
||||
udp_dstip (same as above)
|
||||
udp_dstip2
|
||||
rx_hostname (split('+'))
|
||||
rx_roi (can be done if there;s condition support?)
|
||||
ratecorr (can be done if there's condition support?)
|
||||
burstmode (very special)
|
||||
vetostream
|
||||
counters
|
||||
gaincaps (has for loop and condition)
|
||||
samples (ask Dhanya if it is okay to change the order of calling the ctb functions in PUT)
|
||||
slowadc (has for loop)
|
||||
rx_dbitlist (very special)
|
||||
rx_jsonaddheader (very special)
|
||||
execcommand (has for loop)
|
||||
thresholdnotb
|
||||
# notes
|
||||
# ReceiverStatus error on put is not done
|
||||
# ask about burstmode function
|
Reference in New Issue
Block a user