pax_global_header00006660000000000000000000000064137202344340014514gustar00rootroot0000000000000052 comment=f14bd7e1685e7b8ee1906eb391d924e1f46d26d4 python-livereload-2.6.3/000077500000000000000000000000001372023443400151715ustar00rootroot00000000000000python-livereload-2.6.3/.github/000077500000000000000000000000001372023443400165315ustar00rootroot00000000000000python-livereload-2.6.3/.github/FUNDING.yml000066400000000000000000000002171372023443400203460ustar00rootroot00000000000000# These are supported funding model platforms github: [lepture] patreon: lepture tidelift: pypi/livereload custom: https://lepture.com/donate python-livereload-2.6.3/.gitignore000066400000000000000000000003101372023443400171530ustar00rootroot00000000000000.DS_Store test.* *.swp *~ *.py[co] *.egg *.egg-info dist eggs sdist develop-eggs .installed.cfg build pip-log.txt .idea .coverage .tox .env/ venv/ docs/_build example/style.css tests/tmp cover/ python-livereload-2.6.3/.gitmodules000066400000000000000000000001501372023443400173420ustar00rootroot00000000000000[submodule "docs/_themes"] path = docs/_themes url = git://github.com/lepture/flask-sphinx-themes.git python-livereload-2.6.3/CHANGES.rst000066400000000000000000000074251372023443400170030ustar00rootroot00000000000000Changelog ========= The full list of changes between each Python LiveReload release. Version 2.6.3 ------------- Released on August 22, 2020 1. Support for custom default filenames. Version 2.6.2 ------------- Released on June 6, 2020 1. Support for Python 2.8 2. Enable adding custom headers to response. 3. Updates for Python 2.7 support. 4. Support for use with a reverse proxy. 5. Other bug fixes. Version 2.6.1 ------------- Released on May 7, 2019 1. Fixed bugs Version 2.6.0 ------------- Released on Nov 21, 2018 1. Changed logic of liveport. 2. Fixed bugs Version 2.5.2 ------------- Released on May 2, 2018 1. Fix tornado 4.5+ not closing connection 2. Add ignore dirs 3. Fix bugs Version 2.5.1 ------------- Release on Jan 7, 2017 Happy New Year. 1. Fix Content-Type detection 2. Ensure current version of pyinotify is installed before using Version 2.5.0 ------------- Released on Nov 16, 2016 1. wait parameter can be float via Todd Wolfson 2. Option to disable liveCSS via Yunchi Luo 3. Django management command via Marc-Stefan Cassola Version 2.4.1 ------------- Released on Jan 19, 2016 1. Allow other hostname with JS script location.hostname 2. Expose delay parameter in command line tool 3. Server.watch accept ignore parameter Version 2.4.0 ------------- Released on May 29, 2015 1. Fix unicode issue with tornado built-in StaticFileHandler 2. Add filter for directory watching 3. Watch without browser open 4. Auto use inotify wather if possible 5. Add ``open_url_delay`` parameter 6. Refactor lots of code. Thanks for the patches and issues from everyone. Version 2.3.2 ------------- Released on Nov 5, 2014 1. Fix root parameter in ``serve`` method via `#76`_. 2. Fix shell unicode stdout error. 3. More useful documentation. .. _`#76`: https://github.com/lepture/python-livereload/issues/76 Version 2.3.1 ------------- Released on Nov 1, 2014 1. Add ``cwd`` parameter for ``shell`` 2. When ``delay`` is ``forever``, it will not trigger a livereload 3. Support different ports for app and livereload. Version 2.3.0 ------------- Released on Oct 28, 2014 1. Add '--host' argument to CLI 2. Autoreload when python code changed 3. Add delay parameter to watcher Version 2.2.2 ------------- Released on Sep 10, 2014 Fix for tornado 4. Version 2.2.1 ------------- Released on Jul 10, 2014 Fix for Python 3.x Version 2.2.0 ------------- Released on Mar 15, 2014 + Add bin/livereload + Add inotify support Version 2.1.0 ------------- Released on Jan 26, 2014 Add ForceReloadHandler. Version 2.0.0 ------------- Released on Dec 30, 2013 A new designed livereload server which has the power to serve a wsgi application. Version 1.0.1 ------------- Release on Aug 19th, 2013 + Documentation improvement + Bugfix for server #29 + Bugfix for Task #34 Version 1.0.0 ------------- Released on May 9th, 2013 + Redesign the compiler + Various bugfix Version 0.11 ------------- Released on Nov 7th, 2012 + Redesign server + remove notification Version 0.8 ------------ Released on Jul 10th, 2012 + Static Server support root page + Don't compile at first start Version 0.7 ------------- Released on Jun 20th, 2012 + Static Server support index + Dynamic watch directory changes Version 0.6 ------------ Release on Jun 18th, 2012 + Add static server, 127.0.0.1:35729 Version 0.5 ----------- Release on Jun 18th, 2012 + support for python3 Version 0.4 ----------- Release on May 8th, 2012 + bugfix for notify (sorry) Version 0.3 ----------- Release on May 6th, 2012 + bugfix for compiler alias + raise error for CommandCompiler + add comand-line feature + get static file from internet Version 0.2 ------------ Release on May 5th, 2012. + bugfix + performance improvement + support for notify-OSD + alias of compilers Version 0.1 ------------ Released on May 4th, 2012. python-livereload-2.6.3/LICENSE000066400000000000000000000027461372023443400162070ustar00rootroot00000000000000Copyright (c) 2012-2015, Hsiaoming Yang Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. python-livereload-2.6.3/MANIFEST.in000066400000000000000000000001141372023443400167230ustar00rootroot00000000000000include livereload/vendors/livereload.js include LICENSE include README.rst python-livereload-2.6.3/Makefile000066400000000000000000000007041372023443400166320ustar00rootroot00000000000000.PHONY: clean-pyc clean-build docs test coverage clean: clean-build clean-pyc clean-build: rm -fr build/ rm -fr dist/ rm -fr *.egg-info clean-pyc: find . -name '*.pyc' -exec rm -f {} + find . -name '*.pyo' -exec rm -f {} + find . -name '*~' -exec rm -f {} + install: python setup.py install docs: $(MAKE) -C docs html test: @nosetests -s coverage: @rm -f .coverage @nosetests --with-coverage --cover-package=livereload --cover-html python-livereload-2.6.3/README.rst000066400000000000000000000134231372023443400166630ustar00rootroot00000000000000LiveReload ========== This is a brand new LiveReload in version 2.0.0. `Download on PyPi `_ Installation ------------ Python LiveReload is designed for web developers who know Python. Install Python LiveReload with pip:: $ pip install livereload If you don't have pip installed, try easy_install:: $ easy_install livereload Command Line Interface ---------------------- Python LiveReload provides a command line utility, ``livereload``, for starting a server in a directory. By default, it will listen to port 35729, the common port for `LiveReload browser extensions`_. :: $ livereload --help usage: livereload [-h] [-p PORT] [-w WAIT] [directory] Start a `livereload` server positional arguments: directory Directory to watch for changes optional arguments: -h, --help show this help message and exit -p PORT, --port PORT Port to run `livereload` server on -w WAIT, --wait WAIT Time delay before reloading .. _`livereload browser extensions`: http://feedback.livereload.com/knowledgebase/articles/86242-how-do-i-install-and-use-the-browser-extensions- Older versions of Python LiveReload used a ``Guardfile`` to describe optional additional rules for files to watch and build commands to run on changes. This conflicted with other tools that used the same file for their configuration and is no longer supported since Python LiveReload version 2.0.0. Instead of a ``Guardfile`` you can now write a Python script using very similar syntax and run it instead of the command line application. Script example: Sphinx ---------------------- Here's a simple example script that rebuilds Sphinx documentation: .. code:: python #!/usr/bin/env python from livereload import Server, shell server = Server() server.watch('docs/*.rst', shell('make html', cwd='docs')) server.serve(root='docs/_build/html') Run it, then open http://localhost:5500/ and you can see the documentation changes in real time. Developer Guide --------------- The new livereload server is designed for developers. It can power a wsgi application now: .. code:: python from livereload import Server, shell server = Server(wsgi_app) # run a shell command server.watch('static/*.stylus', 'make static') # run a function def alert(): print('foo') server.watch('foo.txt', alert) # output stdout into a file server.watch('style.less', shell('lessc style.less', output='style.css')) server.serve() The ``Server`` class accepts parameters: - app: a wsgi application - watcher: a watcher instance, you don't have to create one server.watch ~~~~~~~~~~~~ ``server.watch`` can watch a filepath, a directory and a glob pattern:: server.watch('path/to/file.txt') server.watch('directory/path/') server.watch('glob/*.pattern') You can also use other library (for example: formic) for more powerful file adding:: for filepath in formic.FileSet(include="**.css"): server.watch(filepath, 'make css') You can delay a certain seconds to send the reload signal:: # delay 2 seconds for reloading server.watch('path/to/file', delay=2) server.setHeader ~~~~~~~~~~~~~~~~ ```server.setHeader``` can be used to add one or more headers to the HTTP response:: server.setHeader('Access-Control-Allow-Origin', '*') server.setHeader('Access-Control-Allow-Methods', '*') server.serve ~~~~~~~~~~~~ Setup a server with ``server.serve`` method. It can create a static server and a livereload server:: # use default settings server.serve() # livereload on another port server.serve(liveport=35729) # use custom host and port server.serve(port=8080, host='localhost') # open the web browser on startup, based on $BROWSER environment variable server.serve(open_url_delay=5, debug=False) # set a custom default file to open server.serve(default_filename='example.html') shell ~~~~~ The powerful ``shell`` function will help you to execute shell commands. You can use it with ``server.watch``:: # you can redirect command output to a file server.watch('style.less', shell('lessc style.less', output='style.css')) # commands can be a list server.watch('style.less', shell(['lessc', 'style.less'], output='style.css')) # working with Makefile server.watch('assets/*.styl', shell('make assets', cwd='assets')) Frameworks Integration ---------------------- Livereload can work seamlessly with your favorite framework. Django ~~~~~~ For Django there is a management command included. To use simply - add ``'livereload'`` to your ``INSTALLED_APPS`` and - then run ``./manage.py livereload``. For available options like host and ports please refer to ``./manage.py livereload -h``. To automagically serve static files like the native ``runserver`` command you have to use `dj-static `_. (follow the simple instructions there). Flask ~~~~~ Wrap Flask with livereload is much simpler: .. code:: python # app is a Flask object app = create_app() # remember to use DEBUG mode for templates auto reload # https://github.com/lepture/python-livereload/issues/144 app.debug = True server = Server(app.wsgi_app) # server.watch server.serve() Bottle ~~~~~~ Wrap the ``Bottle`` app with livereload server: .. code:: python # Without this line templates won't auto reload because of caching. # http://bottlepy.org/docs/dev/tutorial.html#templates bottle.debug(True) app = Bottle() server = Server(app) # server.watch server.serve() Security Report --------------- To report a security vulnerability, please use the `Tidelift security contact `_. Tidelift will coordinate the fix and disclosure. python-livereload-2.6.3/docs/000077500000000000000000000000001372023443400161215ustar00rootroot00000000000000python-livereload-2.6.3/docs/Makefile000066400000000000000000000126321372023443400175650ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: _themes/.git $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PythonLiveReload.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PythonLiveReload.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/PythonLiveReload" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PythonLiveReload" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." _themes/.git: git submodule update --init python-livereload-2.6.3/docs/_templates/000077500000000000000000000000001372023443400202565ustar00rootroot00000000000000python-livereload-2.6.3/docs/_templates/sidebarintro.html000066400000000000000000000002411372023443400236260ustar00rootroot00000000000000

About LiveReload

LiveReload is an awsome tool for web developers, it helps web developers auto refreshing their browsers when something changed.

python-livereload-2.6.3/docs/_themes/000077500000000000000000000000001372023443400175455ustar00rootroot00000000000000python-livereload-2.6.3/docs/changelog.rst000066400000000000000000000000341372023443400205770ustar00rootroot00000000000000.. include:: ../CHANGES.rst python-livereload-2.6.3/docs/conf.py000066400000000000000000000200711372023443400174200ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Python LiveReload documentation build configuration file, created by # sphinx-quickstart on Sat May 5 18:36:44 2012. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os sys.path.append(os.path.abspath('_themes')) sys.path.append(os.path.abspath('.')) # 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('.')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Python LiveReload' copyright = u'2012, Hsiaoming Yang' # 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.3' # The full version, including alpha/beta/rc tags. release = '0.3' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # 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 # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # 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 = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'flask_small' # 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 = { 'index_logo': None, 'github_fork': 'lepture/python-livereload', } # Add any paths that contain custom themes here, relative to this directory. html_theme_path = ['_themes'] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # 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 # 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, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # 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' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. html_sidebars = { 'index': ['sidebarintro.html', 'sourcelink.html', 'searchbox.html'], '**': ['sidebarintro.html', 'localtoc.html', 'relations.html', 'sourcelink.html', 'searchbox.html'] } # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # 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 = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'PythonLiveReloaddoc' # -- Options for LaTeX output -------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'PythonLiveReload.tex', u'Python LiveReload Documentation', u'Hsiaoming Yang', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'livereload', u'Python LiveReload Documentation', [u'Hsiaoming Yang'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'PythonLiveReload', u'Python LiveReload Documentation', u'Hsiaoming Yang', 'PythonLiveReload', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' python-livereload-2.6.3/docs/index.rst000066400000000000000000000010611372023443400177600ustar00rootroot00000000000000.. include:: ../README.rst API --- .. module:: livereload .. autoclass:: Server :members: :inherited-members: .. autofunction:: shell Changelog --------- The full list of changes between each Python LiveReload release. .. toctree:: :maxdepth: 2 changelog Contact ------- Have any trouble? Want to know more? + Follow me on GitHub_ for the latest updates. + Follow me on Twitter_ (most tweets are in Chinese). + Send Email_ to me. .. _GitHub: https://github.com/lepture .. _Twitter: https://twitter.com/lepture .. _Email: me@lepture.com python-livereload-2.6.3/example/000077500000000000000000000000001372023443400166245ustar00rootroot00000000000000python-livereload-2.6.3/example/index.html000066400000000000000000000004141372023443400206200ustar00rootroot00000000000000 Python LiveReload Example

Example

Container

python-livereload-2.6.3/example/server.py000066400000000000000000000002701372023443400205030ustar00rootroot00000000000000#!/usr/bin/env python from livereload import Server, shell server = Server() server.watch('style.less', shell('lessc style.less', output='style.css')) server.serve(open_url_delay=1) python-livereload-2.6.3/example/style.less000066400000000000000000000001131372023443400206470ustar00rootroot00000000000000@bg: #222; @fg: #fff; html, body { background: @bg; color: @fg; } python-livereload-2.6.3/livereload/000077500000000000000000000000001372023443400173175ustar00rootroot00000000000000python-livereload-2.6.3/livereload/__init__.py000066400000000000000000000005561372023443400214360ustar00rootroot00000000000000""" livereload ~~~~~~~~~~ A python version of livereload. :copyright: (c) 2013 by Hsiaoming Yang :license: BSD, see LICENSE for more details. """ __version__ = '2.6.3' __author__ = 'Hsiaoming Yang ' __homepage__ = 'https://github.com/lepture/python-livereload' from .server import Server, shell __all__ = ('Server', 'shell') python-livereload-2.6.3/livereload/cli.py000066400000000000000000000025451372023443400204460ustar00rootroot00000000000000import argparse import tornado.log from livereload.server import Server parser = argparse.ArgumentParser(description='Start a `livereload` server') parser.add_argument( '--host', help='Hostname to run `livereload` server on', type=str, default='127.0.0.1' ) parser.add_argument( '-p', '--port', help='Port to run `livereload` server on', type=int, default=35729 ) parser.add_argument( 'directory', help='Directory to serve files from', type=str, default='.', nargs='?' ) parser.add_argument( '-t', '--target', help='File or directory to watch for changes', type=str, ) parser.add_argument( '-w', '--wait', help='Time delay in seconds before reloading', type=float, default=0.0 ) parser.add_argument( '-o', '--open-url-delay', help='If set, triggers browser opening seconds after starting', type=float ) parser.add_argument( '-d', '--debug', help='Enable Tornado pretty logging', action='store_true' ) def main(argv=None): args = parser.parse_args() if args.debug: tornado.log.enable_pretty_logging() # Create a new application server = Server() server.watcher.watch(args.target or args.directory, delay=args.wait) server.serve(host=args.host, port=args.port, root=args.directory, open_url_delay=args.open_url_delay) python-livereload-2.6.3/livereload/handlers.py000066400000000000000000000150531372023443400214750ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ livereload.handlers ~~~~~~~~~~~~~~~~~~~ HTTP and WebSocket handlers for livereload. :copyright: (c) 2013 by Hsiaoming Yang :license: BSD, see LICENSE for more details. """ import datetime import hashlib import os import stat import time import logging from tornado import web from tornado import ioloop from tornado import escape from tornado.log import gen_log from tornado.websocket import WebSocketHandler from tornado.util import ObjectDict logger = logging.getLogger('livereload') class LiveReloadHandler(WebSocketHandler): waiters = set() watcher = None live_css = None _last_reload_time = None def allow_draft76(self): return True def check_origin(self, origin): return True def on_close(self): if self in LiveReloadHandler.waiters: LiveReloadHandler.waiters.remove(self) def send_message(self, message): if isinstance(message, dict): message = escape.json_encode(message) try: self.write_message(message) except: logger.error('Error sending message', exc_info=True) @classmethod def start_tasks(cls): if cls._last_reload_time: return if not cls.watcher._tasks: logger.info('Watch current working directory') cls.watcher.watch(os.getcwd()) cls._last_reload_time = time.time() logger.info('Start watching changes') if not cls.watcher.start(cls.poll_tasks): logger.info('Start detecting changes') ioloop.PeriodicCallback(cls.poll_tasks, 800).start() @classmethod def poll_tasks(cls): filepath, delay = cls.watcher.examine() if not filepath or delay == 'forever' or not cls.waiters: return reload_time = 3 if delay: reload_time = max(3 - delay, 1) if filepath == '__livereload__': reload_time = 0 if time.time() - cls._last_reload_time < reload_time: # if you changed lot of files in one time # it will refresh too many times logger.info('Ignore: %s', filepath) return if delay: loop = ioloop.IOLoop.current() loop.call_later(delay, cls.reload_waiters) else: cls.reload_waiters() @classmethod def reload_waiters(cls, path=None): logger.info( 'Reload %s waiters: %s', len(cls.waiters), cls.watcher.filepath, ) if path is None: path = cls.watcher.filepath or '*' msg = { 'command': 'reload', 'path': path, 'liveCSS': cls.live_css, 'liveImg': True, } cls._last_reload_time = time.time() for waiter in cls.waiters.copy(): try: waiter.write_message(msg) except: logger.error('Error sending message', exc_info=True) cls.waiters.remove(waiter) def on_message(self, message): """Handshake with livereload.js 1. client send 'hello' 2. server reply 'hello' 3. client send 'info' """ message = ObjectDict(escape.json_decode(message)) if message.command == 'hello': handshake = { 'command': 'hello', 'protocols': [ 'http://livereload.com/protocols/official-7', ], 'serverName': 'livereload-tornado', } self.send_message(handshake) if message.command == 'info' and 'url' in message: logger.info('Browser Connected: %s' % message.url) LiveReloadHandler.waiters.add(self) class MtimeStaticFileHandler(web.StaticFileHandler): _static_mtimes = {} # type: typing.Dict @classmethod def get_content_modified_time(cls, abspath): """Returns the time that ``abspath`` was last modified. May be overridden in subclasses. Should return a `~datetime.datetime` object or None. """ stat_result = os.stat(abspath) modified = datetime.datetime.utcfromtimestamp( stat_result[stat.ST_MTIME]) return modified @classmethod def get_content_version(cls, abspath): """Returns a version string for the resource at the given path. This class method may be overridden by subclasses. The default implementation is a hash of the file's contents. .. versionadded:: 3.1 """ data = cls.get_content(abspath) hasher = hashlib.md5() mtime_data = format(cls.get_content_modified_time(abspath), "%Y-%m-%d %H:%M:%S") hasher.update(mtime_data.encode()) if isinstance(data, bytes): hasher.update(data) else: for chunk in data: hasher.update(chunk) return hasher.hexdigest() @classmethod def _get_cached_version(cls, abs_path): def _load_version(abs_path): try: hsh = cls.get_content_version(abs_path) mtm = cls.get_content_modified_time(abs_path) return mtm, hsh except Exception: gen_log.error("Could not open static file %r", abs_path) return None, None with cls._lock: hashes = cls._static_hashes mtimes = cls._static_mtimes if abs_path not in hashes: mtm, hsh = _load_version(abs_path) hashes[abs_path] = mtm mtimes[abs_path] = hsh else: hsh = hashes.get(abs_path) mtm = mtimes.get(abs_path) if mtm != cls.get_content_modified_time(abs_path): mtm, hsh = _load_version(abs_path) hashes[abs_path] = mtm mtimes[abs_path] = hsh if hsh: return hsh return None class LiveReloadJSHandler(web.RequestHandler): def get(self): self.set_header('Content-Type', 'application/javascript') root = os.path.abspath(os.path.dirname(__file__)) js_file = os.path.join(root, 'vendors/livereload.js') with open(js_file, 'rb') as f: self.write(f.read()) class ForceReloadHandler(web.RequestHandler): def get(self): path = self.get_argument('path', default=None) or '*' LiveReloadHandler.reload_waiters(path) self.write('ok') class StaticFileHandler(MtimeStaticFileHandler): def should_return_304(self): return False python-livereload-2.6.3/livereload/management/000077500000000000000000000000001372023443400214335ustar00rootroot00000000000000python-livereload-2.6.3/livereload/management/__init__.py000066400000000000000000000000001372023443400235320ustar00rootroot00000000000000python-livereload-2.6.3/livereload/management/commands/000077500000000000000000000000001372023443400232345ustar00rootroot00000000000000python-livereload-2.6.3/livereload/management/commands/__init__.py000066400000000000000000000000001372023443400253330ustar00rootroot00000000000000python-livereload-2.6.3/livereload/management/commands/livereload.py000066400000000000000000000032701372023443400257360ustar00rootroot00000000000000import os import re from django.core.management.base import BaseCommand, CommandError from django.core.management.commands.runserver import naiveip_re from django.core.servers.basehttp import get_internal_wsgi_application from livereload import Server class Command(BaseCommand): help = 'Runs the development server with livereload enabled.' def add_arguments(self, parser): parser.add_argument('addrport', nargs='?', default='127.0.0.1:8000', help='host and optional port the django server should listen on (default: 127.0.0.1:8000)') parser.add_argument('-l', '--liveport', type=int, default=35729, help='port the livereload server should listen on (default: 35729)') def handle(self, *args, **options): m = re.match(naiveip_re, options['addrport']) if m is None: raise CommandError('"%s" is not a valid port number ' 'or address:port pair.' % options['addrport']) addr, _ipv4, _ipv6, _fqdn, port = m.groups() if not port.isdigit(): raise CommandError("%r is not a valid port number." % port) if addr: if _ipv6: raise CommandError('IPv6 addresses are currently not supported.') application = get_internal_wsgi_application() server = Server(application) for file in os.listdir('.'): if file[0] != '.' and file[:2] != '__' and os.path.isdir(file): server.watch(file) server.serve(host=addr, port=port, liveport=options['liveport']) python-livereload-2.6.3/livereload/server.py000066400000000000000000000311241372023443400212000ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ livereload.server ~~~~~~~~~~~~~~~~~ WSGI app server for livereload. :copyright: (c) 2013 - 2015 by Hsiaoming Yang :license: BSD, see LICENSE for more details. """ import os import time import shlex import logging import threading import webbrowser from subprocess import Popen, PIPE from tornado.wsgi import WSGIContainer from tornado.ioloop import IOLoop from tornado.autoreload import add_reload_hook from tornado import web from tornado import escape from tornado import httputil from tornado.log import LogFormatter from .handlers import LiveReloadHandler, LiveReloadJSHandler from .handlers import ForceReloadHandler, StaticFileHandler from .watcher import get_watcher_class from six import string_types, PY3 import sys if sys.version_info >= (3, 7) or sys.version_info.major == 2: import errno else: from os import errno if sys.version_info >= (3, 8) and sys.platform == 'win32': import asyncio asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) logger = logging.getLogger('livereload') HEAD_END = b'' def set_header(fn, name, value): """Helper Function to Add HTTP headers to the server""" def set_default_headers(self, *args, **kwargs): fn(self, *args, **kwargs) self.set_header(name, value) return set_default_headers def shell(cmd, output=None, mode='w', cwd=None, shell=False): """Execute a shell command. You can add a shell command:: server.watch( 'style.less', shell('lessc style.less', output='style.css') ) :param cmd: a shell command, string or list :param output: output stdout to the given file :param mode: only works with output, mode ``w`` means write, mode ``a`` means append :param cwd: set working directory before command is executed. :param shell: if true, on Unix the executable argument specifies a replacement shell for the default ``/bin/sh``. """ if not output: output = os.devnull else: folder = os.path.dirname(output) if folder and not os.path.isdir(folder): os.makedirs(folder) if not isinstance(cmd, (list, tuple)) and not shell: cmd = shlex.split(cmd) def run_shell(): try: p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE, cwd=cwd, shell=shell) except OSError as e: logger.error(e) if e.errno == errno.ENOENT: # file (command) not found logger.error("maybe you haven't installed %s", cmd[0]) return e stdout, stderr = p.communicate() if stderr: logger.error(stderr) return stderr #: stdout is bytes, decode for python3 if PY3: stdout = stdout.decode() with open(output, mode) as f: f.write(stdout) return run_shell class LiveScriptInjector(web.OutputTransform): def __init__(self, request): super(LiveScriptInjector, self).__init__(request) def transform_first_chunk(self, status_code, headers, chunk, finishing): if HEAD_END in chunk: chunk = chunk.replace(HEAD_END, self.script + HEAD_END) if 'Content-Length' in headers: length = int(headers['Content-Length']) + len(self.script) headers['Content-Length'] = str(length) return status_code, headers, chunk class LiveScriptContainer(WSGIContainer): def __init__(self, wsgi_app, script=''): self.wsgi_app = wsgi_app self.script = script def __call__(self, request): data = {} response = [] def start_response(status, response_headers, exc_info=None): data["status"] = status data["headers"] = response_headers return response.append app_response = self.wsgi_app( WSGIContainer.environ(request), start_response) try: response.extend(app_response) body = b"".join(response) finally: if hasattr(app_response, "close"): app_response.close() if not data: raise Exception("WSGI app did not call start_response") status_code, reason = data["status"].split(' ', 1) status_code = int(status_code) headers = data["headers"] header_set = set(k.lower() for (k, v) in headers) body = escape.utf8(body) if HEAD_END in body: body = body.replace(HEAD_END, self.script + HEAD_END) if status_code != 304: if "content-type" not in header_set: headers.append(( "Content-Type", "application/octet-stream; charset=UTF-8" )) if "content-length" not in header_set: headers.append(("Content-Length", str(len(body)))) if "server" not in header_set: headers.append(("Server", "LiveServer")) start_line = httputil.ResponseStartLine( "HTTP/1.1", status_code, reason ) header_obj = httputil.HTTPHeaders() for key, value in headers: if key.lower() == 'content-length': value = str(len(body)) header_obj.add(key, value) request.connection.write_headers(start_line, header_obj, chunk=body) request.connection.finish() self._log(status_code, request) class Server(object): """Livereload server interface. Initialize a server and watch file changes:: server = Server(wsgi_app) server.serve() :param app: a wsgi application instance :param watcher: A Watcher instance, you don't have to initialize it by yourself. Under Linux, you will want to install pyinotify and use INotifyWatcher() to avoid wasted CPU usage. """ def __init__(self, app=None, watcher=None): self.root = None self.app = app if not watcher: watcher_cls = get_watcher_class() watcher = watcher_cls() self.watcher = watcher self.SFH = StaticFileHandler def setHeader(self, name, value): """Add or override HTTP headers at the at the beginning of the request. Once you have intialized a server, you can add one or more headers before starting the server:: server.setHeader('Access-Control-Allow-Origin', '*') server.setHeader('Access-Control-Allow-Methods', '*') server.serve() :param name: The name of the header field to be defined. :param value: The value of the header field to be defined. """ StaticFileHandler.set_default_headers = set_header( StaticFileHandler.set_default_headers, name, value) self.SFH = StaticFileHandler def watch(self, filepath, func=None, delay=None, ignore=None): """Add the given filepath for watcher list. Once you have intialized a server, watch file changes before serve the server:: server.watch('static/*.stylus', 'make static') def alert(): print('foo') server.watch('foo.txt', alert) server.serve() :param filepath: files to be watched, it can be a filepath, a directory, or a glob pattern :param func: the function to be called, it can be a string of shell command, or any callable object without parameters :param delay: Delay sending the reload message. Use 'forever' to not send it. This is useful to compile sass files to css, but reload on changed css files then only. :param ignore: A function return True to ignore a certain pattern of filepath. """ if isinstance(func, string_types): cmd = func func = shell(func) func.name = "shell: {}".format(cmd) self.watcher.watch(filepath, func, delay, ignore=ignore) def application(self, port, host, liveport=None, debug=None, live_css=True): LiveReloadHandler.watcher = self.watcher LiveReloadHandler.live_css = live_css if debug is None and self.app: debug = True live_handlers = [ (r'/livereload', LiveReloadHandler), (r'/forcereload', ForceReloadHandler), (r'/livereload.js', LiveReloadJSHandler) ] # The livereload.js snippet. # Uses JavaScript to dynamically inject the client's hostname. # This allows for serving on 0.0.0.0. live_script = ( '' ) if liveport: live_script = escape.utf8(live_script % liveport) else: live_script = escape.utf8(live_script % "(window.location.port || (window.location.protocol == 'https:' ? 443: 80))") web_handlers = self.get_web_handlers(live_script) class ConfiguredTransform(LiveScriptInjector): script = live_script if not liveport: handlers = live_handlers + web_handlers app = web.Application( handlers=handlers, debug=debug, transforms=[ConfiguredTransform] ) app.listen(port, address=host) else: app = web.Application( handlers=web_handlers, debug=debug, transforms=[ConfiguredTransform] ) app.listen(port, address=host) live = web.Application(handlers=live_handlers, debug=False) live.listen(liveport, address=host) def get_web_handlers(self, script): if self.app: fallback = LiveScriptContainer(self.app, script) return [(r'.*', web.FallbackHandler, {'fallback': fallback})] return [ (r'/(.*)', self.SFH, { 'path': self.root or '.', 'default_filename': self.default_filename, }), ] def serve(self, port=5500, liveport=None, host=None, root=None, debug=None, open_url=False, restart_delay=2, open_url_delay=None, live_css=True, default_filename='index.html'): """Start serve the server with the given port. :param port: serve on this port, default is 5500 :param liveport: live reload on this port :param host: serve on this hostname, default is 127.0.0.1 :param root: serve static on this root directory :param debug: set debug mode, which autoreloads the app on code changes via Tornado (and causes polling). Defaults to True when ``self.app`` is set, otherwise False. :param open_url_delay: open webbrowser after the delay seconds :param live_css: whether to use live css or force reload on css. Defaults to True :param default_filename: launch this file from the selected root on startup """ host = host or '127.0.0.1' if root is not None: self.root = root self._setup_logging() logger.info('Serving on http://%s:%s' % (host, port)) self.default_filename = default_filename self.application( port, host, liveport=liveport, debug=debug, live_css=live_css) # Async open web browser after 5 sec timeout if open_url: logger.error('Use `open_url_delay` instead of `open_url`') if open_url_delay is not None: def opener(): time.sleep(open_url_delay) webbrowser.open('http://%s:%s' % (host, port)) threading.Thread(target=opener).start() try: self.watcher._changes.append(('__livereload__', restart_delay)) LiveReloadHandler.start_tasks() add_reload_hook(lambda: IOLoop.instance().close(all_fds=True)) IOLoop.instance().start() except KeyboardInterrupt: logger.info('Shutting down...') def _setup_logging(self): logger.setLevel(logging.INFO) channel = logging.StreamHandler() channel.setFormatter(LogFormatter()) logger.addHandler(channel) # need a tornado logging handler to prevent IOLoop._setup_logging logging.getLogger('tornado').addHandler(channel) python-livereload-2.6.3/livereload/vendors/000077500000000000000000000000001372023443400207775ustar00rootroot00000000000000python-livereload-2.6.3/livereload/vendors/livereload.js000066400000000000000000001110431372023443400234630ustar00rootroot00000000000000(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o tag"); return; } this.reloader = new Reloader(this.window, this.console, Timer); this.connector = new Connector(this.options, this.WebSocket, Timer, { connecting: (function(_this) { return function() {}; })(this), socketConnected: (function(_this) { return function() {}; })(this), connected: (function(_this) { return function(protocol) { var _base; if (typeof (_base = _this.listeners).connect === "function") { _base.connect(); } _this.log("LiveReload is connected to " + _this.options.host + ":" + _this.options.port + " (protocol v" + protocol + ")."); return _this.analyze(); }; })(this), error: (function(_this) { return function(e) { if (e instanceof ProtocolError) { if (typeof console !== "undefined" && console !== null) { return console.log("" + e.message + "."); } } else { if (typeof console !== "undefined" && console !== null) { return console.log("LiveReload internal error: " + e.message); } } }; })(this), disconnected: (function(_this) { return function(reason, nextDelay) { var _base; if (typeof (_base = _this.listeners).disconnect === "function") { _base.disconnect(); } switch (reason) { case 'cannot-connect': return _this.log("LiveReload cannot connect to " + _this.options.host + ":" + _this.options.port + ", will retry in " + nextDelay + " sec."); case 'broken': return _this.log("LiveReload disconnected from " + _this.options.host + ":" + _this.options.port + ", reconnecting in " + nextDelay + " sec."); case 'handshake-timeout': return _this.log("LiveReload cannot connect to " + _this.options.host + ":" + _this.options.port + " (handshake timeout), will retry in " + nextDelay + " sec."); case 'handshake-failed': return _this.log("LiveReload cannot connect to " + _this.options.host + ":" + _this.options.port + " (handshake failed), will retry in " + nextDelay + " sec."); case 'manual': break; case 'error': break; default: return _this.log("LiveReload disconnected from " + _this.options.host + ":" + _this.options.port + " (" + reason + "), reconnecting in " + nextDelay + " sec."); } }; })(this), message: (function(_this) { return function(message) { switch (message.command) { case 'reload': return _this.performReload(message); case 'alert': return _this.performAlert(message); } }; })(this) }); } LiveReload.prototype.on = function(eventName, handler) { return this.listeners[eventName] = handler; }; LiveReload.prototype.log = function(message) { return this.console.log("" + message); }; LiveReload.prototype.performReload = function(message) { var _ref, _ref1; this.log("LiveReload received reload request: " + (JSON.stringify(message, null, 2))); return this.reloader.reload(message.path, { liveCSS: (_ref = message.liveCSS) != null ? _ref : true, liveImg: (_ref1 = message.liveImg) != null ? _ref1 : true, originalPath: message.originalPath || '', overrideURL: message.overrideURL || '', serverURL: "http://" + this.options.host + ":" + this.options.port }); }; LiveReload.prototype.performAlert = function(message) { return alert(message.message); }; LiveReload.prototype.shutDown = function() { var _base; this.connector.disconnect(); this.log("LiveReload disconnected."); return typeof (_base = this.listeners).shutdown === "function" ? _base.shutdown() : void 0; }; LiveReload.prototype.hasPlugin = function(identifier) { return !!this.pluginIdentifiers[identifier]; }; LiveReload.prototype.addPlugin = function(pluginClass) { var plugin; if (this.hasPlugin(pluginClass.identifier)) { return; } this.pluginIdentifiers[pluginClass.identifier] = true; plugin = new pluginClass(this.window, { _livereload: this, _reloader: this.reloader, _connector: this.connector, console: this.console, Timer: Timer, generateCacheBustUrl: (function(_this) { return function(url) { return _this.reloader.generateCacheBustUrl(url); }; })(this) }); this.plugins.push(plugin); this.reloader.addPlugin(plugin); }; LiveReload.prototype.analyze = function() { var plugin, pluginData, pluginsData, _i, _len, _ref; if (!(this.connector.protocol >= 7)) { return; } pluginsData = {}; _ref = this.plugins; for (_i = 0, _len = _ref.length; _i < _len; _i++) { plugin = _ref[_i]; pluginsData[plugin.constructor.identifier] = pluginData = (typeof plugin.analyze === "function" ? plugin.analyze() : void 0) || {}; pluginData.version = plugin.constructor.version; } this.connector.sendCommand({ command: 'info', plugins: pluginsData, url: this.window.location.href }); }; return LiveReload; })(); }).call(this); },{"./connector":1,"./options":5,"./reloader":7,"./timer":9}],5:[function(require,module,exports){ (function() { var Options; exports.Options = Options = (function() { function Options() { this.host = null; this.port = 35729; this.snipver = null; this.ext = null; this.extver = null; this.mindelay = 1000; this.maxdelay = 60000; this.handshake_timeout = 5000; } Options.prototype.set = function(name, value) { if (typeof value === 'undefined') { return; } if (!isNaN(+value)) { value = +value; } return this[name] = value; }; return Options; })(); Options.extract = function(document) { var element, keyAndValue, m, mm, options, pair, src, _i, _j, _len, _len1, _ref, _ref1; _ref = document.getElementsByTagName('script'); for (_i = 0, _len = _ref.length; _i < _len; _i++) { element = _ref[_i]; if ((src = element.src) && (m = src.match(/^[^:]+:\/\/(.*)\/z?livereload\.js(?:\?(.*))?$/))) { options = new Options(); if (mm = m[1].match(/^([^\/:]+)(?::(\d+))?$/)) { options.host = mm[1]; if (mm[2]) { options.port = parseInt(mm[2], 10); } } if (m[2]) { _ref1 = m[2].split('&'); for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { pair = _ref1[_j]; if ((keyAndValue = pair.split('=')).length > 1) { options.set(keyAndValue[0].replace(/-/g, '_'), keyAndValue.slice(1).join('=')); } } } return options; } } return null; }; }).call(this); },{}],6:[function(require,module,exports){ (function() { var PROTOCOL_6, PROTOCOL_7, Parser, ProtocolError, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; exports.PROTOCOL_6 = PROTOCOL_6 = 'http://livereload.com/protocols/official-6'; exports.PROTOCOL_7 = PROTOCOL_7 = 'http://livereload.com/protocols/official-7'; exports.ProtocolError = ProtocolError = (function() { function ProtocolError(reason, data) { this.message = "LiveReload protocol error (" + reason + ") after receiving data: \"" + data + "\"."; } return ProtocolError; })(); exports.Parser = Parser = (function() { function Parser(handlers) { this.handlers = handlers; this.reset(); } Parser.prototype.reset = function() { return this.protocol = null; }; Parser.prototype.process = function(data) { var command, e, message, options, _ref; try { if (this.protocol == null) { if (data.match(/^!!ver:([\d.]+)$/)) { this.protocol = 6; } else if (message = this._parseMessage(data, ['hello'])) { if (!message.protocols.length) { throw new ProtocolError("no protocols specified in handshake message"); } else if (__indexOf.call(message.protocols, PROTOCOL_7) >= 0) { this.protocol = 7; } else if (__indexOf.call(message.protocols, PROTOCOL_6) >= 0) { this.protocol = 6; } else { throw new ProtocolError("no supported protocols found"); } } return this.handlers.connected(this.protocol); } else if (this.protocol === 6) { message = JSON.parse(data); if (!message.length) { throw new ProtocolError("protocol 6 messages must be arrays"); } command = message[0], options = message[1]; if (command !== 'refresh') { throw new ProtocolError("unknown protocol 6 command"); } return this.handlers.message({ command: 'reload', path: options.path, liveCSS: (_ref = options.apply_css_live) != null ? _ref : true }); } else { message = this._parseMessage(data, ['reload', 'alert']); return this.handlers.message(message); } } catch (_error) { e = _error; if (e instanceof ProtocolError) { return this.handlers.error(e); } else { throw e; } } }; Parser.prototype._parseMessage = function(data, validCommands) { var e, message, _ref; try { message = JSON.parse(data); } catch (_error) { e = _error; throw new ProtocolError('unparsable JSON', data); } if (!message.command) { throw new ProtocolError('missing "command" key', data); } if (_ref = message.command, __indexOf.call(validCommands, _ref) < 0) { throw new ProtocolError("invalid command '" + message.command + "', only valid commands are: " + (validCommands.join(', ')) + ")", data); } return message; }; return Parser; })(); }).call(this); },{}],7:[function(require,module,exports){ (function() { var IMAGE_STYLES, Reloader, numberOfMatchingSegments, pathFromUrl, pathsMatch, pickBestMatch, splitUrl; splitUrl = function(url) { var hash, index, params; if ((index = url.indexOf('#')) >= 0) { hash = url.slice(index); url = url.slice(0, index); } else { hash = ''; } if ((index = url.indexOf('?')) >= 0) { params = url.slice(index); url = url.slice(0, index); } else { params = ''; } return { url: url, params: params, hash: hash }; }; pathFromUrl = function(url) { var path; url = splitUrl(url).url; if (url.indexOf('file://') === 0) { path = url.replace(/^file:\/\/(localhost)?/, ''); } else { path = url.replace(/^([^:]+:)?\/\/([^:\/]+)(:\d*)?\//, '/'); } return decodeURIComponent(path); }; pickBestMatch = function(path, objects, pathFunc) { var bestMatch, object, score, _i, _len; bestMatch = { score: 0 }; for (_i = 0, _len = objects.length; _i < _len; _i++) { object = objects[_i]; score = numberOfMatchingSegments(path, pathFunc(object)); if (score > bestMatch.score) { bestMatch = { object: object, score: score }; } } if (bestMatch.score > 0) { return bestMatch; } else { return null; } }; numberOfMatchingSegments = function(path1, path2) { var comps1, comps2, eqCount, len; path1 = path1.replace(/^\/+/, '').toLowerCase(); path2 = path2.replace(/^\/+/, '').toLowerCase(); if (path1 === path2) { return 10000; } comps1 = path1.split('/').reverse(); comps2 = path2.split('/').reverse(); len = Math.min(comps1.length, comps2.length); eqCount = 0; while (eqCount < len && comps1[eqCount] === comps2[eqCount]) { ++eqCount; } return eqCount; }; pathsMatch = function(path1, path2) { return numberOfMatchingSegments(path1, path2) > 0; }; IMAGE_STYLES = [ { selector: 'background', styleNames: ['backgroundImage'] }, { selector: 'border', styleNames: ['borderImage', 'webkitBorderImage', 'MozBorderImage'] } ]; exports.Reloader = Reloader = (function() { function Reloader(window, console, Timer) { this.window = window; this.console = console; this.Timer = Timer; this.document = this.window.document; this.importCacheWaitPeriod = 200; this.plugins = []; } Reloader.prototype.addPlugin = function(plugin) { return this.plugins.push(plugin); }; Reloader.prototype.analyze = function(callback) { return results; }; Reloader.prototype.reload = function(path, options) { var plugin, _base, _i, _len, _ref; this.options = options; if ((_base = this.options).stylesheetReloadTimeout == null) { _base.stylesheetReloadTimeout = 15000; } _ref = this.plugins; for (_i = 0, _len = _ref.length; _i < _len; _i++) { plugin = _ref[_i]; if (plugin.reload && plugin.reload(path, options)) { return; } } if (options.liveCSS) { if (path.match(/\.css$/i)) { if (this.reloadStylesheet(path)) { return; } } } if (options.liveImg) { if (path.match(/\.(jpe?g|png|gif)$/i)) { this.reloadImages(path); return; } } return this.reloadPage(); }; Reloader.prototype.reloadPage = function() { return this.window.document.location.reload(); }; Reloader.prototype.reloadImages = function(path) { var expando, img, selector, styleNames, styleSheet, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _ref2, _ref3, _results; expando = this.generateUniqueString(); _ref = this.document.images; for (_i = 0, _len = _ref.length; _i < _len; _i++) { img = _ref[_i]; if (pathsMatch(path, pathFromUrl(img.src))) { img.src = this.generateCacheBustUrl(img.src, expando); } } if (this.document.querySelectorAll) { for (_j = 0, _len1 = IMAGE_STYLES.length; _j < _len1; _j++) { _ref1 = IMAGE_STYLES[_j], selector = _ref1.selector, styleNames = _ref1.styleNames; _ref2 = this.document.querySelectorAll("[style*=" + selector + "]"); for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { img = _ref2[_k]; this.reloadStyleImages(img.style, styleNames, path, expando); } } } if (this.document.styleSheets) { _ref3 = this.document.styleSheets; _results = []; for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) { styleSheet = _ref3[_l]; _results.push(this.reloadStylesheetImages(styleSheet, path, expando)); } return _results; } }; Reloader.prototype.reloadStylesheetImages = function(styleSheet, path, expando) { var e, rule, rules, styleNames, _i, _j, _len, _len1; try { rules = styleSheet != null ? styleSheet.cssRules : void 0; } catch (_error) { e = _error; } if (!rules) { return; } for (_i = 0, _len = rules.length; _i < _len; _i++) { rule = rules[_i]; switch (rule.type) { case CSSRule.IMPORT_RULE: this.reloadStylesheetImages(rule.styleSheet, path, expando); break; case CSSRule.STYLE_RULE: for (_j = 0, _len1 = IMAGE_STYLES.length; _j < _len1; _j++) { styleNames = IMAGE_STYLES[_j].styleNames; this.reloadStyleImages(rule.style, styleNames, path, expando); } break; case CSSRule.MEDIA_RULE: this.reloadStylesheetImages(rule, path, expando); } } }; Reloader.prototype.reloadStyleImages = function(style, styleNames, path, expando) { var newValue, styleName, value, _i, _len; for (_i = 0, _len = styleNames.length; _i < _len; _i++) { styleName = styleNames[_i]; value = style[styleName]; if (typeof value === 'string') { newValue = value.replace(/\burl\s*\(([^)]*)\)/, (function(_this) { return function(match, src) { if (pathsMatch(path, pathFromUrl(src))) { return "url(" + (_this.generateCacheBustUrl(src, expando)) + ")"; } else { return match; } }; })(this)); if (newValue !== value) { style[styleName] = newValue; } } } }; Reloader.prototype.reloadStylesheet = function(path) { var imported, link, links, match, style, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1; links = (function() { var _i, _len, _ref, _results; _ref = this.document.getElementsByTagName('link'); _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { link = _ref[_i]; if (link.rel.match(/^stylesheet$/i) && !link.__LiveReload_pendingRemoval) { _results.push(link); } } return _results; }).call(this); imported = []; _ref = this.document.getElementsByTagName('style'); for (_i = 0, _len = _ref.length; _i < _len; _i++) { style = _ref[_i]; if (style.sheet) { this.collectImportedStylesheets(style, style.sheet, imported); } } for (_j = 0, _len1 = links.length; _j < _len1; _j++) { link = links[_j]; this.collectImportedStylesheets(link, link.sheet, imported); } if (this.window.StyleFix && this.document.querySelectorAll) { _ref1 = this.document.querySelectorAll('style[data-href]'); for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) { style = _ref1[_k]; links.push(style); } } this.console.log("LiveReload found " + links.length + " LINKed stylesheets, " + imported.length + " @imported stylesheets"); match = pickBestMatch(path, links.concat(imported), (function(_this) { return function(l) { return pathFromUrl(_this.linkHref(l)); }; })(this)); if (match) { if (match.object.rule) { this.console.log("LiveReload is reloading imported stylesheet: " + match.object.href); this.reattachImportedRule(match.object); } else { this.console.log("LiveReload is reloading stylesheet: " + (this.linkHref(match.object))); this.reattachStylesheetLink(match.object); } } else { this.console.log("LiveReload will reload all stylesheets because path '" + path + "' did not match any specific one"); for (_l = 0, _len3 = links.length; _l < _len3; _l++) { link = links[_l]; this.reattachStylesheetLink(link); } } return true; }; Reloader.prototype.collectImportedStylesheets = function(link, styleSheet, result) { var e, index, rule, rules, _i, _len; try { rules = styleSheet != null ? styleSheet.cssRules : void 0; } catch (_error) { e = _error; } if (rules && rules.length) { for (index = _i = 0, _len = rules.length; _i < _len; index = ++_i) { rule = rules[index]; switch (rule.type) { case CSSRule.CHARSET_RULE: continue; case CSSRule.IMPORT_RULE: result.push({ link: link, rule: rule, index: index, href: rule.href }); this.collectImportedStylesheets(link, rule.styleSheet, result); break; default: break; } } } }; Reloader.prototype.waitUntilCssLoads = function(clone, func) { var callbackExecuted, executeCallback, poll; callbackExecuted = false; executeCallback = (function(_this) { return function() { if (callbackExecuted) { return; } callbackExecuted = true; return func(); }; })(this); clone.onload = (function(_this) { return function() { _this.console.log("LiveReload: the new stylesheet has finished loading"); _this.knownToSupportCssOnLoad = true; return executeCallback(); }; })(this); if (!this.knownToSupportCssOnLoad) { (poll = (function(_this) { return function() { if (clone.sheet) { _this.console.log("LiveReload is polling until the new CSS finishes loading..."); return executeCallback(); } else { return _this.Timer.start(50, poll); } }; })(this))(); } return this.Timer.start(this.options.stylesheetReloadTimeout, executeCallback); }; Reloader.prototype.linkHref = function(link) { return link.href || link.getAttribute('data-href'); }; Reloader.prototype.reattachStylesheetLink = function(link) { var clone, parent; if (link.__LiveReload_pendingRemoval) { return; } link.__LiveReload_pendingRemoval = true; if (link.tagName === 'STYLE') { clone = this.document.createElement('link'); clone.rel = 'stylesheet'; clone.media = link.media; clone.disabled = link.disabled; } else { clone = link.cloneNode(false); } clone.href = this.generateCacheBustUrl(this.linkHref(link)); parent = link.parentNode; if (parent.lastChild === link) { parent.appendChild(clone); } else { parent.insertBefore(clone, link.nextSibling); } return this.waitUntilCssLoads(clone, (function(_this) { return function() { var additionalWaitingTime; if (/AppleWebKit/.test(navigator.userAgent)) { additionalWaitingTime = 5; } else { additionalWaitingTime = 200; } return _this.Timer.start(additionalWaitingTime, function() { var _ref; if (!link.parentNode) { return; } link.parentNode.removeChild(link); clone.onreadystatechange = null; return (_ref = _this.window.StyleFix) != null ? _ref.link(clone) : void 0; }); }; })(this)); }; Reloader.prototype.reattachImportedRule = function(_arg) { var href, index, link, media, newRule, parent, rule, tempLink; rule = _arg.rule, index = _arg.index, link = _arg.link; parent = rule.parentStyleSheet; href = this.generateCacheBustUrl(rule.href); media = rule.media.length ? [].join.call(rule.media, ', ') : ''; newRule = "@import url(\"" + href + "\") " + media + ";"; rule.__LiveReload_newHref = href; tempLink = this.document.createElement("link"); tempLink.rel = 'stylesheet'; tempLink.href = href; tempLink.__LiveReload_pendingRemoval = true; if (link.parentNode) { link.parentNode.insertBefore(tempLink, link); } return this.Timer.start(this.importCacheWaitPeriod, (function(_this) { return function() { if (tempLink.parentNode) { tempLink.parentNode.removeChild(tempLink); } if (rule.__LiveReload_newHref !== href) { return; } parent.insertRule(newRule, index); parent.deleteRule(index + 1); rule = parent.cssRules[index]; rule.__LiveReload_newHref = href; return _this.Timer.start(_this.importCacheWaitPeriod, function() { if (rule.__LiveReload_newHref !== href) { return; } parent.insertRule(newRule, index); return parent.deleteRule(index + 1); }); }; })(this)); }; Reloader.prototype.generateUniqueString = function() { return 'livereload=' + Date.now(); }; Reloader.prototype.generateCacheBustUrl = function(url, expando) { var hash, oldParams, originalUrl, params, _ref; if (expando == null) { expando = this.generateUniqueString(); } _ref = splitUrl(url), url = _ref.url, hash = _ref.hash, oldParams = _ref.params; if (this.options.overrideURL) { if (url.indexOf(this.options.serverURL) < 0) { originalUrl = url; url = this.options.serverURL + this.options.overrideURL + "?url=" + encodeURIComponent(url); this.console.log("LiveReload is overriding source URL " + originalUrl + " with " + url); } } params = oldParams.replace(/(\?|&)livereload=(\d+)/, function(match, sep) { return "" + sep + expando; }); if (params === oldParams) { if (oldParams.length === 0) { params = "?" + expando; } else { params = "" + oldParams + "&" + expando; } } return url + params + hash; }; return Reloader; })(); }).call(this); },{}],8:[function(require,module,exports){ (function() { var CustomEvents, LiveReload, k; CustomEvents = require('./customevents'); LiveReload = window.LiveReload = new (require('./livereload').LiveReload)(window); for (k in window) { if (k.match(/^LiveReloadPlugin/)) { LiveReload.addPlugin(window[k]); } } LiveReload.addPlugin(require('./less')); LiveReload.on('shutdown', function() { return delete window.LiveReload; }); LiveReload.on('connect', function() { return CustomEvents.fire(document, 'LiveReloadConnect'); }); LiveReload.on('disconnect', function() { return CustomEvents.fire(document, 'LiveReloadDisconnect'); }); CustomEvents.bind(document, 'LiveReloadShutDown', function() { return LiveReload.shutDown(); }); }).call(this); },{"./customevents":2,"./less":3,"./livereload":4}],9:[function(require,module,exports){ (function() { var Timer; exports.Timer = Timer = (function() { function Timer(func) { this.func = func; this.running = false; this.id = null; this._handler = (function(_this) { return function() { _this.running = false; _this.id = null; return _this.func(); }; })(this); } Timer.prototype.start = function(timeout) { if (this.running) { clearTimeout(this.id); } this.id = setTimeout(this._handler, timeout); return this.running = true; }; Timer.prototype.stop = function() { if (this.running) { clearTimeout(this.id); this.running = false; return this.id = null; } }; return Timer; })(); Timer.start = function(timeout, func) { return setTimeout(func, timeout); }; }).call(this); },{}]},{},[8]); python-livereload-2.6.3/livereload/watcher.py000066400000000000000000000170561372023443400213370ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ livereload.watcher ~~~~~~~~~~~~~~~~~~ A file watch management for LiveReload Server. :copyright: (c) 2013 - 2015 by Hsiaoming Yang :license: BSD, see LICENSE for more details. """ import glob import logging import os import time import sys if sys.version_info.major < 3: import inspect else: from inspect import signature try: import pyinotify except ImportError: pyinotify = None logger = logging.getLogger('livereload') class Watcher(object): """A file watcher registry.""" def __init__(self): self._tasks = {} # modification time of filepaths for each task, # before and after checking for changes self._task_mtimes = {} self._new_mtimes = {} # setting changes self._changes = [] # filepath that is changed self.filepath = None self._start = time.time() # list of ignored dirs self.ignored_dirs = ['.git', '.hg', '.svn', '.cvs'] def ignore_dirs(self, *args): self.ignored_dirs.extend(args) def remove_dirs_from_ignore(self, *args): for a in args: self.ignored_dirs.remove(a) def ignore(self, filename): """Ignore a given filename or not.""" _, ext = os.path.splitext(filename) return ext in ['.pyc', '.pyo', '.o', '.swp'] def watch(self, path, func=None, delay=0, ignore=None): """Add a task to watcher. :param path: a filepath or directory path or glob pattern :param func: the function to be executed when file changed :param delay: Delay sending the reload message. Use 'forever' to not send it. This is useful to compile sass files to css, but reload on changed css files then only. :param ignore: A function return True to ignore a certain pattern of filepath. """ self._tasks[path] = { 'func': func, 'delay': delay, 'ignore': ignore, 'mtimes': {}, } def start(self, callback): """Start the watcher running, calling callback when changes are observed. If this returns False, regular polling will be used.""" return False def examine(self): """Check if there are changes. If so, run the given task. Returns a tuple of modified filepath and reload delay. """ if self._changes: return self._changes.pop() # clean filepath self.filepath = None delays = set() for path in self._tasks: item = self._tasks[path] self._task_mtimes = item['mtimes'] changed = self.is_changed(path, item['ignore']) if changed: func = item['func'] delay = item['delay'] if delay and isinstance(delay, float): delays.add(delay) if func: name = getattr(func, 'name', None) if not name: name = getattr(func, '__name__', 'anonymous') logger.info( "Running task: {} (delay: {})".format(name, delay)) if sys.version_info.major < 3: sig_len = len(inspect.getargspec(func)[0]) else: sig_len = len(signature(func).parameters) if sig_len > 0 and isinstance(changed, list): func(changed) else: func() if delays: delay = max(delays) else: delay = None return self.filepath, delay def is_changed(self, path, ignore=None): """Check if any filepaths have been added, modified, or removed. Updates filepath modification times in self._task_mtimes. """ self._new_mtimes = {} changed = False if os.path.isfile(path): changed = self.is_file_changed(path, ignore) elif os.path.isdir(path): changed = self.is_folder_changed(path, ignore) else: changed = self.get_changed_glob_files(path, ignore) if not changed: changed = self.is_file_removed() self._task_mtimes.update(self._new_mtimes) return changed def is_file_removed(self): """Check if any filepaths have been removed since last check. Deletes removed paths from self._task_mtimes. Sets self.filepath to one of the removed paths. """ removed_paths = set(self._task_mtimes) - set(self._new_mtimes) if not removed_paths: return False for path in removed_paths: self._task_mtimes.pop(path) # self.filepath seems purely informational, so setting one # of several removed files seems sufficient self.filepath = path return True def is_file_changed(self, path, ignore=None): """Check if filepath has been added or modified since last check. Updates filepath modification times in self._new_mtimes. Sets self.filepath to changed path. """ if not os.path.isfile(path): return False if self.ignore(path): return False if ignore and ignore(path): return False mtime = os.path.getmtime(path) if path not in self._task_mtimes: self._new_mtimes[path] = mtime self.filepath = path return mtime > self._start if self._task_mtimes[path] != mtime: self._new_mtimes[path] = mtime self.filepath = path return True self._new_mtimes[path] = mtime return False def is_folder_changed(self, path, ignore=None): """Check if directory path has any changed filepaths.""" for root, dirs, files in os.walk(path, followlinks=True): for d in self.ignored_dirs: if d in dirs: dirs.remove(d) for f in files: if self.is_file_changed(os.path.join(root, f), ignore): return True return False def get_changed_glob_files(self, path, ignore=None): """Check if glob path has any changed filepaths.""" if sys.version_info[0] >=3 and sys.version_info[1] >=5: files = glob.glob(path, recursive=True) else: files = glob.glob(path) changed_files = [f for f in files if self.is_file_changed(f, ignore)] return changed_files class INotifyWatcher(Watcher): def __init__(self): Watcher.__init__(self) self.wm = pyinotify.WatchManager() self.notifier = None self.callback = None def watch(self, path, func=None, delay=None, ignore=None): flag = pyinotify.IN_CREATE | pyinotify.IN_DELETE | pyinotify.IN_MODIFY self.wm.add_watch(path, flag, rec=True, do_glob=True, auto_add=True) Watcher.watch(self, path, func, delay, ignore) def inotify_event(self, event): self.callback() def start(self, callback): if not self.notifier: self.callback = callback from tornado import ioloop self.notifier = pyinotify.TornadoAsyncNotifier( self.wm, ioloop.IOLoop.instance(), default_proc_fun=self.inotify_event ) callback() return True def get_watcher_class(): if pyinotify is None or not hasattr(pyinotify, 'TornadoAsyncNotifier'): return Watcher return INotifyWatcher python-livereload-2.6.3/server.py000066400000000000000000000002361372023443400170520ustar00rootroot00000000000000# coding: utf-8 from livereload import Server, shell server = Server() server.watch('docs/*.rst', shell('make html')) server.serve(root='docs/_build/html') python-livereload-2.6.3/setup.cfg000066400000000000000000000000771372023443400170160ustar00rootroot00000000000000[bdist_wheel] universal = 1 [metadata] license_file = LICENSE python-livereload-2.6.3/setup.py000066400000000000000000000040521372023443400167040ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- import re from setuptools import setup def fread(filepath): with open(filepath, 'r') as f: return f.read() def version(): content = fread('livereload/__init__.py') pattern = r"__version__ = '([0-9\.dev]*)'" m = re.findall(pattern, content) return m[0] setup( name='livereload', version=version(), author='Hsiaoming Yang', author_email='me@lepture.com', url='https://github.com/lepture/python-livereload', packages=['livereload', 'livereload.management.commands'], description='Python LiveReload is an awesome tool for web developers', long_description_content_type='text/x-rst', long_description=fread('README.rst'), entry_points={ 'console_scripts': [ 'livereload = livereload.cli:main', ] }, install_requires=[ 'tornado;python_version>"2.7"', 'tornado<6;python_version=="2.7"', 'six', ], license='BSD', include_package_data=True, classifiers=[ 'Development Status :: 4 - Beta', 'Environment :: Console', 'Environment :: Web Environment :: Mozilla', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Natural Language :: English', 'Operating System :: MacOS :: MacOS X', 'Operating System :: POSIX :: Linux', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Software Development :: Build Tools', 'Topic :: Software Development :: Compilers', 'Topic :: Software Development :: Debuggers', ] ) python-livereload-2.6.3/tests/000077500000000000000000000000001372023443400163335ustar00rootroot00000000000000python-livereload-2.6.3/tests/__init__.py000066400000000000000000000000001372023443400204320ustar00rootroot00000000000000python-livereload-2.6.3/tests/test_watcher.py000066400000000000000000000106301372023443400214010ustar00rootroot00000000000000#!/usr/bin/python import os import time import shutil import unittest from livereload.watcher import get_watcher_class Watcher = get_watcher_class() tmpdir = os.path.join(os.path.dirname(__file__), 'tmp') class TestWatcher(unittest.TestCase): def setUp(self): if os.path.isdir(tmpdir): shutil.rmtree(tmpdir) os.mkdir(tmpdir) def tearDown(self): shutil.rmtree(tmpdir) def test_watch_dir(self): os.mkdir(os.path.join(tmpdir, '.git')) os.mkdir(os.path.join(tmpdir, '.hg')) os.mkdir(os.path.join(tmpdir, '.svn')) os.mkdir(os.path.join(tmpdir, '.cvs')) watcher = Watcher() watcher.watch(tmpdir) assert watcher.is_changed(tmpdir) is False # sleep 1 second so that mtime will be different # TODO: This doesn't seem necessary; test passes without it time.sleep(1) filepath = os.path.join(tmpdir, 'foo') with open(filepath, 'w') as f: f.write('') assert watcher.is_changed(tmpdir) assert watcher.is_changed(tmpdir) is False os.remove(filepath) assert watcher.is_changed(tmpdir) assert watcher.is_changed(tmpdir) is False def test_watch_file(self): watcher = Watcher() watcher.count = 0 # sleep 1 second so that mtime will be different # TODO: This doesn't seem necessary; test passes without it time.sleep(1) filepath = os.path.join(tmpdir, 'foo') with open(filepath, 'w') as f: f.write('') def add_count(): watcher.count += 1 watcher.watch(filepath, add_count) assert watcher.is_changed(filepath) assert watcher.is_changed(filepath) is False # sleep 1 second so that mtime will be different # TODO: This doesn't seem necessary; test passes without it time.sleep(1) with open(filepath, 'w') as f: f.write('') abs_filepath = os.path.abspath(filepath) assert watcher.examine() == (abs_filepath, None) assert watcher.examine() == (None, None) assert watcher.count == 1 os.remove(filepath) assert watcher.examine() == (abs_filepath, None) assert watcher.examine() == (None, None) assert watcher.count == 2 def test_watch_glob(self): watcher = Watcher() watcher.watch(tmpdir + '/*') assert watcher.examine() == (None, None) with open(os.path.join(tmpdir, 'foo.pyc'), 'w') as f: f.write('') assert watcher.examine() == (None, None) filepath = os.path.join(tmpdir, 'foo') with open(filepath, 'w') as f: f.write('') abs_filepath = os.path.abspath(filepath) assert watcher.examine() == (abs_filepath, None) assert watcher.examine() == (None, None) os.remove(filepath) assert watcher.examine() == (abs_filepath, None) assert watcher.examine() == (None, None) def test_watch_ignore(self): watcher = Watcher() watcher.watch(tmpdir + '/*', ignore=lambda o: o.endswith('.ignore')) assert watcher.examine() == (None, None) with open(os.path.join(tmpdir, 'foo.ignore'), 'w') as f: f.write('') assert watcher.examine() == (None, None) def test_watch_multiple_dirs(self): first_dir = os.path.join(tmpdir, 'first') second_dir = os.path.join(tmpdir, 'second') watcher = Watcher() os.mkdir(first_dir) watcher.watch(first_dir) assert watcher.examine() == (None, None) first_path = os.path.join(first_dir, 'foo') with open(first_path, 'w') as f: f.write('') assert watcher.examine() == (first_path, None) assert watcher.examine() == (None, None) os.mkdir(second_dir) watcher.watch(second_dir) assert watcher.examine() == (None, None) second_path = os.path.join(second_dir, 'bar') with open(second_path, 'w') as f: f.write('') assert watcher.examine() == (second_path, None) assert watcher.examine() == (None, None) with open(first_path, 'a') as f: f.write('foo') assert watcher.examine() == (first_path, None) assert watcher.examine() == (None, None) os.remove(second_path) assert watcher.examine() == (second_path, None) assert watcher.examine() == (None, None)