You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

177 lines
5.7 KiB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
  1. '''
  2. Created on Jan 24, 2014
  3. @author: Chris
  4. Hey, whaduya know. This is out of date again. TODO: update giant doctring.
  5. ##How things work these days (though, likely to change)
  6. The decorator is used solely as a nice way to get the location
  7. of the executing script. It no longer returns a decorated version
  8. of the client code, but in fact completely hijacks the execution.
  9. So, rather than returning a reference to the client's main, it now
  10. returns itself, thus short-circuiting the execution of the client
  11. program.
  12. What it DOES do now is grab where the client module is stored and
  13. read it in as a file so that it can hack away at it.
  14. The first step, as before, is getting the ArgumentParser reference
  15. so that the needed values can be extracted. This is done by reading
  16. the source file up to the point where the `parse_args()` method is
  17. called. This puts us smack in the middle of the client's `main` method.
  18. This first half guarantees that all imports, modules, variable assignments,
  19. etc.. are caught (unlike before).
  20. Next step: getting the rest of the source code that's relevant
  21. The file is again read up to the `parse_args` call, but this time everything
  22. leading up to that point is dropped and we keep only the remainder of the file.
  23. So, now the top and the bottom is located, but the bottom needs to be trimmed a
  24. little more -- we want to drop everything remaining in the main method.
  25. So, we `dropwhile` lines are currently indented (and thus still part of the `main`
  26. method)
  27. Finally, we arrive at the end, which gives us an exact copy of the original source
  28. file, minus all of it's main logic. The two pieces are then sandwiched together,
  29. saved to a file, and imported as a new module. Now all that has to be done is call
  30. it (moddified) main function, and bam! It returns to fully populated parser object
  31. to us. No more complicated ast stuff. Just a little bit of string parsing and we're
  32. done.
  33. '''
  34. import os
  35. import tempfile
  36. import wx
  37. import source_parser
  38. import atexit
  39. from functools import partial
  40. from gooey.gui.lang import i18n
  41. from gooey.gui.windows import layouts
  42. from gooey.python_bindings import argparse_to_json
  43. def Gooey(f=None, advanced=True,
  44. language='english', show_config=True,
  45. program_name=None, program_description=None):
  46. '''
  47. Decorator for client code's main function.
  48. Entry point for the GUI generator.
  49. Scans the client code for argparse data.
  50. If found, extracts it and build the proper
  51. configuration gui windows (basic or advanced).
  52. '''
  53. params = locals()
  54. def build(payload):
  55. def inner():
  56. show_config = params['show_config'] #because nonlocal keyword doesn't exist yet :(
  57. main_module_path = get_caller_path()
  58. _, filename = os.path.split(main_module_path)
  59. cleaned_source = clean_source(main_module_path)
  60. descriptor, tmp_filepath = tempfile.mkstemp(suffix='.py')
  61. atexit.register(cleanup, descriptor, tmp_filepath)
  62. with open(tmp_filepath, 'w') as f:
  63. f.write(cleaned_source)
  64. if not has_argparse(cleaned_source):
  65. show_config = False
  66. run_cmd = 'python {}'.format(tmp_filepath)
  67. # Must be called before anything else
  68. app = wx.App(False)
  69. i18n.load(language)
  70. # load gui components after loading the language pack
  71. from gooey.gui.client_app import ClientApp
  72. from gooey.gui.client_app import EmptyClientApp
  73. from gooey.gui.windows.base_window import BaseWindow
  74. from gooey.gui.windows.advanced_config import AdvancedConfigPanel
  75. from gooey.gui.windows.basic_config_panel import BasicConfigPanel
  76. meta = {
  77. 'target': run_cmd,
  78. 'program_name': program_name,
  79. 'program_description': program_description or '',
  80. 'show_config': show_config,
  81. 'show_advanced': advanced,
  82. 'default_size': (610, 530),
  83. 'requireds_cols': 1,
  84. 'optionals_cols': 2,
  85. 'manual_start': False
  86. }
  87. if show_config:
  88. parser = get_parser(main_module_path)
  89. meta['program_description'] = parser.description or program_description
  90. client_app = ClientApp(parser, payload)
  91. if advanced:
  92. build_spec = dict(meta.items() + argparse_to_json.convert(parser).items())
  93. BodyPanel = partial(AdvancedConfigPanel, build_spec=build_spec)
  94. else:
  95. build_spec = dict(meta.items() + layouts.basic_config.items())
  96. BodyPanel = partial(AdvancedConfigPanel, build_spec=build_spec)
  97. # User doesn't want to display configuration screen
  98. # Just jump straight to the run panel
  99. else:
  100. build_spec = dict(meta.items() + layouts.basic_config.items())
  101. build_spec['manual_start'] = True
  102. BodyPanel = partial(AdvancedConfigPanel, build_spec=build_spec)
  103. client_app = EmptyClientApp(payload)
  104. frame = BaseWindow(BodyPanel, build_spec, params)
  105. if not show_config:
  106. frame.ManualStart()
  107. frame.Show(True)
  108. app.MainLoop()
  109. inner.__name__ = payload.__name__
  110. return inner
  111. if callable(f):
  112. return build(f)
  113. return build
  114. def clean_source(module_path):
  115. with open(module_path, 'r') as f:
  116. return ''.join(
  117. line for line in f.readlines()
  118. if '@gooey' not in line.lower())
  119. def get_parser(module_path):
  120. return source_parser.extract_parser(module_path)
  121. def get_caller_path():
  122. tmp_sys = __import__('sys')
  123. return tmp_sys.argv[0]
  124. def has_argparse(source):
  125. bla = ['.parse_args()' in line.lower() for line in source.split('\n')]
  126. return any(bla)
  127. def cleanup(descriptor, filepath):
  128. os.close(descriptor)
  129. os.remove(filepath)
  130. if __name__ == '__main__':
  131. pass