TclCommand.py 6.4 KB

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