diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..791f075 --- /dev/null +++ b/.flake8 @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 119 diff --git a/docs/_static/README.txt b/docs/_static/README.txt new file mode 100644 index 0000000..c0ee0ee --- /dev/null +++ b/docs/_static/README.txt @@ -0,0 +1 @@ +File to keep the empty `_static` directory in version control. diff --git a/docs/conf.py b/docs/conf.py index a8acffc..92acaa5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -11,17 +11,20 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os +import os # noqa: F401 +import sys # noqa: F401 + +import galley # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) +#sys.path.insert(0, os.path.abspath('.')) # noqa: E265 # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +#needs_sphinx = '1.0' # noqa: E265 # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. @@ -34,57 +37,57 @@ source_suffix = '.rst' # The encoding of source files. -#source_encoding = 'utf-8-sig' +#source_encoding = 'utf-8-sig' # noqa: E265 # The master toctree document. master_doc = 'index' # General information about the project. -project = u'Galley' -copyright = u'2013, Russell Keith-Magee' +project = 'Galley' +copyright = '2013, Russell Keith-Magee' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = '0.1' +version = '.'.join(str(num) for num in galley.NUM_VERSION[:3] if isinstance(num, int)) # The full version, including alpha/beta/rc tags. -release = '0.1' +release = galley.VERSION # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +#language = None # noqa: E265 # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +#today = '' # noqa: E265 # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +#today_fmt = '%B %d, %Y' # noqa: E265 # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None +#default_role = None # noqa: E265 # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +#add_function_parentheses = True # noqa: E265 # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +#add_module_names = True # noqa: E265 # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +#show_authors = False # noqa: E265 # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +#modindex_common_prefix = [] # noqa: E265 # -- Options for HTML output --------------------------------------------------- @@ -96,26 +99,26 @@ # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +#html_theme_options = {} # noqa: E265 # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +#html_theme_path = [] # noqa: E265 # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +#html_title = None # noqa: E265 # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +#html_short_title = None # noqa: E265 # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +#html_logo = None # noqa: E265 # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +#html_favicon = None # noqa: E265 # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, @@ -124,44 +127,44 @@ # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +#html_last_updated_fmt = '%b %d, %Y' # noqa: E265 # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +#html_use_smartypants = True # noqa: E265 # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +#html_sidebars = {} # noqa: E265 # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +#html_additional_pages = {} # noqa: E265 # If false, no module index is generated. -#html_domain_indices = True +#html_domain_indices = True # noqa: E265 # If false, no index is generated. -#html_use_index = True +#html_use_index = True # noqa: E265 # If true, the index is split into individual pages for each letter. -#html_split_index = False +#html_split_index = False # noqa: E265 # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +#html_show_sourcelink = True # noqa: E265 # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +#html_show_sphinx = True # noqa: E265 # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +#html_show_copyright = True # noqa: E265 # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +#html_use_opensearch = '' # noqa: E265 # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +#html_file_suffix = None # noqa: E265 # Output file base name for HTML help builder. htmlhelp_basename = 'galleydoc' @@ -170,42 +173,42 @@ # -- Options for LaTeX output -------------------------------------------------- latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', + # The paper size ('letterpaper' or 'a4paper'). + #'papersize': 'letterpaper', # noqa: E265 -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', + # The font size ('10pt', '11pt' or '12pt'). + #'pointsize': '10pt', # noqa: E265 -# Additional stuff for the LaTeX preamble. -#'preamble': '', + # Additional stuff for the LaTeX preamble. + #'preamble': '', # noqa: E265 } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'galley.tex', u'Galley Documentation', - u'Russell Keith-Magee', 'manual'), + ('index', 'galley.tex', 'Galley Documentation', + 'Russell Keith-Magee', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +#latex_logo = None # noqa: E265 # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +#latex_use_parts = False # noqa: E265 # If true, show page references after internal links. -#latex_show_pagerefs = False +#latex_show_pagerefs = False # noqa: E265 # If true, show URL addresses after external links. -#latex_show_urls = False +#latex_show_urls = False # noqa: E265 # Documents to append as an appendix to all manuals. -#latex_appendices = [] +#latex_appendices = [] # noqa: E265 # If false, no module index is generated. -#latex_domain_indices = True +#latex_domain_indices = True # noqa: E265 # -- Options for manual page output -------------------------------------------- @@ -213,12 +216,12 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'galley', u'Galley Documentation', - [u'Russell Keith-Magee'], 1) + ('index', 'galley', 'Galley Documentation', + ['Russell Keith-Magee'], 1) ] # If true, show URL addresses after external links. -#man_show_urls = False +#man_show_urls = False # noqa: E265 # -- Options for Texinfo output ------------------------------------------------ @@ -227,16 +230,16 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'galley', u'Galley Documentation', - u'Russell Keith-Magee', 'Galley', 'GUI tool to assist in drafting documentation.', + ('index', 'galley', 'Galley Documentation', + 'Russell Keith-Magee', 'Galley', 'GUI tool to assist in drafting documentation.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. -#texinfo_appendices = [] +#texinfo_appendices = [] # noqa: E265 # If false, no module index is generated. -#texinfo_domain_indices = True +#texinfo_domain_indices = True # noqa: E265 # How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' \ No newline at end of file +#texinfo_show_urls = 'footnote' # noqa: E265 diff --git a/docs/index.rst b/docs/index.rst index 2a53aab..cabe9b5 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -17,10 +17,11 @@ Problems under Ubuntu/Debian ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Deian and Ubuntu's packaging of Python omits the ``idlelib`` library from it's -base packge. If you're using Python 2.7 on Ubuntu 13.04, you can install +base packge. If you're using Python on Ubuntu, you can install ``idlelib`` by running:: - $ sudo apt-get install idle-python2.7 + $ sudo apt-get update + $ sudo apt-get install idle3 For other versions of Python, Ubuntu and Debian, you'll need to adjust this as appropriate. @@ -31,7 +32,7 @@ Problems under Windows If you're running Galley in a virtualenv, you'll need to set an environment variable so that Galley can find the TCL graphics library:: - $ set TCL_LIBRARY=c:\Python27\tcl\tcl8.5 + $ set TCL_LIBRARY=c:\Python35\tcl\tcl8.6 You'll need to adjust the exact path to reflect your local Python install. You may find it helpful to put this line in the ``activate.bat`` script diff --git a/docs/releases.rst b/docs/releases.rst new file mode 100644 index 0000000..54f6dfb --- /dev/null +++ b/docs/releases.rst @@ -0,0 +1,12 @@ +Release History +=============== + +0.1.1 - In development +---------------------- + +Drop Python 2 support. + +0.1.0 - In development +---------------------- + +Initial public release. diff --git a/docs/releases.txt b/docs/releases.txt deleted file mode 100644 index 276df8e..0000000 --- a/docs/releases.txt +++ /dev/null @@ -1,7 +0,0 @@ -Release History -=============== - -0.1.0 - In development ----------------------- - -Initial public release. \ No newline at end of file diff --git a/galley/__init__.py b/galley/__init__.py index a6963db..667ef49 100644 --- a/galley/__init__.py +++ b/galley/__init__.py @@ -22,9 +22,14 @@ def get_git_changeset(): import subprocess repo_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - git_log = subprocess.Popen('git log --pretty=format:%ct --quiet -1 HEAD', - stdout=subprocess.PIPE, stderr=subprocess.PIPE, - shell=True, cwd=repo_dir, universal_newlines=True) + git_log = subprocess.Popen( + 'git log --pretty=format:%ct --quiet -1 HEAD', + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + shell=True, + cwd=repo_dir, + universal_newlines=True + ) timestamp = git_log.communicate()[0] try: timestamp = datetime.datetime.utcfromtimestamp(int(timestamp)) @@ -63,4 +68,6 @@ def part_string(part, i): s = '.' + s return s -VERSION = "".join(part_string(nv, i) for i, nv in enumerate(NUM_VERSION)) \ No newline at end of file + +VERSION = "".join(part_string(nv, i) for i, nv in enumerate(NUM_VERSION)) +__version__ = VERSION diff --git a/galley/__main__.py b/galley/__main__.py index 0b5776d..da4fa2d 100644 --- a/galley/__main__.py +++ b/galley/__main__.py @@ -1,17 +1,18 @@ ''' This is the main entry point for the Galley GUI. ''' -from Tkinter import * - import argparse +from tkinter import Tk from galley import VERSION from galley.view import MainWindow def main(): - parser = argparse.ArgumentParser( - description='GUI tool to assist in drafting documentation.', + parser = argparse.ArgumentParser(description='GUI tool to assist in drafting documentation.') + parser.add_argument( + '--version', + action='version', version=VERSION ) @@ -39,5 +40,6 @@ def main(): except KeyboardInterrupt: view.cmd_quit() + if __name__ == '__main__': - main() \ No newline at end of file + main() diff --git a/galley/monitor.py b/galley/monitor.py index 172b4df..ad3ff08 100644 --- a/galley/monitor.py +++ b/galley/monitor.py @@ -1,7 +1,6 @@ -from collections import namedtuple import os import sys - +from collections import namedtuple ###################################################################### # Output message types @@ -13,7 +12,7 @@ def project_visitor(on_dir, on_file): """A filesystem visitor factory - Returns os.path.walk-compatible visitor functions that will + Returns os.walk-compatible visitor functions that will invoke on_dir(dirname, data) and on_file(dirname, filename, data) whenever a new directory or file is detected, respectively. """ @@ -90,12 +89,12 @@ def gather_file(dirname, filename, monitor): def file_monitor(base_path, stop_event, output_queue): "The actual thread method that checks for file modifications" monitor = Monitor() - os.path.walk(base_path, project_visitor(gather_dir, gather_file), monitor) + os.walk(base_path, project_visitor(gather_dir, gather_file), monitor) while not stop_event.is_set(): stop_event.wait(1.0) monitor.reset() - os.path.walk(base_path, project_visitor(gather_dir, gather_file), monitor) + os.walk(base_path, project_visitor(gather_dir, gather_file), monitor) if monitor.new_files or monitor.modified_files: output_queue.put(FileChange(monitor.new_files, monitor.modified_files)) diff --git a/galley/view.py b/galley/view.py index f024314..d23014f 100644 --- a/galley/view.py +++ b/galley/view.py @@ -5,37 +5,24 @@ """ import os import threading -from Tkinter import * -from tkFont import * -from ttk import * -import tkMessageBox -import urlparse +import tkinter.messagebox as tkMessageBox import webbrowser - -try: - from Queue import Queue, Empty -except ImportError: - from queue import Queue, Empty # python 3.x +from queue import Empty, Queue +from tkinter import (ACTIVE, DISABLED, END, FALSE, HORIZONTAL, NORMAL, + VERTICAL, WORD, E, IntVar, Menu, N, S, StringVar, W) +from tkinter.ttk import (Button, Frame, Label, PanedWindow, Progressbar, + Scrollbar, Sizegrip) +from urllib.parse import urlparse from tkreadonly import ReadOnlyText -from galley import VERSION, NUM_VERSION -from galley.widgets import SimpleHTMLView, FileView -from galley.monitor import file_monitor, FileChange -from galley.worker import ( - sphinx_worker, - ReloadConfig, - BuildAll, - BuildSpecific, - Quit, - Output, - WarningOutput, - Progress, - InitializationStart, - InitializationEnd, - BuildStart, - BuildEnd -) +from galley import NUM_VERSION, VERSION +from galley.monitor import FileChange, file_monitor +from galley.widgets import FileView, SimpleHTMLView +from galley.worker import (BuildAll, BuildEnd, BuildSpecific, BuildStart, + InitializationEnd, InitializationStart, Output, + Progress, Quit, ReloadConfig, WarningOutput, + sphinx_worker) def filename_normalizer(base_path): @@ -53,7 +40,6 @@ def _normalizer(filename): return _normalizer - class MainWindow(object): def __init__(self, root, options): ''' @@ -118,13 +104,19 @@ def __init__(self, root, options): # Set up a background worker thread to build docs. self.work_queue = Queue() self.results_queue = Queue() - self.worker_thread = threading.Thread(target=sphinx_worker, args=(os.path.join(self.base_path, 'docs'), self.work_queue, self.results_queue)) + self.worker_thread = threading.Thread( + target=sphinx_worker, + args=(os.path.join(self.base_path, 'docs'), self.work_queue, self.results_queue) + ) self.worker_thread.daemon = True self.worker_thread.start() # Set up a background monitor thread. self.stop_event = threading.Event() - self.monitor_thread = threading.Thread(target=file_monitor, args=(os.path.join(self.base_path, 'docs'), self.stop_event, self.results_queue)) + self.monitor_thread = threading.Thread( + target=file_monitor, + args=(os.path.join(self.base_path, 'docs'), self.stop_event, self.results_queue) + ) self.monitor_thread.daemon = True self.monitor_thread.start() @@ -132,12 +124,12 @@ def __init__(self, root, options): # as fast as we need to update to match human visual acuity) self.root.after(40, self.handle_background_tasks) - ###################################################### # Internal GUI layout methods. ###################################################### def _setup_menubar(self): + # Menubar self.menubar = Menu(self.root) @@ -180,7 +172,11 @@ def _setup_button_toolbar(self): self.forward_button = Button(self.toolbar, text='▶', command=self.cmd_forward, state=DISABLED) self.forward_button.grid(column=1, row=0) - self.rebuild_all_button = Button(self.toolbar, text='Rebuild all', command=self.cmd_rebuild_all, state=DISABLED) + self.rebuild_all_button = Button( + self.toolbar, + text='Rebuild all', + command=self.cmd_rebuild_all, + state=DISABLED) self.rebuild_all_button.grid(column=2, row=0) self.rebuild_file_button = Button(self.toolbar, text='Rebuild', command=self.cmd_rebuild_file, state=DISABLED) @@ -219,7 +215,11 @@ def _setup_project_file_tree(self): self.project_file_tree_frame = Frame(self.content) self.project_file_tree_frame.grid(column=0, row=0, sticky=(N, S, E, W)) - self.project_file_tree = FileView(self.project_file_tree_frame, normalizer=self.filename_normalizer, root=os.path.join(self.base_path, 'docs')) + self.project_file_tree = FileView( + self.project_file_tree_frame, + normalizer=self.filename_normalizer, + root=os.path.join(self.base_path, 'docs') + ) self.project_file_tree.grid(column=0, row=0, sticky=(N, S, E, W)) # # The tree's vertical scrollbar @@ -262,7 +262,14 @@ def _setup_html_area(self): self.warnings = ReadOnlyText(self.html_frame, height=6) self.warnings.grid(column=1, row=2, pady=5, columnspan=2, sticky=(N, S, E, W,)) - self.warnings.tag_configure('warning', wrap=WORD, lmargin1=5, lmargin2=20, spacing1=2, spacing3=2) + self.warnings.tag_configure( + 'warning', + wrap=WORD, + lmargin1=5, + lmargin2=20, + spacing1=2, + spacing3=2 + ) self.warnings_scrollbar = Scrollbar(self.html_frame, orient=VERTICAL) self.warnings_scrollbar.grid(column=2, row=2, pady=5, sticky=(N, S)) self.warnings.config(yscrollcommand=self.warnings_scrollbar.set) @@ -291,7 +298,13 @@ def _setup_status_bar(self): # Progress bar; initially started, because we don't know how long initialization will take. self.progress_value = IntVar() - # self.progress = Progressbar(self.statusbar, orient=HORIZONTAL, length=200, mode='indeterminate', maximum=100, variable=self.progress_value) + # self.progress = Progressbar( + # self.statusbar, + # orient=HORIZONTAL, + # length=200, + # mode='indeterminate', + # maximum=100, + # variable=self.progress_value) self.progress = Progressbar(self.statusbar, orient=HORIZONTAL, length=200, mode='indeterminate') self.progress.grid(column=1, row=0, sticky=(W, E)) @@ -318,7 +331,10 @@ def show_file(self, filename, anchor=None): """ # TEMP: Rework into HTML view path, ext = os.path.splitext(filename) - compiled_filename = path.replace(os.path.join(self.base_path, 'docs'), os.path.join(self.base_path, 'docs', '_build', 'json')) + '.fjson' + compiled_filename = path.replace( + os.path.join(self.base_path, 'docs'), + os.path.join(self.base_path, 'docs', '_build', 'json') + ) + '.fjson' # Set the filename label for the current file self.current_file.set(self.filename_normalizer(filename)) @@ -352,7 +368,9 @@ def show_file(self, filename, anchor=None): self._traversing_history = False except IOError: - tkMessageBox.showerror(message='%s has not been compiled to HTML' % self.filename_normalizer(filename)) + tkMessageBox.showerror( + message='%s has not been compiled to HTML' % self.filename_normalizer(filename) + ) def _show_warnings(self, filename): "Show the warnings output panel" @@ -362,16 +380,16 @@ def _show_warnings(self, filename): # First, the global warnings for (lineno, warning) in self.warning_output.get(None, []): if lineno: - warnings.append(u'○ Line %s: %s' % (lineno, warning)) + warnings.append('○ Line %s: %s' % (lineno, warning)) else: - warnings.append(u'○ %s' % warning) + warnings.append('○ %s' % warning) # Then, the file specific warnings. for (lineno, warning) in self.warning_output.get(filename, []): if lineno: - warnings.append(u'● Line %s: %s' % (lineno, warning)) + warnings.append('● Line %s: %s' % (lineno, warning)) else: - warnings.append(u'● %s' % warning) + warnings.append('● %s' % warning) # If there are warnings, show the widget, and populate it. # Otherwise, hide the widget. @@ -380,11 +398,9 @@ def _show_warnings(self, filename): self.warnings.insert(END, warning, 'warning') self.warnings.insert(END, '\n') - ###################################################### # TK Main loop ###################################################### - def mainloop(self): self.root.mainloop() @@ -431,7 +447,9 @@ def handle_background_tasks(self): self.source_extension = result.extension # Set the initial file - self.project_file_tree.selection_set(os.path.join(self.base_path, 'docs', 'index' + self.source_extension)) + self.project_file_tree.selection_set( + os.path.join(self.base_path, 'docs', 'index' + self.source_extension) + ) elif isinstance(result, BuildStart): # Build start; set up the progress bar, set initial progress to 0 @@ -629,7 +647,7 @@ def on_file_selected(self, event): def on_link_click(self, event): "When a link is clicked, open the new URL" - url_parts = urlparse.urlparse(event.url) + url_parts = urlparse(event.url) if url_parts.netloc and url_parts.scheme: webbrowser.open_new(event.url) else: @@ -643,4 +661,3 @@ def on_link_click(self, event): self.project_file_tree.selection_set(index_filename) else: tkMessageBox.showerror(message="Couldn't find %s" % self.filename_normalizer(filename)) - diff --git a/galley/widgets.py b/galley/widgets.py index f29c1cb..fb223a4 100644 --- a/galley/widgets.py +++ b/galley/widgets.py @@ -1,8 +1,9 @@ # -*- coding: UTF-8 -*- import json import os -from ttk import * -from Tkinter import * +import sys +from tkinter import ALL, SW, VERTICAL, Canvas, E, Frame, N, S, W +from tkinter.ttk import Scrollbar, Treeview from xml.etree import ElementTree as et from tkreadonly import normalize_sequence @@ -205,7 +206,7 @@ def __init__(self, node): style = STYLE.get(node.tag, {}) for key, value in style.items(): - setattr(self, key.replace('-','_'), value) + setattr(self, key.replace('-', '_'), value) def __getattr__(self, attr): "Silence all AttributeErrors" @@ -249,6 +250,7 @@ def extra(self): return {'href': self.node.attrib['href']} return {} + class RenderContext(object): INHERITED_PROPERTIES = set([ 'color', @@ -276,7 +278,7 @@ def __getattr__(self, attr): value = None index = -1 if attr in RenderContext.INHERITED_PROPERTIES: - while value is None or value is 'inherit': + while value is None or value == 'inherit': value = getattr(self.frames[index], attr) index = index - 1 else: @@ -354,7 +356,7 @@ def push(self, frame): self.frames.append(frame) self._apply(1) - # print "START CONTEXT", frame.node, self.origin + # print('START CONTEXT', frame.node, self.origin) def pop(self): # If we're leaving a block element, then: @@ -373,7 +375,7 @@ def pop(self): def clear(self): # Adjust the y coordinate to match the maximum line height - # print "CLEAR", self.origin, self.line_box, [f.node for f in self.frames] + # print('CLEAR', self.origin, self.line_box, [f.node for f in self.frames]) for offset, obj in self.line_box: self._widget.coords( obj, @@ -396,14 +398,15 @@ def __init__(self, *args, **kwargs): self.document = None # The Main Text Widget - self.html = Canvas(self, + self.html = Canvas( + self, # background=self.style.background_color, ) self.html.grid(column=0, row=0, sticky=(N, S, E, W)) # Handle canvas resize events by redrawing content. - self.html.bind("", self.redraw) + self.html.bind('', self.redraw) # Set up storage for ID anchors self.element_id = {} @@ -436,9 +439,9 @@ def __init__(self, *args, **kwargs): self.html.config(yscrollcommand=self.vScrollbar.set) self.vScrollbar.config(command=self.html.yview) - self.html.bind_all("", self._on_mousewheel) - self.html.bind_all("<4>", self._on_mousewheel) - self.html.bind_all("<5>", self._on_mousewheel) + self.html.bind_all('', self._on_mousewheel) + self.html.bind_all('<4>', self._on_mousewheel) + self.html.bind_all('<5>', self._on_mousewheel) # Configure the weights for the grid. # All the weight goes to the code view. @@ -446,9 +449,7 @@ def __init__(self, *args, **kwargs): self.columnconfigure(1, weight=0) self.rowconfigure(0, weight=1) - def _insert_text(self, text, context): - max_width = self.html.winfo_width() - context.origin[0] - context.limits[0] start = 0 @@ -456,7 +457,17 @@ def _insert_text(self, text, context): words = text.split(' ') - # print "INSERT", text, context.node.tag, context.font, context.origin, context.x_offset, context.y_offset, max_width, context.tags + # print( + # 'INSERT', + # text, + # context.node.tag, + # context.font, + # context.origin, + # context.x_offset, + # context.y_offset, + # max_width, + # context.tags + # ) widget = self.html.create_text( context.origin[0] + context.x_offset, context.origin[1] + context.y_offset, @@ -485,7 +496,7 @@ def _insert_text(self, text, context): dirty = False while end <= len(words): - # print 'try', context.x_offset, context.y_offset, ' '.join(words[start:end]) + # print('try', context.x_offset, context.y_offset, ' '.join(words[start:end])) self.html.itemconfig(widget, text=' '.join(words[start:end])) # Get the dimensions of the rendered text. @@ -493,7 +504,7 @@ def _insert_text(self, text, context): height = y2 - y width = x2 - x - # print "END/HEIGHT", context.x_offset + width, height + # print('END/HEIGHT', context.x_offset + width, height) if height > context.line_box_height: context.line_box_height = height @@ -502,9 +513,10 @@ def _insert_text(self, text, context): # if start == end - 1: # raise WindowTooSmallException() - # print " LINE OVERRUN; output:",' '.join(words[start:end - 1]), 'width',width + # print(' LINE OVERRUN; output:',' '.join(words[start:end - 1]), 'width',width) # We've exceeded the line length. Output the line. - self.html.itemconfig(widget, + self.html.itemconfig( + widget, text=' '.join(words[start:end - 1]) ) @@ -513,10 +525,10 @@ def _insert_text(self, text, context): context.line_box.append(((context.x_offset, context.y_offset), widget)) start = end - 1 end = start - # print "Remainder", words[start:], start, end, len(words) + # print('Remainder', words[start:], start, end, len(words)) # Clear the line. - # print "CLEAR BY FULL LINE BOX" + # print('CLEAR BY FULL LINE BOX') context.clear() # Set up a new empty text container. @@ -543,7 +555,7 @@ def _insert_text(self, text, context): end = end + 1 if dirty: - # print " BLOCK FITS; output:",' '.join(words[start:end - 1]), 'width',width + # print(' BLOCK FITS; output:', ' '.join(words[start:end - 1]), 'width', width) context.line_box.append(((context.x_offset, context.y_offset), widget)) context.x_offset += width @@ -554,9 +566,9 @@ def _display(self, node, context): if context.white_space == 'pre': normalized = node.text.strip() else: - normalized = node.text.replace('\n',' ').strip() + normalized = node.text.replace('\n', ' ').strip() if normalized: - # print " ", node.tag, 'text', normalized.split() + # print(' ', node.tag, 'text', normalized.split()) self._insert_text(normalized, context) for child in node: @@ -568,9 +580,9 @@ def _display(self, node, context): if context.white_space == 'pre': normalized = node.tail.strip() else: - normalized = node.tail.replace('\n',' ').strip() + normalized = node.tail.replace('\n', ' ').strip() if normalized: - # print " ", node.tag, 'tail', normalized.split() + # print(' ', node.tag, 'tail', normalized.split()) self._insert_text(normalized, context) def _on_mousewheel(self, event): @@ -584,7 +596,7 @@ def _on_mousewheel(self, event): else: delta = -1 * event.delta / 120 - self.html.yview_scroll(delta, "units") + self.html.yview_scroll(delta, 'units') @property def filename(self): @@ -613,11 +625,11 @@ def redraw(self, event=None): self.html.delete(ALL) context = RenderContext(self.html) self._display(self.document, context) - # print "CLEAR BY END OF DRAW" + # print('CLEAR BY END OF DRAW') context.clear() self.html.config(scrollregion=self.html.bbox(ALL)) except WindowTooSmallException: - print "Window too small to render." + print('Window too small to render.') def refresh(self): "Force a refresh of the file currently in the view" diff --git a/galley/worker.py b/galley/worker.py index 3c0b3fb..3be73d8 100644 --- a/galley/worker.py +++ b/galley/worker.py @@ -1,10 +1,9 @@ -from collections import namedtuple -import re import os +import re +from collections import namedtuple from sphinx.application import Sphinx - ###################################################################### # Command types ###################################################################### @@ -92,9 +91,11 @@ def emit(self, content): """ self.queue.put(Output(message=content)) + SIMPLE_PROGRESS_RE = re.compile(r'(.+)\.\.\.$') PERCENT_PROGRESS_RE = re.compile(r'([\w\s]+)\.\.\. \[([\s\d]{3})\%\] (.+)') + class SphinxStatusHandler(ANSIOutputHandler): "A Sphinx output handler for normal status update, stripping ANSI codes." def __init__(self, *args, **kwargs): @@ -172,9 +173,19 @@ def sphinx_worker(base_path, work_queue, output_queue): output_queue.put(InitializationStart()) - sphinx = Sphinx(srcdir, confdir, outdir, doctreedir, buildername, - confoverrides, status, warning, freshenv, - warningiserror, tags) + sphinx = Sphinx( + srcdir, + confdir, + outdir, + doctreedir, + buildername, + confoverrides, + status, + warning, + freshenv, + warningiserror, + tags + ) output_queue.put(InitializationEnd(extension=sphinx.config.source_suffix)) @@ -189,9 +200,19 @@ def sphinx_worker(base_path, work_queue, output_queue): elif isinstance(cmd, ReloadConfig): output_queue.put(InitializationStart()) freshenv = True - sphinx = Sphinx(srcdir, confdir, outdir, doctreedir, buildername, - confoverrides, status, warning, freshenv, - warningiserror, tags) + sphinx = Sphinx( + srcdir, + confdir, + outdir, + doctreedir, + buildername, + confoverrides, + status, + warning, + freshenv, + warningiserror, + tags + ) output_queue.put(InitializationEnd(extension=sphinx.config.source_suffix)) elif isinstance(cmd, BuildAll): diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..21d42af --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +sphinx +tkreadonly diff --git a/requirements_dev.py26.txt b/requirements_dev.py26.txt deleted file mode 100644 index 832d2a5..0000000 --- a/requirements_dev.py26.txt +++ /dev/null @@ -1 +0,0 @@ --r requirements_dev.txt \ No newline at end of file diff --git a/requirements_dev.txt b/requirements_dev.txt index e69de29..ff0f787 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -0,0 +1,3 @@ +-r requirements.txt +flake8 +isort diff --git a/setup.py b/setup.py index 22ac67a..b7c009e 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,6 @@ -#/usr/bin/env python -import sys - +#!/usr/bin/env python from setuptools import setup + from galley import VERSION try: @@ -11,8 +10,6 @@ readme.close() required_pkgs = ['tkreadonly', 'sphinx'] -if sys.version_info < (2, 7): - required_pkgs.append('argparse') setup( name='galley', @@ -37,7 +34,7 @@ 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', - 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 3', 'Topic :: Software Development', 'Topic :: Utilities', ], diff --git a/tests/worker/test_outputhandlers.py b/tests/worker/test_outputhandlers.py index 8a1b0ef..db18011 100644 --- a/tests/worker/test_outputhandlers.py +++ b/tests/worker/test_outputhandlers.py @@ -1,18 +1,9 @@ import unittest +from queue import Queue -try: - from Queue import Queue -except ImportError: - from queue import Queue # python 3.x - -from galley.worker import ( - ANSIOutputHandler, - SphinxStatusHandler, - SphinxWarningHandler, - Output, - WarningOutput, - Progress, -) +from galley.worker import (ANSIOutputHandler, Output, Progress, + SphinxStatusHandler, SphinxWarningHandler, + WarningOutput) class ANSIOutputHandlerTest(unittest.TestCase): @@ -167,12 +158,18 @@ def test_percent_progress(self): output = self.queue.get(block=False) self.assertEqual(output, Output(message="copying downloadable files... [ 80%] /path/to/other_file.bat")) output = self.queue.get(block=False) - self.assertEqual(output, Progress(stage='copying downloadable files', progress=80, context='/path/to/other_file.bat')) + self.assertEqual( + output, + Progress(stage='copying downloadable files', progress=80, context='/path/to/other_file.bat') + ) output = self.queue.get(block=False) self.assertEqual(output, Output(message="copying downloadable files... [100%] /path/to/3rd-file.sh")) output = self.queue.get(block=False) - self.assertEqual(output, Progress(stage='copying downloadable files', progress=100, context='/path/to/3rd-file.sh')) + self.assertEqual( + output, + Progress(stage='copying downloadable files', progress=100, context='/path/to/3rd-file.sh') + ) # Nothing left in the queue self.assertTrue(self.queue.empty()) @@ -190,34 +187,58 @@ def test_global_warning(self): self.handler.flush() output = self.queue.get(block=False) - self.assertEqual(output, WarningOutput(filename=None, lineno=None, message="html_static_path entry '/beeware/galley/docs/_static' does not exist")) + self.assertEqual( + output, + WarningOutput( + filename=None, + lineno=None, + message="html_static_path entry '/beeware/galley/docs/_static' does not exist" + ) + ) # Nothing left in the queue self.assertTrue(self.queue.empty()) - - def test_file_warning(self): + def test_file_warning_missing_for_document(self): "A warning message that mentions a filename is parsed for file name/number content" - self.handler.write("/beeware/galley/docs/internals/newfile.rst:: WARNING: document isn't included in any toctree") + self.handler.write( + "/beeware/galley/docs/internals/newfile.rst:: " + "WARNING: document isn't included in any toctree" + ) self.handler.flush() output = self.queue.get(block=False) - self.assertEqual(output, WarningOutput(filename='/beeware/galley/docs/internals/newfile.rst', lineno=None, message="document isn't included in any toctree")) + self.assertEqual( + output, + WarningOutput( + filename="/beeware/galley/docs/internals/newfile.rst", + lineno=None, + message="document isn't included in any toctree" + ) + ) # Nothing left in the queue self.assertTrue(self.queue.empty()) - - - def test_file_warning(self): + def test_file_warning_for_glob_match(self): "A warning message that mentions a filename is parsed for file name/number content" - self.handler.write("/beeware/galley/docs/index.rst:65: WARNING: toctree glob pattern u'releases' didn't match any documents") + self.handler.write( + "/beeware/galley/docs/index.rst:65: " + "WARNING: toctree glob pattern u'releases' didn't match any documents" + ) self.handler.flush() output = self.queue.get(block=False) - self.assertEqual(output, WarningOutput(filename='/beeware/galley/docs/index.rst', lineno=65, message="toctree glob pattern u'releases' didn't match any documents")) + self.assertEqual( + output, + WarningOutput( + filename="/beeware/galley/docs/index.rst", + lineno=65, + message="toctree glob pattern u'releases' didn't match any documents" + ) + ) # Nothing left in the queue self.assertTrue(self.queue.empty())