diff --git a/docs/conf.py b/docs/conf.py index 74799d52..3827b54f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -33,7 +33,7 @@ needs_sphinx = '1.3' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['codeblockext', 'onlineinclude', 'sphinx.ext.todo', 'sphinx.ext.autodoc', 'sphinxcontrib.httpdomain', - 'sphinx.ext.napoleon'] + 'sphinx.ext.napoleon', 'sphinxcontrib.mermaid'] todo_include_todos = True # Add any paths that contain templates here, relative to this directory. diff --git a/docs/plugins/viewmodels.rst b/docs/plugins/viewmodels.rst index f389ce5c..584b745b 100644 --- a/docs/plugins/viewmodels.rst +++ b/docs/plugins/viewmodels.rst @@ -299,6 +299,130 @@ on your view model, taking a list of all bound view models: }); }) +.. _sec-plugins-viewmodels-livecycle: + +Lifecycle diagrams +------------------ + +.. _sec-plugins-viewmodels-startup: + +Web interface startup +~~~~~~~~~~~~~~~~~~~~~ + +.. mermaid:: + + sequenceDiagram + participant Main + participant onServerConnect + participant fetchSettings + participant bindViewModels + participant DataUpdater + participant LoginStateViewModel + + Note right of DataUpdater: connectCallback = undefined + + activate Main + + Main->>+DataUpdater: connect + Note right of DataUpdater: initialized = false + DataUpdater-->>Main: ok + deactivate Main + DataUpdater->>DataUpdater: asynchronous connect to server... + activate DataUpdater + Note right of DataUpdater: store any callbacks instead of triggering (e.g. onServerConnect, fromHistoryData, fromCurrentData, ...) + DataUpdater-X+Main: done + deactivate DataUpdater + deactivate DataUpdater + + Main->>+DataUpdater: connectCallback = onServerConnect + Note right of DataUpdater: connectCallback = onServerConnect + DataUpdater-->>-Main: ok + Main->>+onServerConnect: call + onServerConnect->>+LoginStateViewModel: passiveLogin + LoginStateViewModel-->>onServerConnect: ok + onServerConnect-->>Main: ok + deactivate onServerConnect + deactivate Main + + LoginStateViewModel->>+LoginStateViewModel: asynchronous passive login + Note over Main,LoginStateViewModel: Session available! + LoginStateViewModel-X+onServerConnect: done + deactivate LoginStateViewModel + deactivate LoginStateViewModel + + onServerConnect->>+DataUpdater: initialized + Note right of DataUpdater: initialized = true + DataUpdater->DataUpdater: trigger stored callbacks + DataUpdater-->>-onServerConnect: ok + onServerConnect-X+Main: done + deactivate onServerConnect + + Main->>+fetchSettings: call + Note right of fetchSettings: trigger onStartup + + fetchSettings-->>Main: ok + deactivate Main + + fetchSettings->>+fetchSettings: asynchronous settings fetch + fetchSettings->>+bindViewModels: call + + loop for each view model + bindViewModels->bindViewModels: trigger onBeforeBinding + bindViewModels->bindViewModels: trigger onBoundTo + bindViewModels->bindViewModels: trigger onAfterBinding + end + + bindViewModels->bindViewModels: trigger onAllBound + opt User is logged in + bindViewModels->>+LoginStateViewModel: onAllBound + LoginStateViewModel->LoginStateViewModel: trigger onUserLoggedIn + LoginStateViewModel-->>-bindViewModels: ok + end + bindViewModels->bindViewModels: trigger onStartupComplete + bindViewModels-->>-fetchSettings: ok + + deactivate fetchSettings + deactivate fetchSettings + + +.. _sec-plugins-viewmodels-reconnect: + +Web interface reconnect +~~~~~~~~~~~~~~~~~~~~~~~ + +.. mermaid:: + + sequenceDiagram + participant onServerConnect + participant DataUpdater + participant LoginStateViewModel + + activate DataUpdater + DataUpdater->>DataUpdater: call connectCallback + DataUpdater->>+onServerConnect: call + onServerConnect-->>DataUpdater: ok + deactivate DataUpdater + + onServerConnect->>+LoginStateViewModel: passiveLogin + LoginStateViewModel-->>onServerConnect: ok + deactivate onServerConnect + LoginStateViewModel->>+LoginStateViewModel: asynchronous passive login + Note over onServerConnect,LoginStateViewModel: Session available! + opt User is logged in + LoginStateViewModel->LoginStateViewModel: trigger onUserLoggedIn + end + + activate onServerConnect + LoginStateViewModel-XonServerConnect: done + deactivate LoginStateViewModel + deactivate LoginStateViewModel + + onServerConnect->>+DataUpdater: initialized + DataUpdater->DataUpdater: trigger stored callbacks + DataUpdater-->>onServerConnect: ok + deactivate DataUpdater + deactivate onServerConnect + .. seealso:: `OctoPrint's core viewmodels `_ diff --git a/docs/sphinxext/onlineinclude.py b/docs/sphinxext/onlineinclude.py index 633eeef3..b6d9a181 100644 --- a/docs/sphinxext/onlineinclude.py +++ b/docs/sphinxext/onlineinclude.py @@ -7,41 +7,99 @@ __copyright__ = "Copyright (C) 2015 Gina Häußge - Released under terms of the import codecs -import urllib2 +import requests +from contextlib import closing -from sphinx.directives import LiteralInclude, dedent_lines +from sphinx.directives.code import LiteralIncludeReader, LiteralInclude, dedent_lines, \ + nodes, set_source_info, parselinenos, logger, container_wrapper + +if False: + # For type annotation + from typing import Any, List # NOQA cache = dict() +class OnlineIncludeReader(LiteralIncludeReader): + + def read_file(self, filename, location=None): + # type: (unicode, Any) -> List[unicode] + + global cache + try: + if filename in cache: + lines = cache[filename] + else: + with closing(requests.get(filename, stream=True)) as r: + r.encoding = self.encoding + lines = r.text.splitlines(True) + cache[filename] = lines + + if 'tab-width' in self.options: + lines = [line.expandtabs(self.options['tab-width']) for line in lines] + + return lines + except (IOError, OSError): + raise IOError(_('Include file %r not found or reading it failed') % filename) + except UnicodeError: + raise UnicodeError(_('Encoding %r used for reading included file %r seems to ' + 'be wrong, try giving an :encoding: option') % + (self.encoding, filename)) + + class OnlineIncludeDirective(LiteralInclude): - def read_with_encoding(self, filename, document, codec_info, encoding): - global cache + def run(self): + # type: () -> List[nodes.Node] + document = self.state.document + if not document.settings.file_insertion_enabled: + return [document.reporter.warning('File insertion disabled', + line=self.lineno)] + env = document.settings.env + + # convert options['diff'] to absolute path + if 'diff' in self.options: + _, path = env.relfn2path(self.options['diff']) + self.options['diff'] = path - f = None try: - if not self.arguments[0] in cache: - f = codecs.StreamReaderWriter(urllib2.urlopen(self.arguments[0]), codec_info[2], - codec_info[3], 'strict') - lines = f.readlines() - cache[self.arguments[0]] = lines - else: - lines = cache[self.arguments[0]] + location = self.state_machine.get_source_and_line(self.lineno) + url = self.arguments[0] + + reader = OnlineIncludeReader(url, self.options, env.config) + text, lines = reader.read(location=location) + + retnode = nodes.literal_block(text, text, source=url) + set_source_info(self, retnode) + if self.options.get('diff'): # if diff is set, set udiff + retnode['language'] = 'udiff' + elif 'language' in self.options: + retnode['language'] = self.options['language'] + retnode['linenos'] = ('linenos' in self.options or + 'lineno-start' in self.options or + 'lineno-match' in self.options) + retnode['classes'] += self.options.get('class', []) + extra_args = retnode['highlight_args'] = {} + if 'emphasize-lines' in self.options: + hl_lines = parselinenos(self.options['emphasize-lines'], lines) + if any(i >= lines for i in hl_lines): + logger.warning('line number spec is out of range(1-%d): %r' % + (lines, self.options['emphasize-lines']), + location=location) + extra_args['hl_lines'] = [x + 1 for x in hl_lines if x < lines] + extra_args['linenostart'] = reader.lineno_start + + if 'caption' in self.options: + caption = self.options['caption'] or self.arguments[0] + retnode = container_wrapper(self, retnode, caption) + + # retnode will be note_implicit_target that is linked from caption and numref. + # when options['name'] is provided, it should be primary ID. + self.add_name(retnode) + + return [retnode] + except Exception as exc: + return [document.reporter.warning(str(exc), line=self.lineno)] - lines = dedent_lines(lines, self.options.get('dedent')) - return lines - except (IOError, OSError, urllib2.URLError): - return [document.reporter.warning( - 'Include file %r not found or reading it failed' % self.arguments[0], - line=self.lineno)] - except UnicodeError: - return [document.reporter.warning( - 'Encoding %r used for reading included file %r seems to ' - 'be wrong, try giving an :encoding: option' % - (encoding, self.arguments[0]))] - finally: - if f is not None: - f.close() def visit_onlineinclude(translator, node): translator.visit_literal_block(node) @@ -53,4 +111,4 @@ def setup(app): app.add_directive("onlineinclude", OnlineIncludeDirective) handler = (visit_onlineinclude, depart_onlineinclude) - app.add_node(OnlineIncludeDirective, html=handler, latex=handler, text=handler) \ No newline at end of file + app.add_node(OnlineIncludeDirective, html=handler, latex=handler, text=handler) diff --git a/setup.py b/setup.py index 24af4a59..79ee74d1 100644 --- a/setup.py +++ b/setup.py @@ -65,9 +65,10 @@ EXTRA_REQUIRES = dict( "ddt", # Documentation dependencies - "sphinx>=1.3,<1.4", + "sphinx>=1.6,<1.7", "sphinxcontrib-httpdomain", - "sphinx_rtd_theme", + "sphinxcontrib-mermaid", + "sphinx_bootstrap_theme", # PyPi upload related "pypandoc"