diff --git a/examples/tutorials/tutor.py b/examples/tutorials/tutor.py index 713fc1dad..52170949d 100644 --- a/examples/tutorials/tutor.py +++ b/examples/tutorials/tutor.py @@ -20,7 +20,7 @@ import os import sys -from traitsui.extras.tutor import Tutor +from traitsui.extras.demo import demo # Correct program usage information: usage = """ @@ -31,33 +31,22 @@ If omitted, 'root_dir' defaults to the current directory.""" -def main(home_dir, root_dir): +def main(root_dir): # Create a tutor and display the tutorial: - tutor = Tutor(home=os.path.dirname(home_dir)).trait_set( - path=root_dir) - if tutor.root is not None: - tutor.configure_traits() - else: - raise NameError("No traits tutorial found in %s" % root_dir) + path, name = os.path.splitext(root_dir) + demo(dir_name=root_dir) if __name__ == '__main__': # Validate the command line arguments: - if len(sys.argv) > 2: + if len(sys.argv) != 2: print(usage) sys.exit(1) - home_dir = os.path.dirname(sys.argv[0]) - - # Determine the root path to use for the tutorial files: - if len(sys.argv) == 2: - root_dir = sys.argv[1] - else: - root_dir = os.getcwd() - + root_dir = sys.argv[1] try: - main(home_dir, root_dir) + main(root_dir) except NameError as e: print(e) print(usage) diff --git a/traitsui/extras/demo.py b/traitsui/extras/demo.py index 7bee831d2..81bdb7052 100644 --- a/traitsui/extras/demo.py +++ b/traitsui/extras/demo.py @@ -21,8 +21,8 @@ from __future__ import absolute_import +import contextlib import glob -import io from io import StringIO import operator from os import listdir @@ -45,9 +45,11 @@ from configobj import ConfigObj +from pyface.api import ImageResource from traits.api import ( Any, Bool, + Button, cached_property, Code, Dict, @@ -59,9 +61,11 @@ Str, ) from traitsui.api import ( + CodeEditor, Handler, Heading, HGroup, + HSplit, HTMLEditor, Include, InstanceEditor, @@ -69,11 +73,14 @@ ObjectTreeNode, spring, Tabbed, + TitleEditor, TreeEditor, TreeNodeObject, UIInfo, + UItem, VGroup, - View + View, + VSplit, ) @@ -151,8 +158,7 @@ def parse_source(file_name): The source code, sans docstring. """ try: - with io.open(file_name, "r", encoding="utf-8") as fh: - source_code = fh.read() + source_code = _read_file(file_name) return extract_docstring_from_source(source_code) except Exception: # Print an error message instead of failing silently. @@ -163,10 +169,27 @@ def parse_source(file_name): return (error_msg, "") +def _read_file(path, mode='r', encoding='utf8'): + """ Returns the contents of a specified text file. + """ + with open(path, mode, encoding=encoding) as fh: + result = fh.read() + return result + + # ------------------------------------------------------------------------- # 'DemoFileHandler' class: # ------------------------------------------------------------------------- +@contextlib.contextmanager +def _set_stdout(std_out): + stdout, stderr = sys.stdout, sys.stderr + try: + sys.stdout = sys.stderr = std_out + yield std_out + finally: + sys.stdout, sys.stderr = stdout, stderr + class DemoFileHandler(Handler): @@ -174,80 +197,38 @@ class DemoFileHandler(Handler): # Trait definitions: # ------------------------------------------------------------------------- + #: Run the demo file + run_button = Button(image=ImageResource("run"), label="Run") + #: The current 'info' object (for use by the 'write' method): info = Instance(UIInfo) + def _run_button_changed(self): + demo_file = self.info.object + with _set_stdout(self): + demo_file.run_code() + def init(self, info): # Save the reference to the current 'info' object: self.info = info - - # Set up the 'print' logger: - df = info.object - df.log = "" - sys.stdout = sys.stderr = self - - # Read in the demo source file: - description, source = parse_source(df.path) - df.description = publish_html_str(description) - df.source = source - # Try to run the demo source file: - - # Append the path for the demo source file to sys.path, so as to - # resolve any local (relative) imports in the demo source file. - sys.path.append(dirname(df.path)) - - locals = df.parent.init_dic - locals["__name__"] = "___main___" - locals["__file__"] = df.path - sys.modules["__main__"].__file__ = df.path - try: - with io.open(df.path, "r", encoding="utf-8") as fp: - exec(compile(fp.read(), df.path, "exec"), locals, locals) - demo = self._get_object("modal_popup", locals) - if demo is not None: - demo = ModalDemoButton(demo=demo) - else: - demo = self._get_object("popup", locals) - if demo is not None: - demo = DemoButton(demo=demo) - else: - demo = self._get_object("demo", locals) - except Exception as excp: - demo = DemoError(msg=str(excp)) - - # Clean up sys.path - sys.path.remove(dirname(df.path)) - df.demo = demo + demo_file = info.object + with _set_stdout(self): + demo_file.init() def closed(self, info, is_ok): """ Closes the view. """ - info.object.demo = None - - # ------------------------------------------------------------------------- - # Get a specified object from the execution dictionary: - # ------------------------------------------------------------------------- - - def _get_object(self, name, dic): - object = dic.get(name) or dic.get(name.capitalize()) - if object is not None: - if isinstance(type(object), type): - try: - object = object() - except Exception: - pass - - if isinstance(object, HasTraits): - return object - - return None + demo_file = info.object + if hasattr(demo_file, 'demo'): + demo_file.demo = None # ------------------------------------------------------------------------- # Handles 'print' output: # ------------------------------------------------------------------------- def write(self, text): - self.info.object.log += text + demo_file = self.info.object + demo_file.log += text def flush(self): pass @@ -379,18 +360,13 @@ def has_children(self, node): # Gets the object's children: # ------------------------------------------------------------------------- - def get_children(self, node): + def get_children(self): """ Gets the object's children. """ raise NotImplementedError -class DemoFile(DemoTreeNodeObject): - - # ------------------------------------------------------------------------- - # Trait definitions: - # ------------------------------------------------------------------------- - +class DemoFileBase(DemoTreeNodeObject): #: Parent of this file: parent = Any @@ -409,22 +385,20 @@ class DemoFile(DemoTreeNodeObject): #: Description of what the demo does: description = HTML - #: Source code for the demo: - source = Code - - #: Demo object whose traits UI is to be displayed: - demo = Instance(HasTraits) - #: Log of all print messages displayed: log = Code _nice_name = Str + + def init(self): + self.log = "" + # ------------------------------------------------------------------------- # Implementation of the 'path' property: # ------------------------------------------------------------------------- def _get_path(self): - return join(self.parent.path, self.name + ".py") + return join(self.parent.path, self.name) # ------------------------------------------------------------------------- # Implementation of the 'nice_name' property: @@ -432,7 +406,8 @@ def _get_path(self): def _get_nice_name(self): if not self._nice_name: - self._nice_name = user_name_for(self.name) + name, ext = splitext(self.name) + self._nice_name = user_name_for(name) return self._nice_name def _set_nice_name(self, value): @@ -449,6 +424,95 @@ def has_children(self): """ return False + def get_children(self): + """ Gets the demo file's children. + """ + return [] + + +class DemoFile(DemoFileBase): + + #: Source code for the demo: + source = Code + + #: Demo object whose traits UI is to be displayed: + demo = Instance(HasTraits) + + def init(self): + super(DemoFile, self).init() + description, source = parse_source(self.path) + self.description = publish_html_str(description) + self.source = source + self.run_code() + + def run_code(self): + """ Runs the code associated with this demo file. + """ + try: + # Get the execution context dictionary: + locals = self.parent.init_dic + locals["__name__"] = "___main___" + locals["__file__"] = self.path + sys.modules["__main__"].__file__ = self.path + + exec(self.source, locals, locals) + + demo = self._get_object("modal_popup", locals) + if demo is not None: + demo = ModalDemoButton(demo=demo) + else: + demo = self._get_object("popup", locals) + if demo is not None: + demo = DemoButton(demo=demo) + else: + demo = self._get_object("demo", locals) + except Exception: + traceback.print_exc() + else: + self.demo = demo + + # ------------------------------------------------------------------------- + # Get a specified object from the execution dictionary: + # ------------------------------------------------------------------------- + + def _get_object(self, name, dic): + object = dic.get(name) or dic.get(name.capitalize()) + if object is not None: + if isinstance(type(object), type): + try: + object = object() + except Exception: + pass + + if isinstance(object, HasTraits): + return object + + return None + + +# HTML template for displaying an image file: +_image_template = """ +
+ + +This section contains the following topics:
-- - -""" - -#------------------------------------------------------------------------- -# Returns the contents of a specified text file (or None): -#------------------------------------------------------------------------- - - -def read_file(path, mode='rU', encoding='utf8'): - """ Returns the contents of a specified text file (or None). - - """ - try: - with open(path, mode, encoding=encoding) as fh: - result = fh.read() - return result - except Exception: - return None - - -#------------------------------------------------------------------------- -# Creates a title from a specified string: -#------------------------------------------------------------------------- - -def title_for(title): - """ Creates a title from a specified string. - """ - return capwords(title.replace('_', ' ')) - -#------------------------------------------------------------------------- -# Returns a relative CSS style sheet path for a specified path and parent -# section: -#------------------------------------------------------------------------- - - -def css_path_for(path, parent): - """ Returns a relative CSS style sheet path for a specified path and parent - section. - """ - if os.path.isfile(os.path.join(path, 'default.css')): - return 'default.css' - - if parent is not None: - result = parent.css_path - if result != '': - if path != parent.path: - result = os.path.join('..', result) - - return result - - return '' - -#------------------------------------------------------------------------- -# 'StdOut' class: -#------------------------------------------------------------------------- - - -class StdOut(object): - """ Simulate stdout, but redirect the output to the 'output' string - supplied by some 'owner' object. - """ - - def __init__(self, owner): - self.owner = owner - - def write(self, data): - """ Adds the specified data to the output log. - """ - self.owner.output += data - - def flush(self): - """ Flushes all current data to the output log. - """ - pass - -#------------------------------------------------------------------------- -# 'NoDemo' class: -#------------------------------------------------------------------------- - - -class NoDemo(HasPrivateTraits): - - #-- Traits View Definitions ---------------------------------------------- - - view = View( - Heading('No demo defined for this lab.'), - resizable=True - ) - -#------------------------------------------------------------------------- -# 'DemoPane' class: -#------------------------------------------------------------------------- - - -class DemoPane(HasPrivateTraits): - """ Displays the contents of a Python lab's *demo* value. - """ - - #-- Trait Definitions ---------------------------------------------------- - - demo = Instance(HasTraits, factory=NoDemo) - - #-- Traits View Definitions ---------------------------------------------- - - view = View( - Item('demo', - id='demo', - show_label=False, - style='custom', - resizable=True - ), - id='enthought.tutor.demo', - resizable=True - ) - -#------------------------------------------------------------------------- -# 'ATutorialItem' class: -#------------------------------------------------------------------------- - - -class ATutorialItem(HasPrivateTraits): - """ Defines the abstract base class for each type of item (HTML, Flash, - text, code) displayed within the tutor. - """ - - #-- Traits Definitions --------------------------------------------------- - - # The title for the item: - title = Str - - # The path to the item: - path = File - - # The displayable content for the item: - content = Property - -#------------------------------------------------------------------------- -# 'ADescriptionItem' class: -#------------------------------------------------------------------------- - - -class ADescriptionItem(ATutorialItem): - """ Defines a common base class for all description items. - """ - - #-- Event Handlers ------------------------------------------------------- - - def _path_changed(self, path): - """ Sets the title for the item based on the item's path name. - """ - self.title = title_for(os.path.splitext(os.path.basename( - path))[0]) - -#------------------------------------------------------------------------- -# 'HTMLItem' class: -#------------------------------------------------------------------------- - - -class HTMLItem(ADescriptionItem): - """ Defines a class used for displaying a single HTML page within the tutor - using the default Traits HTML editor. - """ - - #-- Traits Definitions --------------------------------------------------- - - url = Str - - #-- Traits View Definitions ---------------------------------------------- - - view = View( - Item('content', - style='readonly', - show_label=False, - editor=HTMLEditor() - ) - ) - - #-- Event Handlers ------------------------------------------------------- - - def _url_changed(self, url): - """ Sets the item title when the 'url' is changed. - """ - match = url_pat1.match(url) - if match is not None: - title = match.group(2).strip() - else: - title = url.strip() - col = title.rfind('/') - if col >= 0: - title = os.path.splitext(title[col + 1:])[0] - - self.title = title - - #-- Property Implementations --------------------------------------------- - - @cached_property - def _get_content(self): - """ Returns the item content. - """ - url = self.url - if url != '': - match = url_pat1.match(url) - if match is not None: - url = match.group(1) + match.group(3) - - return url - - return read_file(self.path) - - def _set_content(self, content): - """ Sets the item content. - """ - self._content = content - -#------------------------------------------------------------------------- -# 'HTMLStrItem' class: -#------------------------------------------------------------------------- - - -class HTMLStrItem(HTMLItem): - """ Defines a class used for displaying a single HTML text string within - the tutor using the default Traits HTML editor. - """ - - # Make the content a real trait rather than a property: - content = Str - -#------------------------------------------------------------------------- -# 'IEHTMLItem' class: -#------------------------------------------------------------------------- - - -class IEHTMLItem(HTMLItem): - """ Defines a class used for displaying a single HTML page within the tutor - using the Traits Internet Explorer HTML editor. - """ - - #-- Traits View Definitions ---------------------------------------------- - - view = View( - Item('content', - style='readonly', - show_label=False, - editor=IEHTMLEditor() - ) - ) - -#------------------------------------------------------------------------- -# 'IEHTMLStrItem' class: -#------------------------------------------------------------------------- - - -class IEHTMLStrItem(IEHTMLItem): - """ Defines a class used for displaying a single HTML text string within - the tutor using the Traits Internet Explorer HTML editor. - """ - - # Make the content a real trait rather than a property: - content = Str - -#------------------------------------------------------------------------- -# 'FlashItem' class: -#------------------------------------------------------------------------- - - -class FlashItem(HTMLItem): - """ Defines a class used for displaying a Flash-based animation or video - within the tutor. - """ - - #-- Traits View Definitions ---------------------------------------------- - - view = View( - Item('content', - style='readonly', - show_label=False, - editor=FlashEditor() - ) - ) - -#------------------------------------------------------------------------- -# 'TextItem' class: -#------------------------------------------------------------------------- - - -class TextItem(ADescriptionItem): - """ Defines a class used for displaying a text file within the tutor. - """ - - #-- Traits View Definitions ---------------------------------------------- - - view = View( - Item('content', - style='readonly', - show_label=False, - editor=CodeEditor(show_line_numbers=False, - selected_color=0xFFFFFF) - ) - ) - - #-- Property Implementations --------------------------------------------- - - @cached_property - def _get_content(self): - """ Returns the item content. - """ - return read_file(self.path) - -#------------------------------------------------------------------------- -# 'TextStrItem' class: -#------------------------------------------------------------------------- - - -class TextStrItem(TextItem): - """ Defines a class used for displaying a text file within the tutor. - """ - - # Make the content a real trait, rather than a property: - content = Str - -#------------------------------------------------------------------------- -# 'CodeItem' class: -#------------------------------------------------------------------------- - - -class CodeItem(ATutorialItem): - """ Defines a class used for displaying a Python source code fragment - within the tutor. - """ - - #-- Trait Definitions ---------------------------------------------------- - - # The displayable content for the item (override): - content = Str - - # The starting line of the code snippet within the original file: - start_line = Int - - # The currently selected line: - selected_line = Int - - # Should this section normally be hidden? - hidden = Bool - - #-- Traits View Definitions ---------------------------------------------- - - view = View( - Item('content', - style='custom', - show_label=False, - editor=CodeEditor(selected_line='selected_line') - ) - ) - -#------------------------------------------------------------------------- -# 'ASection' abstract base class: -#------------------------------------------------------------------------- - - -class ASection(HasPrivateTraits): - """ Defines an abstract base class for a single section of a tutorial. - """ - - #-- Traits Definitions --------------------------------------------------- - - # The title of the section: - title = Str - - # The path to this section: - path = Directory - - # The parent section of this section (if any): - parent = Instance('ASection') - - # Optional table of contents (can be used to define/locate the - # subsections): - toc = List(Str) - - # The path to the CSS style sheet to use for this section: - css_path = Property - - # The list of subsections contained in this section: - subsections = Property # List( ASection ) - - # This section can be executed: - is_runnable = Bool(True) - - # Should the Python code be automatically executed on start-up? - auto_run = Bool(False) - - #-- Property Implementations --------------------------------------------- - - @cached_property - def _get_subsections(self): - """ Returns the subsections for this section: - """ - if len(self.toc) > 0: - self._load_toc() - else: - self._load_dirs() - - # Return the cached list of sections: - return self._subsections - - @cached_property - def _get_css_path(self): - """ Returns the path to the CSS style sheet for this section. - """ - return css_path_for(self.path, self.parent) - - #-- Private Methods ------------------------------------------------------ - - def _load_dirs(self): - """ Defines the section's subsections by analyzing all of the section's - sub-directories. - """ - # No value cached yet: - dirs = [] - path = self.path - - # Find every sub-directory whose name begins with a number of the - # form ddd, or ends with a number of the form _ddd.ddd (used for - # sorting them into the correct presentation order): - for name in os.listdir(path): - dir = os.path.join(path, name) - if os.path.isdir(dir): - match = dir_pat1.match(name) - if match is not None: - dirs.append((float(match.group(1)), - match.group(2), dir)) - else: - match = dir_pat2.match(name) - if match is not None: - dirs.append((float(match.group(2)), - match.group(1), dir)) - - # Sort the directories by their index value: - dirs.sort(key=itemgetter(0)) - - # Create the appropriate type of section for each valid directory: - self._subsections = [ - sf.section for sf in [ - SectionFactory(title=title_for(title), - parent=self).trait_set( - path=dir) - for index, title, dir in dirs - ] if sf.section is not None - ] - - def _load_toc(self): - """ Defines the section's subsections by finding matches for the items - defined in the section's table of contents. - """ - toc = self.toc - base_names = [item.split(':', 1)[0] for item in toc] - subsections = [None] * len(base_names) - path = self.path - - # Classify all file names that match a base name in the table of - # contents: - for name in os.listdir(path): - try: - base_name = os.path.splitext(os.path.basename(name))[0] - index = base_names.index(base_name) - if subsections[index] is None: - subsections[index] = [] - subsections[index].append(name) - except: - pass - - # Try to convert each group of names into a section: - for i, names in enumerate(subsections): - - # Only process items for which we found at least one matching file - # name: - if names is not None: - - # Get the title for the section from its table of contents - # entry: - parts = toc[i].split(':', 1) - if len(parts) == 1: - title = title_for(parts[0].strip()) - else: - title = parts[1].strip() - - # Handle an item with one file which is a directory as a normal - # section: - if len(names) == 1: - dir = os.path.join(path, names[0]) - if os.path.isdir(dir): - subsections[i] = SectionFactory(title=title, - parent=self).trait_set( - path=dir).section - continue - - # Otherwise, create a section from the list of matching files: - subsections[i] = SectionFactory(title=title, - parent=self, - files=names).trait_set( - path=path).section - - # Set the subsections to the non-None values that are left: - self._subsections = [subsection for subsection in subsections - if subsection is not None] - -#------------------------------------------------------------------------- -# 'Lecture' class: -#------------------------------------------------------------------------- - - -class Lecture(ASection): - """ Defines a lecture, which is a section of a tutorial with descriptive - information, but no associated Python code. Can be used to provide - course overviews, introductory sections, or lead-ins to follow-on - lessons or labs. - """ - - #-- Trait Definitions----------------------------------------------------- - - # The list of descriptive items for the lecture: - descriptions = List(ATutorialItem) - - # This section can be executed (override): - is_runnable = False - - #-- Traits View Definitions ---------------------------------------------- - - view = View( - Item('descriptions', - style='custom', - show_label=False, - editor=list_editor - ), - id='enthought.tutor.lecture' - ) - -#------------------------------------------------------------------------- -# 'LabHandler' class: -#------------------------------------------------------------------------- - - -class LabHandler(Handler): - """ Defines the controller functions for the Lab view. - """ - - def init(self, info): - """ Handles initialization of the view. - """ - # Run the associated Python code if the 'auto-run' feature is enabled: - if info.object.auto_run: - info.object.run_code() - -#------------------------------------------------------------------------- -# 'Lab' class: -#------------------------------------------------------------------------- - - -class Lab(ASection): - """ Defines a lab, which is a section of a tutorial with only Python code. - This type of section might typically follow a lecture which introduced - the code being worked on in the lab. - """ - - #-- Trait Definitions----------------------------------------------------- - - # The set-up code (if any) for the lab: - setup = Instance(CodeItem) - - # The list of code items for the lab: - snippets = List(CodeItem) - - # The list of visible code items for the lab: - visible_snippets = Property(depends_on='visible', cached=True) - - # The currently selected snippet: - snippet = Instance(CodeItem) - - # Should normally hidden code items be shown? - visible = Bool(False) - - # The dictionary containing the items from the Python code execution: - values = Dict # Any( {} ) - - # The run Python code button: - run = Button(image=ImageResource('run'), height_padding=1) - - # User error message: - message = Str - - # The output produced while the program is running: - output = Str - - # The current demo pane (if any): - demo = Instance(DemoPane, ()) - - #-- Traits View Definitions ---------------------------------------------- - - view = View( - VSplit( - VGroup( - Item('visible_snippets', - style='custom', - show_label=False, - editor=snippet_editor - ), - HGroup( - Item('run', - style='custom', - show_label=False, - tooltip='Run the Python code' - ), - '_', - Item('message', - springy=True, - show_label=False, - editor=TitleEditor() - ), - ), - ), - Tabbed( - Item('values', - id='values_1', - label='Shell', - editor=ShellEditor(share=True), - dock='tab', - export='DockWindowShell' - ), - Item('values', - id='values_2', - editor=ValueEditor(), - dock='tab', - export='DockWindowShell' - ), - Item('output', - style='readonly', - editor=CodeEditor(show_line_numbers=False, - selected_color=0xFFFFFF), - dock='tab', - export='DockWindowShell' - ), - Item('demo', - id='demo', - style='custom', - resizable=True, - dock='tab', - export='DockWindowShell' - ), - show_labels=False, - ), - id='splitter', - ), - id='enthought.tutor.lab', - handler=LabHandler - ) - - #-- Event Handlers ------------------------------------------------------- - - def _run_changed(self): - """ Runs the current set of snippet code. - """ - self.run_code() - - #-- Property Implementations --------------------------------------------- - - @cached_property - def _get_visible_snippets(self): - """ Returns the list of code items that are currently visible. - """ - if self.visible: - return self.snippets - - return [snippet for snippet in self.snippets if (not snippet.hidden)] - - #-- Public Methods ------------------------------------------------------- - - def run_code(self): - """ Runs all of the code snippets associated with the section. - """ - # Reconstruct the lab code from the current set of code snippets: - start_line = 1 - module = '' - for snippet in self.snippets: - snippet.start_line = start_line - module = '%s\n\n%s' % (module, snippet.content) - start_line += (snippet.content.count('\n') + 2) - - # Reset any syntax error and message log values: - self.message = self.output = '' - - # Redirect standard out and error to the message log: - stdout, stderr = sys.stdout, sys.stderr - sys.stdout = sys.stderr = StdOut(self) - - try: - try: - # Get the execution context dictionary: - values = self.values - - # Clear out any special variables defined by the last run: - for name in ('demo', 'popup'): - if isinstance(values.get(name), HasTraits): - del values[name] - - # Execute the current lab code: - exec(module[2:], values, values) - - # fixme: Hack trying to update the Traits UI view of the dict. - self.values = {} - self.values = values - - # Handle a 'demo' value being defined: - demo = values.get('demo') - if not isinstance(demo, HasTraits): - demo = NoDemo() - self.demo.demo = demo - - # Handle a 'popup' value being defined: - popup = values.get('popup') - if isinstance(popup, HasTraits): - popup.edit_traits(kind='livemodal') - - except SyntaxError as excp: - # Convert the line number of the syntax error from one in the - # composite module to one in the appropriate code snippet: - line = excp.lineno - if line is not None: - snippet = self.snippets[0] - for s in self.snippets: - if s.start_line > line: - break - snippet = s - line -= (snippet.start_line - 1) - - # Highlight the line in error: - snippet.selected_line = line - - # Select the correct code snippet: - self.snippet = snippet - - # Display the syntax error message: - self.message = '%s in column %s of line %s' % ( - excp.msg.capitalize(), excp.offset, line) - else: - # Display the syntax error message without line # info: - self.message = excp.msg.capitalize() - except: - import traceback - traceback.print_exc() - finally: - # Restore standard out and error to their original values: - sys.stdout, sys.stderr = stdout, stderr - -#------------------------------------------------------------------------- -# 'Lesson' class: -#------------------------------------------------------------------------- - - -class Lesson(Lab): - """ Defines a lesson, which is a section of a tutorial with both descriptive - information and associated Python code. - """ - - #-- Trait Definitions----------------------------------------------------- - - # The list of descriptive items for the lesson: - descriptions = List(ATutorialItem) - - #-- Traits View Definitions ---------------------------------------------- - - view = View( - HSplit( - Item('descriptions', - label='Lesson', - style='custom', - show_label=False, - dock='horizontal', - editor=list_editor - ), - VSplit( - VGroup( - Item('visible_snippets', - style='custom', - show_label=False, - editor=snippet_editor - ), - HGroup( - Item('run', - style='custom', - show_label=False, - tooltip='Run the Python code' - ), - '_', - Item('message', - springy=True, - show_label=False, - editor=TitleEditor() - ), - '_', - Item('visible', - label='View hidden sections' - ) - ), - label='Lab', - dock='horizontal' - ), - Tabbed( - Item('values', - id='values_1', - label='Shell', - editor=ShellEditor(share=True), - dock='tab', - export='DockWindowShell' - - ), - Item('values', - id='values_2', - editor=ValueEditor(), - dock='tab', - export='DockWindowShell' - ), - Item('output', - style='readonly', - editor=CodeEditor(show_line_numbers=False, - selected_color=0xFFFFFF), - dock='tab', - export='DockWindowShell' - ), - Item('demo', - id='demo', - style='custom', - resizable=True, - dock='tab', - export='DockWindowShell' - ), - show_labels=False, - ), - label='Lab', - dock='horizontal' - ), - id='splitter', - ), - id='enthought.tutor.lesson', - handler=LabHandler - ) - -#------------------------------------------------------------------------- -# 'Demo' class: -#------------------------------------------------------------------------- - - -class Demo(Lesson): - """ Defines a demo, which is a section of a tutorial with both descriptive - information and associated Python code which is executed but not - shown. - """ - - #-- Traits View Definitions ---------------------------------------------- - - view = View( - HSplit( - Item('descriptions', - label='Lesson', - style='custom', - show_label=False, - dock='horizontal', - editor=list_editor - ), - Item('demo', - id='demo', - style='custom', - show_label=False, - resizable=True, - dock='horizontal', - export='DockWindowShell' - ), - id='splitter', - ), - id='enthought.tutor.demo', - handler=LabHandler - ) - -#------------------------------------------------------------------------- -# 'SectionFactory' class: -#------------------------------------------------------------------------- - - -class SectionFactory(HasPrivateTraits): - """ Defines a class that creates Lecture, Lesson or Lab sections (or None), - based on the content of a specified directory. None is returned if the - directory does not contain any recognized files. - """ - - #-- Traits Definitions --------------------------------------------------- - - # The path the section is to be created for: - path = Directory - - # The list of files contained in the section: - files = List(Str) - - # The parent of the section being created: - parent = Instance(ASection) - - # The section created from the path: - section = Instance(ASection) - - # The title for the section: - title = Str - - # The optional table of contents for the section: - toc = List(Str) - - # The list of descriptive items for the section: - descriptions = List(ADescriptionItem) - - # The list of code snippet items for the section: - snippets = List(CodeItem) - - # The path to the CSS style sheet for the section: - css_path = Property - - # Should the Python code be automatically executed on start-up? - auto_run = Bool(False) - - #-- Event Handlers ------------------------------------------------------- - - def _path_changed(self, path): - """ Creates the appropriate section based on the value of the path. - """ - # Get the list of files to process: - files = self.files - if len(files) == 0: - # If none were specified, then use all files in the directory: - files = os.listdir(path) - - # Process the description file (if any) first: - for name in files: - if os.path.splitext(name)[1] == '.desc': - self._add_desc_item(os.path.join(path, name)) - break - - # Try to convert each file into one or more 'xxxItem' objects: - toc = [item.split(':', 1)[0].strip() for item in self.toc] - for name in files: - file_name = os.path.join(path, name) - - # Only process the ones that are actual files: - if os.path.isfile(file_name): - - # Use the file extension to determine the file's type: - root, ext = os.path.splitext(name) - if (root not in toc) and (len(ext) > 1): - - # If we have a handler for the file type, invoke it: - method = getattr(self, '_add_%s_item' % ext[1:].lower(), - None) - if method is not None: - method(file_name) - - # Based on the type of items created (if any), create the corresponding - # type of section: - if len(self.descriptions) > 0: - if len(self.snippets) > 0: - if len([snippet for snippet in self.snippets - if (not snippet.hidden)]) > 0: - self.section = Lesson( - title=self.title, - path=path, - toc=self.toc, - parent=self.parent, - descriptions=self.descriptions, - snippets=self.snippets, - auto_run=self.auto_run - ) - else: - self.section = Demo( - title=self.title, - path=path, - toc=self.toc, - parent=self.parent, - descriptions=self.descriptions, - snippets=self.snippets, - auto_run=True - ) - else: - self.section = Lecture( - title=self.title, - path=path, - toc=self.toc, - parent=self.parent, - descriptions=self.descriptions - ) - elif len(self.snippets) > 0: - self.section = Lab( - title=self.title, - path=path, - toc=self.toc, - parent=self.parent, - snippets=self.snippets, - auto_run=self.auto_run - ) - else: - # No descriptions or code snippets were found. Create a lecture - # anyway: - section = Lecture( - title=self.title, - path=path, - toc=self.toc, - parent=self.parent - ) - - # If the lecture has subsections, then return the lecture and add - # a default item containing a description of the subsections of the - # lecture: - if len(section.subsections) > 0: - self._create_html_item(path=path, content=DefaultLecture % ('\n'.join( - ['