TclCommand.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. import sys, inspect, pkgutil
  2. import re
  3. import FlatCAMApp
  4. class TclCommand(object):
  5. app=None
  6. # array of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon)
  7. aliases = None
  8. # dictionary of types from Tcl command: args = {'name': str}, this is for value without optionname
  9. arg_names = {'name': str}
  10. # dictionary of types from Tcl command: types = {'outname': str} , this is for options like -optionname value
  11. option_types = {}
  12. # array of mandatory options for current Tcl command: required = {'name','outname'}
  13. required = ['name']
  14. # structured help for current command
  15. help = {
  16. 'main': "undefined help.",
  17. 'args': {
  18. 'argumentname': 'undefined help.',
  19. 'optionname': 'undefined help.'
  20. },
  21. 'examples' : []
  22. }
  23. def __init__(self, app):
  24. self.app=app
  25. def get_decorated_help(self):
  26. """
  27. Decorate help for TCL console output.
  28. :return: decorated help from structue
  29. """
  30. def get_decorated_command(alias):
  31. command_string = []
  32. for key, value in reversed(self.help['args'].items()):
  33. command_string.append(get_decorated_argument(key, value, True))
  34. return "> " + alias + " " + " ".join(command_string)
  35. def get_decorated_argument(key, value, in_command=False):
  36. option_symbol = ''
  37. if key in self.arg_names:
  38. type=self.arg_names[key]
  39. in_command_name = "<" + str(type.__name__) + ">"
  40. else:
  41. option_symbol = '-'
  42. type=self.option_types[key]
  43. in_command_name = option_symbol + key + " <" + str(type.__name__) + ">"
  44. if in_command:
  45. if key in self.required:
  46. return in_command_name
  47. else:
  48. return '[' + in_command_name + "]"
  49. else:
  50. if key in self.required:
  51. return "\t" + option_symbol + key + " <" + str(type.__name__) + ">: " + value
  52. else:
  53. return "\t[" + option_symbol + key + " <" + str(type.__name__) + ">: " + value+"]"
  54. def get_decorated_example(example):
  55. example_string = ''
  56. return "todo" + example_string
  57. help_string=[self.help['main']]
  58. for alias in self.aliases:
  59. help_string.append(get_decorated_command(alias))
  60. for key, value in reversed(self.help['args'].items()):
  61. help_string.append(get_decorated_argument(key, value))
  62. for example in self.help['examples']:
  63. help_string.append(get_decorated_example(example))
  64. return "\n".join(help_string)
  65. def parse_arguments(self, args):
  66. """
  67. Pre-processes arguments to detect '-keyword value' pairs into dictionary
  68. and standalone parameters into list.
  69. This is copy from FlatCAMApp.setup_shell().h() just for accesibility, original should be removed after all commands will be converted
  70. """
  71. options = {}
  72. arguments = []
  73. n = len(args)
  74. name = None
  75. for i in range(n):
  76. match = re.search(r'^-([a-zA-Z].*)', args[i])
  77. if match:
  78. assert name is None
  79. name = match.group(1)
  80. continue
  81. if name is None:
  82. arguments.append(args[i])
  83. else:
  84. options[name] = args[i]
  85. name = None
  86. return arguments, options
  87. def check_args(self, args):
  88. """
  89. Check arguments and options for right types
  90. :param args: arguments from tcl to check
  91. :return:
  92. """
  93. arguments, options = self.parse_arguments(args)
  94. named_args={}
  95. unnamed_args=[]
  96. # check arguments
  97. idx=0
  98. arg_names_reversed=self.arg_names.items()
  99. for argument in arguments:
  100. if len(self.arg_names) > idx:
  101. key, type = arg_names_reversed[len(self.arg_names)-idx-1]
  102. try:
  103. named_args[key] = type(argument)
  104. except Exception, e:
  105. self.app.raiseTclError("Cannot cast named argument '%s' to type %s." % (key, type))
  106. else:
  107. unnamed_args.append(argument)
  108. idx += 1
  109. # check otions
  110. for key in options:
  111. if key not in self.option_types:
  112. self.app.raiseTclError('Unknown parameter: %s' % key)
  113. try:
  114. named_args[key] = self.option_types[key](options[key])
  115. except Exception, e:
  116. self.app.raiseTclError("Cannot cast argument '-%s' to type %s." % (key, self.option_types[key]))
  117. # check required arguments
  118. for key in self.required:
  119. if key not in named_args:
  120. self.app.raiseTclError("Missing required argument '%s'." % (key))
  121. return named_args, unnamed_args
  122. def execute_wrapper(self, *args):
  123. """
  124. Command which is called by tcl console when current commands aliases are hit.
  125. Main catch(except) is implemented here.
  126. This method should be reimplemented only when initial checking sequence differs
  127. :param args: arguments passed from tcl command console
  128. :return: None, output text or exception
  129. """
  130. try:
  131. args, unnamed_args = self.check_args(args)
  132. return self.execute(args, unnamed_args)
  133. except Exception as unknown:
  134. self.app.raiseTclUnknownError(unknown)
  135. def execute(self, args, unnamed_args):
  136. """
  137. Direct execute of command, this method should be implemented in each descendant.
  138. No main catch should be implemented here.
  139. :param args: array of known named arguments and options
  140. :param unnamed_args: array of other values which were passed into command
  141. without -somename and we do not have them in known arg_names
  142. :return: None, output text or exception
  143. """
  144. raise NotImplementedError("Please Implement this method")