pax_global_header00006660000000000000000000000064122043054530014510gustar00rootroot0000000000000052 comment=b2937024c7ab8f6ed1bb2d449c9f4579aac42afd python-livereload-1.0.1/000077500000000000000000000000001220430545300151545ustar00rootroot00000000000000python-livereload-1.0.1/.gitignore000066400000000000000000000012221220430545300171410ustar00rootroot00000000000000################# # Common ignore # ################# build # Test DB *.sqlite # Test test.* .hg .svn CVS *~.nib *.swp *~ ################# # python ignore # ################# *.py[co] # Packages *.egg *.egg-info dist eggs parts bin var sdist develop-eggs .installed.cfg # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox # Translations *.mo # Mr Developer .mr.developer.cfg # sphinx docs/_build ############## # Mac ignore # ############## .DS_Store *.mode1 *.mode1v3 *.mode2v3 *.perspective *.perspectivev3 *.pbxuser xcuserdata *.[oa] *(Autosaved).rtfd/ Backup[ ]of[ ]*.pages/ Backup[ ]of[ ]*.key/ Backup[ ]of[ ]*.numbers/ python-livereload-1.0.1/.gitmodules000066400000000000000000000001501220430545300173250ustar00rootroot00000000000000[submodule "docs/_themes"] path = docs/_themes url = git://github.com/lepture/flask-sphinx-themes.git python-livereload-1.0.1/LICENSE000066400000000000000000000027451220430545300161710ustar00rootroot00000000000000Copyright (c) 2012, 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-1.0.1/MANIFEST.in000066400000000000000000000001041220430545300167050ustar00rootroot00000000000000include livereload/livereload.js include LICENSE include README.rst python-livereload-1.0.1/Makefile000066400000000000000000000004771220430545300166240ustar00rootroot00000000000000.PHONY: clean-pyc clean-build docs 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 python-livereload-1.0.1/README.rst000066400000000000000000000063061220430545300166500ustar00rootroot00000000000000Python LiveReload ================= `LiveReload `_ Server in Python Version. Web Developers need to refresh a browser everytime when he saved a file (css, javascript, html), it is really boring. LiveReload will take care of that for you. When you saved a file, your browser will refresh itself. And what's more, it can do some tasks like compiling less to css before the browser refreshing. Installation ------------ Python LiveReload is designed for web developers who know Python. Install python-livereload ~~~~~~~~~~~~~~~~~~~~~~~~~ Install Python LiveReload with pip:: $ pip install livereload If you don't have pip installed, try easy_install:: $ easy_install livereload Install Browser Extensions ~~~~~~~~~~~~~~~~~~~~~~~~~~ A browser extension is not required, you can insert a script into your html page manually:: But a browser extension will make your life easier, available extensions: + Chrome Extension + Safari Extension + Firefox Extension Visit: http://help.livereload.com/kb/general-use/browser-extensions Quickstart ------------ LiveReload is designed for more complex tasks, not just for refreshing a browser. But you can still do the simple task. Assume you have livereload and its extension installed, and now you are in your working directory. With command:: $ livereload [-p port] your browser will reload, if any file in the working directory changed. LiveReload as SimpleHTTPServer ------------------------------- Livereload server can be a SimpleHTTPServer:: $ livereload -p 8000 It will set up a server at port 8000, take a look at http://127.0.0.1:8000. Oh, it can livereload! **IF YOU ARE NOT USING IT AS A HTTP SERVER, DO NOT ADD THE PORT OPTION**. Guardfile ---------- More complex tasks can be done by Guardfile. Write a Guardfile in your working directory, the basic syntax:: #!/usr/bin/env python from livereload.task import Task Task.add('static/style.css') Task.add('*.html') Now livereload will only guard static/style.css and html in your workding directory. But python-livereload is more than that, you can specify a task before refreshing the browser:: #!/usr/bin/env python from livereload.task import Task from livereload.compiler import lessc Task.add('style.less', lessc('style.less', 'style.css')) And it will compile less css before refreshing the browser now. Linux ---------- If you're using python-livereload under Linux, you should also install pyinotify, as it will greatly improve responsiveness and reduce CPU load. You may see errors such as:: [2013-06-19 11:11:07,499 pyinotify ERROR] add_watch: cannot watch somefile WD=-1, Errno=No space left on device (ENOSPC) If so, you need to increase the number of "user watches". You can either do this temporarily by running (as root):: echo 51200 > /proc/sys/fs/inotify/max_user_watches To make this change permanent, add the following line to /etc/sysctl.conf and reboot:: fs.inotify.max_user_watches = 51200 Others -------- If you are on a Mac, you can buy `LiveReload2 `_. If you are a rubist, you can get guard-livereload. python-livereload-1.0.1/docs/000077500000000000000000000000001220430545300161045ustar00rootroot00000000000000python-livereload-1.0.1/docs/Makefile000066400000000000000000000125401220430545300175460ustar00rootroot00000000000000# 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: $(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." python-livereload-1.0.1/docs/_templates/000077500000000000000000000000001220430545300202415ustar00rootroot00000000000000python-livereload-1.0.1/docs/_templates/sidebarintro.html000066400000000000000000000002411220430545300236110ustar00rootroot00000000000000

About LiveReload

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

python-livereload-1.0.1/docs/_themes/000077500000000000000000000000001220430545300175305ustar00rootroot00000000000000python-livereload-1.0.1/docs/changelog.rst000066400000000000000000000024261220430545300205710ustar00rootroot00000000000000Changelog ========= The full list of changes between each Python LiveReload release. Version 1.0.1 ------------- Release on Aug 19th, 2013 + Documentation improvement + Bugfix for server #29 + Bugfix for Task #34 Version 1.0.0 ------------- Release 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 .. _ver0.6: Version 0.6 ------------ Release on Jun 18th, 2012 + Add static server, 127.0.0.1:35729 .. _ver0.5: Version 0.5 ----------- Release on Jun 18th, 2012 + support for python3 .. _ver0.4: Version 0.4 ----------- Release on May 8th, 2012 + bugfix for notify (sorry) .. _ver0.3: 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-1.0.1/docs/compiler.rst000066400000000000000000000040161220430545300204510ustar00rootroot00000000000000.. _compiler: Compiler ========= In web development, compiling (compressing) is a common task, Python LiveReload has provided some compilers for you. Overview ---------- In :ref:`quickstart` and :ref:`guardfile` you already know ``lessc``. It is simple. But ``lessc`` just write code to a file, sometimes you don't want to write code, you want to append code. In this case, you should know the basic of a `Compiler`. ``CommandCompiler`` takes source path for constructor, and has ``init_command()`` method to setup a executable. :: from livereload.compiler import CommandCompiler c = CommandCompiler('style.less') c.init_command('lessc --compress') c.write('site.css') #: write compiled code to 'site.css' c.append('global.css') #: append compiled code to 'global.css' Quick Alias ------------ In most cases, you don't need to write every `Compiler`, you can use a simple and easy alias. The available: + lessc + uglifyjs + slimmer + coffee + shell These aliases accept ``mode`` parameter to switch calling ``write()`` or ``append()``. "``w``" leads ``write()``, while "``a``" leads ``append()``. And "``w``" is the default value. Above example can be changed as followings:: from livereload.compiler import lessc lessc('style.less', 'site.css') lessc('style.less', 'global.css', mode='a') Get static files from internet ------------------------------- New in :ref:`ver0.3`. With this new feature, you can keep the source of your project clean. If the path starts with "``http://``" or "``https://``", download it automatically. :: from livereload.compiler import uglifyjs uglifyjs('http://code.jquery.com/jquery.js', 'static/lib.js') Invoke command line task ------------------------ Using ``shell``, you can invoke any command line tasks such as *Sphinx* html documentation:: from livereload.task import Task from livereload.compiler import shell Task.add('*.rst', shell('make html')) Contribute ----------- Want more compiler? Fork GitHub Repo and send pull request to me. python-livereload-1.0.1/docs/conf.py000066400000000000000000000200031220430545300173760ustar00rootroot00000000000000# -*- 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' # 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, } # 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-1.0.1/docs/guardfile.rst000066400000000000000000000045371220430545300206110ustar00rootroot00000000000000.. _guardfile: Guardfile ========= :file:`Guardfile` is an executable python file which defines the tasks Python LiveReload should guard. Writing Guardfile is simple (or not). The basic syntax:: #!/usr/bin/env python from livereload.task import Task Task.add('static/style.css') Which means our Server should guard ``static/style.css`` , when this (and only this) file is saved, the server will send a signal to the client side, and the browser will refresh itself. Add a task ----------- In Guardfile, the most important thing is adding a task:: Task.add(...) ``Task.add`` accepts two parameters: 1. the first one is the path you want to guard 2. the second one is optional, it should be a callable function Define a path -------------- Path is the first parameter of a Task, a path can be absolute or relative: 1. a filepath: ``static/style.css`` 2. a directory path: ``static`` 3. a glob pattern: ``static/*.css`` Define a function ------------------- Function is the second parameter of a Task, it is not required. When files in the given path changed, the related function will execute. A good example in :ref:`quickstart`:: #!/usr/bin/env python from livereload.task import Task from livereload.compiler import lessc Task.add('style.less', lessc('style.less', 'style.css')) This means when ``style.less`` is saved, the server will execute:: lessc('style.less', 'style.css')() Please note that ``lessc`` here will create a function. You can't do:: #!/usr/bin/env python from livereload.task import Task def say(word): print(word) Task.add('style.less', say('hello')) Because ``say('hello')`` is not a callable function, it is executed already. But you can easily create a function by:: #!/usr/bin/env python from livereload.task import Task import functools @functools.partial def say(word): print(word) Task.add('style.less', say('hello')) And there is one more thing you should know. When the function is called, it losts its context already, which means you should never import a module outside of the task function:: #: don't import A def task1(): return A.do_some_thing() #: do def task2(): import B return B.do_some_thing() Python LiveReload provides some common tasks for web developers, check :ref:`compiler` . python-livereload-1.0.1/docs/index.rst000066400000000000000000000023161220430545300177470ustar00rootroot00000000000000Welcome to Python LiveReload ============================= `LiveReload `_ contains two parts, the client side and the server side. And Python LiveReload is the server side in python version. Web Developers need to refresh a browser everytime when he saves a file (css, javascript, html). It is really boring. LiveReload will take care of that for you. When you save a file, your browser will refresh itself. And what's more, it can do some tasks like **compiling less to css before the browser refreshing**. **Bug Report** https://github.com/lepture/python-livereload/issues User's Guide ------------- Python LiveReload is designed for **Web Developers who know Python**. It assumes that you want to do some complex tasks that LiveReload2.app can't do. If you are not, you should buy LiveReload2.app instead. .. toctree:: :maxdepth: 2 install quickstart guardfile compiler 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: lepture@me.com python-livereload-1.0.1/docs/install.rst000066400000000000000000000032671220430545300203140ustar00rootroot00000000000000.. _installation: Installation ============= This section covers the installation of Python LiveReload and other essentials to make LiveReload available. LiveReload contains two parts, the client side and the server side. Client means the browser, it listens to the server's signal, and refreshs your browser when catching the proper signals. Install Browser Extensions ---------------------------- A browser extension is not required, you can insert a script into your html page manually:: But a browser extension will make your life easier, available extensions: + Chrome Extension + Safari Extension + Firefox Extension Visit: http://help.livereload.com/kb/general-use/browser-extensions Distribute & Pip ----------------- Installing Python LiveReload is simple with pip:: $ pip install livereload If you don't have pip installed, try easy_install:: $ easy_install livereload Enhancement ------------ Python LiveReload is designed to do some complex tasks like compiling. The package itself has provided some useful compilers for you. But you need to install them first. Get Lesscss ~~~~~~~~~~~~ Lesscss_ is a dynamic stylesheet language that makes css more elegent. Install less with npm:: $ npm install less -g Get UglifyJS ~~~~~~~~~~~~ UglifyJS_ is a popular JavaScript parser/compressor/beautifier. Install UglifyJS with npm:: $ npm install uglify-js -g Get slimmer ~~~~~~~~~~~~ Slimmer is a python library that compressing css, JavaScript, and html. Install slimmer:: $ pip install slimmer .. _Lesscss: http://lesscss.org .. _UglifyJs: https://github.com/mishoo/UglifyJS python-livereload-1.0.1/docs/quickstart.rst000066400000000000000000000033451220430545300210350ustar00rootroot00000000000000.. _quickstart: Quickstart ========== This section assumes that you have everything installed. If you do not, head over to the :ref:`installation` section. Simple Task ------------ LiveReload is designed for more complex tasks, not just for refreshing a browser. But you can still do the simple task. Assume you have livereload and its extension installed, and now you are in your working directory. With command:: $ livereload your browser will reload, if any file in the working directory changed. Working with file protocal --------------------------- Enable file protocal on Chrome: .. image:: http://i.imgur.com/qGpJI.png Guardfile ---------- More complex tasks can be done by Guardfile. Write a Guardfile in your working directory, the basic syntax:: #!/usr/bin/env python from livereload.task import Task Task.add('static/style.css') Task.add('*.html') Now livereload will only guard static/style.css and html in your workding directory. But python-livereload is more than that, you can specify a task before refreshing the browser:: #!/usr/bin/env python from livereload.task import Task from livereload.compiler import lessc Task.add('style.less', lessc('style.less', 'style.css')) And it will compile less css before refreshing the browser now. Want to know about :ref:`guardfile` ? Commands like Makefile ----------------------- New in :ref:`ver0.3` If you want to do some tasks in Guardfile manually:: # Guardfile def task1(): print('task1') def task2(): print('task2') In terminal:: $ livereload task1 task2 Others -------- If you are on a Mac, you can buy `LiveReload2 `_. If you are a rubist, you can get guard-livereload. python-livereload-1.0.1/example/000077500000000000000000000000001220430545300166075ustar00rootroot00000000000000python-livereload-1.0.1/example/Guardfile000066400000000000000000000002541220430545300204350ustar00rootroot00000000000000#!/usr/bin/env python from livereload.task import Task from livereload.compiler import lessc Task.add('style.less', lessc('style.less', 'style.css')) Task.add('*.html') python-livereload-1.0.1/example/index.html000066400000000000000000000004141220430545300206030ustar00rootroot00000000000000 Python LiveReload Example

Example

Container

python-livereload-1.0.1/example/style.less000066400000000000000000000001131220430545300206320ustar00rootroot00000000000000@bg: #222; @fg: #fff; html, body { background: @bg; color: @fg; } python-livereload-1.0.1/livereload/000077500000000000000000000000001220430545300173025ustar00rootroot00000000000000python-livereload-1.0.1/livereload/__init__.py000066400000000000000000000101061220430545300214110ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright (c) 2012, 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 ================= `LiveReload `_ Server in Python Version. Web Developers need to refresh a browser everytime when he saved a file (css, javascript, html), it is really boring. LiveReload will take care of that for you. When you saved a file, your browser will refresh itself. And what's more, it can do some tasks like compiling less to css before the browser refreshing. Installation ------------ Python LiveReload is designed for web developers who know Python. Install python-livereload ~~~~~~~~~~~~~~~~~~~~~~~~~ Install Python LiveReload with pip:: $ pip install livereload If you don't have pip installed, try easy_install:: $ easy_install livereload Install Browser Extensions ~~~~~~~~~~~~~~~~~~~~~~~~~~ Get Browser Extensions From LiveReload.com + Chrome Extension + Safari Extension + Firefox Extension Visit: http://help.livereload.com/kb/general-use/browser-extensions Get Notification ~~~~~~~~~~~~~~~~~ If you are on Mac, and you are a Growl user:: $ pip install gntp If you are on Ubuntu, you don't need to do anything. Notification just works. Working with file protocal ~~~~~~~~~~~~~~~~~~~~~~~~~~ Enable file protocal on Chrome: .. image:: http://i.imgur.com/qGpJI.png Quickstart ------------ LiveReload is designed for more complex tasks, not just for refreshing a browser. But you can still do the simple task. Assume you have livereload and its extension installed, and now you are in your working directory. With command:: $ livereload your browser will reload, if any file in the working directory changed. Guardfile ---------- More complex tasks can be done by Guardfile. Write a Guardfile in your working directory, the basic syntax:: #!/usr/bin/env python from livereload.task import Task Task.add('static/style.css') Task.add('*.html') Now livereload will only guard static/style.css and html in your workding directory. But python-livereload is more than that, you can specify a task before refreshing the browser:: #!/usr/bin/env python from livereload.task import Task from livereload.compiler import lessc Task.add('style.less', lessc('style.less', 'style.css')) And it will compile less css before refreshing the browser now. Others -------- If you are on a Mac, you can buy `LiveReload2 `_. If you are a rubist, you can get guard-livereload. """ __version__ = '1.0.1' __author__ = 'Hsiaoming Yang ' __homepage__ = 'http://lab.lepture.com/livereload/' python-livereload-1.0.1/livereload/cli.py000066400000000000000000000011731220430545300204250ustar00rootroot00000000000000#!/usr/bin/env python from docopt import docopt from .server import start cmd = """Python LiveReload Usage: livereload [-p |--port=] [-b|--browser] [] Options: -h --help show this screen -p --port= specify a server port, default is 35729 -b --browser open browser when start server """ def main(): args = docopt(cmd) port = args.get('--port') root = args.get('') autoraise = args.get('--browser') if port: port = int(port) else: port = 35729 start(port, root, autoraise) python-livereload-1.0.1/livereload/compiler.py000066400000000000000000000105261220430545300214720ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- """livereload.compiler Provides a set of compilers for web developers. Available compilers now: + less + coffee + uglifyjs + slimmer """ import os import functools import logging from subprocess import Popen, PIPE def make_folder(dest): folder = os.path.split(dest)[0] if not folder: return if os.path.isdir(folder): return try: os.makedirs(folder) except: pass def _get_http_file(url, build_dir='build/assets'): import hashlib key = hashlib.md5(url).hexdigest() filename = os.path.join(os.getcwd(), build_dir, key) if os.path.exists(filename): return filename make_folder(filename) import urllib print('Downloading: %s' % url) urllib.urlretrieve(url, filename) return filename class BaseCompiler(object): """BaseCompiler BaseCompiler defines the basic syntax of a Compiler. >>> c = BaseCompiler('a') >>> c.write('b') #: write compiled code to 'b' >>> c.append('c') #: append compiled code to 'c' """ def __init__(self, path=None): if path: if path.startswith('http://') or path.startswith('https://'): path = _get_http_file(path) self.filetype = os.path.splitext(path)[1] self.path = path def get_code(self): f = open(self.path) code = f.read() f.close() return code def write(self, output): """write code to output""" logging.info('write %s' % output) make_folder(output) f = open(output, 'w') code = self.get_code() if code: f.write(code) f.close() def append(self, output): """append code to output""" logging.info('append %s' % output) make_folder(output) f = open(output, 'a') f.write(self.get_code()) f.close() def __call__(self, output, mode='w'): if mode == 'a': self.append(output) return self.write(output) return class CommandCompiler(BaseCompiler): def init_command(self, command, source=None): self.command = command self.source = source def get_code(self): cmd = self.command.split() if self.path: cmd.append(self.path) try: p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE) except OSError as e: logging.error(e) if e.errno == os.errno.ENOENT: # file (command) not found logging.error("maybe you haven't installed %s", cmd[0]) return None if self.source: stdout, stderr = p.communicate(input=self.source) else: stdout, stderr = p.communicate() if stderr: logging.error(stderr) return None #: stdout is bytes, decode for python3 return stdout.decode() def lessc(path, output, mode='w'): _compile = CommandCompiler(path) _compile.init_command('lessc --compress') return functools.partial(_compile, output, mode) def uglifyjs(path, output, mode='w'): _compile = CommandCompiler(path) _compile.init_command('uglifyjs --nc') return functools.partial(_compile, output, mode) class SlimmerCompiler(BaseCompiler): def get_code(self): import slimmer f = open(self.path) code = f.read() f.close() if self.filetype == '.css': return slimmer.css_slimmer(code) if self.filetype == '.js': return slimmer.js_slimmer(code) if self.filetype == '.html': return slimmer.xhtml_slimmer(code) return code def slimmer(path, output, mode='w'): _compile = SlimmerCompiler(path) return functools.partial(_compile, output, mode) def rstc(path, output, mode='w'): _compile = CommandCompiler(path) _compile.init_command('rst2html.py') return functools.partial(_compile, output, mode) def shell(command, path=None, output=os.devnull, mode='w'): _compile = CommandCompiler(path) _compile.init_command(command) return functools.partial(_compile, output, mode) def coffee(path, output, mode='w'): _compile = CommandCompiler(path) f = open(path) code = f.read() f.close() _compile.init_command('coffee --compile --stdio', code) return functools.partial(_compile, output, mode) python-livereload-1.0.1/livereload/livereload.js000066400000000000000000001014731220430545300217740ustar00rootroot00000000000000(function() { var __customevents = {}, __protocol = {}, __connector = {}, __timer = {}, __options = {}, __reloader = {}, __livereload = {}, __less = {}, __startup = {}; // customevents var CustomEvents; CustomEvents = { bind: function(element, eventName, handler) { if (element.addEventListener) { return element.addEventListener(eventName, handler, false); } else if (element.attachEvent) { element[eventName] = 1; return element.attachEvent('onpropertychange', function(event) { if (event.propertyName === eventName) { return handler(); } }); } else { throw new Error("Attempt to attach custom event " + eventName + " to something which isn't a DOMElement"); } }, fire: function(element, eventName) { var event; if (element.addEventListener) { event = document.createEvent('HTMLEvents'); event.initEvent(eventName, true, true); return document.dispatchEvent(event); } else if (element.attachEvent) { if (element[eventName]) { return element[eventName]++; } } else { throw new Error("Attempt to fire custom event " + eventName + " on something which isn't a DOMElement"); } } }; __customevents.bind = CustomEvents.bind; __customevents.fire = CustomEvents.fire; // protocol var PROTOCOL_6, PROTOCOL_7, Parser, ProtocolError; var __indexOf = Array.prototype.indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (this[i] === item) return i; } return -1; }; __protocol.PROTOCOL_6 = PROTOCOL_6 = 'http://livereload.com/protocols/official-6'; __protocol.PROTOCOL_7 = PROTOCOL_7 = 'http://livereload.com/protocols/official-7'; __protocol.ProtocolError = ProtocolError = (function() { function ProtocolError(reason, data) { this.message = "LiveReload protocol error (" + reason + ") after receiving data: \"" + data + "\"."; } return ProtocolError; })(); __protocol.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, 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 (e) { if (e instanceof ProtocolError) { return this.handlers.error(e); } else { throw e; } } }; Parser.prototype._parseMessage = function(data, validCommands) { var message, _ref; try { message = JSON.parse(data); } catch (e) { 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; })(); // connector // Generated by CoffeeScript 1.3.3 var Connector, PROTOCOL_6, PROTOCOL_7, Parser, Version, _ref; _ref = __protocol, Parser = _ref.Parser, PROTOCOL_6 = _ref.PROTOCOL_6, PROTOCOL_7 = _ref.PROTOCOL_7; Version = '2.0.8'; __connector.Connector = Connector = (function() { function Connector(options, WebSocket, Timer, handlers) { var _this = this; this.options = options; this.WebSocket = WebSocket; this.Timer = Timer; this.handlers = handlers; this._uri = "ws://" + this.options.host + ":" + this.options.port + "/livereload"; this._nextDelay = this.options.mindelay; this._connectionDesired = false; this.protocol = 0; this.protocolParser = new Parser({ connected: function(protocol) { _this.protocol = protocol; _this._handshakeTimeout.stop(); _this._nextDelay = _this.options.mindelay; _this._disconnectionReason = 'broken'; return _this.handlers.connected(protocol); }, error: function(e) { _this.handlers.error(e); return _this._closeOnError(); }, message: function(message) { return _this.handlers.message(message); } }); this._handshakeTimeout = new Timer(function() { if (!_this._isSocketConnected()) { return; } _this._disconnectionReason = 'handshake-timeout'; return _this.socket.close(); }); this._reconnectTimer = new Timer(function() { if (!_this._connectionDesired) { return; } return _this.connect(); }); this.connect(); } Connector.prototype._isSocketConnected = function() { return this.socket && this.socket.readyState === this.WebSocket.OPEN; }; Connector.prototype.connect = function() { var _this = this; this._connectionDesired = true; if (this._isSocketConnected()) { return; } this._reconnectTimer.stop(); this._disconnectionReason = 'cannot-connect'; this.protocolParser.reset(); this.handlers.connecting(); this.socket = new this.WebSocket(this._uri); this.socket.onopen = function(e) { return _this._onopen(e); }; this.socket.onclose = function(e) { return _this._onclose(e); }; this.socket.onmessage = function(e) { return _this._onmessage(e); }; return this.socket.onerror = function(e) { return _this._onerror(e); }; }; Connector.prototype.disconnect = function() { this._connectionDesired = false; this._reconnectTimer.stop(); if (!this._isSocketConnected()) { return; } this._disconnectionReason = 'manual'; return this.socket.close(); }; Connector.prototype._scheduleReconnection = function() { if (!this._connectionDesired) { return; } if (!this._reconnectTimer.running) { this._reconnectTimer.start(this._nextDelay); return this._nextDelay = Math.min(this.options.maxdelay, this._nextDelay * 2); } }; Connector.prototype.sendCommand = function(command) { if (this.protocol == null) { return; } return this._sendCommand(command); }; Connector.prototype._sendCommand = function(command) { return this.socket.send(JSON.stringify(command)); }; Connector.prototype._closeOnError = function() { this._handshakeTimeout.stop(); this._disconnectionReason = 'error'; return this.socket.close(); }; Connector.prototype._onopen = function(e) { var hello; this.handlers.socketConnected(); this._disconnectionReason = 'handshake-failed'; hello = { command: 'hello', protocols: [PROTOCOL_6, PROTOCOL_7] }; hello.ver = Version; if (this.options.ext) { hello.ext = this.options.ext; } if (this.options.extver) { hello.extver = this.options.extver; } if (this.options.snipver) { hello.snipver = this.options.snipver; } this._sendCommand(hello); return this._handshakeTimeout.start(this.options.handshake_timeout); }; Connector.prototype._onclose = function(e) { this.protocol = 0; this.handlers.disconnected(this._disconnectionReason, this._nextDelay); return this._scheduleReconnection(); }; Connector.prototype._onerror = function(e) {}; Connector.prototype._onmessage = function(e) { return this.protocolParser.process(e.data); }; return Connector; })(); // timer var Timer; var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; __timer.Timer = Timer = (function() { function Timer(func) { this.func = func; this.running = false; this.id = null; this._handler = __bind(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); }; // options var Options; __options.Options = Options = (function() { function Options() { this.host = null; this.port = {{port}}; 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) { switch (typeof this[name]) { case 'undefined': break; case 'number': return this[name] = +value; default: return this[name] = value; } }; return Options; })(); Options.extract = function(document) { var element, keyAndValue, m, mm, options, pair, src, _i, _j, _len, _len2, _ref, _ref2; _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]) { _ref2 = m[2].split('&'); for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) { pair = _ref2[_j]; if ((keyAndValue = pair.split('=')).length > 1) { options.set(keyAndValue[0].replace(/-/g, '_'), keyAndValue.slice(1).join('=')); } } } return options; } } return null; }; // reloader // Generated by CoffeeScript 1.3.1 (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'] } ]; __reloader.Reloader = Reloader = (function() { Reloader.name = 'Reloader'; 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 rule, rules, styleNames, _i, _j, _len, _len1; try { rules = styleSheet != null ? styleSheet.cssRules : void 0; } catch (e) { } 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, _this = this; 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(match, src) { if (pathsMatch(path, pathFromUrl(src))) { return "url(" + (_this.generateCacheBustUrl(src, expando)) + ")"; } else { return match; } }); 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, _this = this; 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 === 'stylesheet' && !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(l) { return pathFromUrl(_this.linkHref(l)); }); 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 index, rule, rules, _i, _len; try { rules = styleSheet != null ? styleSheet.cssRules : void 0; } catch (e) { } 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, _this = this; callbackExecuted = false; executeCallback = function() { if (callbackExecuted) { return; } callbackExecuted = true; return func(); }; clone.onload = function() { console.log("onload!"); _this.knownToSupportCssOnLoad = true; return executeCallback(); }; if (!this.knownToSupportCssOnLoad) { (poll = function() { if (clone.sheet) { console.log("polling!"); return executeCallback(); } else { return _this.Timer.start(50, poll); } })(); } 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, _this = this; 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() { 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; }); }); }; Reloader.prototype.reattachImportedRule = function(_arg) { var href, index, link, media, newRule, parent, rule, tempLink, _this = this; 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() { 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); }); }); }; Reloader.prototype.generateUniqueString = function() { return 'livereload=' + Date.now(); }; Reloader.prototype.generateCacheBustUrl = function(url, expando) { var hash, oldParams, 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) { url = this.options.serverURL + this.options.overrideURL + "?url=" + encodeURIComponent(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); // livereload var Connector, LiveReload, Options, Reloader, Timer; Connector = __connector.Connector; Timer = __timer.Timer; Options = __options.Options; Reloader = __reloader.Reloader; __livereload.LiveReload = LiveReload = (function() { function LiveReload(window) { var _this = this; this.window = window; this.listeners = {}; this.plugins = []; this.pluginIdentifiers = {}; this.console = this.window.location.href.match(/LR-verbose/) && this.window.console && this.window.console.log && this.window.console.error ? this.window.console : { log: function() {}, error: function() {} }; if (!(this.WebSocket = this.window.WebSocket || this.window.MozWebSocket)) { console.error("LiveReload disabled because the browser does not seem to support web sockets"); return; } if (!(this.options = Options.extract(this.window.document))) { console.error("LiveReload disabled because it could not find its own ') def read_path(self, abspath): filepath = abspath if os.path.isdir(filepath): filepath = os.path.join(abspath, 'index.html') if not os.path.exists(filepath): self.create_index(abspath) return elif not os.path.exists(abspath): filepath = abspath + '.html' if os.path.exists(filepath): if self.mime_type == 'text/html': f = open(filepath) data = f.read() f.close() before, after = data.split('') self.write(before) self.inject_livereload() self.write('') self.write(after) else: f = open(filepath, 'rb') data = f.read() f.close() self.write(data) hasher = hashlib.sha1() hasher.update(data) self.set_header('Etag', '"%s"' % hasher.hexdigest()) return self.send_error(404) return def create_index(self, root): self.inject_livereload() files = os.listdir(root) self.write('
    ') for f in files: path = os.path.join(root, f) self.write('
  • ') if os.path.isdir(path): self.write('%s' % (f, f)) else: self.write('%s' % (f, f)) self.write('
  • ') self.write('
') class LiveReloadJSHandler(RequestHandler): def get(self): f = open(LIVERELOAD) self.set_header('Content-Type', 'application/javascript') for line in f: if '{{port}}' in line: line = line.replace('{{port}}', str(PORT)) self.write(line) f.close() handlers = [ (r'/livereload', LiveReloadHandler), (r'/livereload.js', LiveReloadJSHandler), (r'(.*)', IndexHandler), ] def start(port=35729, root='.', autoraise=False): global PORT PORT = port global ROOT if root is None: root = '.' ROOT = root logging.getLogger().setLevel(logging.INFO) enable_pretty_logging() app = Application(handlers=handlers) app.listen(port) print('Serving path %s on 127.0.0.1:%s' % (root, port)) if autoraise: webbrowser.open( 'http://127.0.0.1:%s' % port, new=2, autoraise=True ) try: ioloop.IOLoop.instance().start() except KeyboardInterrupt: print('Shutting down...') if __name__ == '__main__': start(8000) python-livereload-1.0.1/livereload/task.py000066400000000000000000000070321220430545300206200ustar00rootroot00000000000000# -*- coding: utf-8 -*- """livereload.task Task management for LiveReload Server. A basic syntax overview:: from livereload.task import Task Task.add('file.css') def do_some_thing(): pass Task.add('file.css', do_some_thing) """ import os import glob import logging try: import pyinotify from tornado import ioloop class TaskEventHandler(pyinotify.ProcessEvent): def my_init(self, **kwargs): self.func = kwargs['func'] def process_default(self, event): if Task.watch(): self.func() HAS_PYINOTIFY = True except ImportError: HAS_PYINOTIFY = False IGNORE = [ '.pyc', '.pyo', '.o', '.swp' ] class Task(object): tasks = {} _modified_times = {} last_modified = None if HAS_PYINOTIFY: wm = pyinotify.WatchManager() notifier = None @classmethod def add(cls, path, func=None): logging.info('Add task: %s' % path) if HAS_PYINOTIFY: cls.wm.add_watch(path, pyinotify.IN_CREATE | pyinotify.IN_DELETE | pyinotify.IN_MODIFY, rec=True, do_glob=True, auto_add=True) cls.tasks[path] = func @classmethod def start(cls, func): if HAS_PYINOTIFY: if not cls.notifier: cls.notifier = pyinotify.TornadoAsyncNotifier(cls.wm, ioloop.IOLoop.instance(), default_proc_fun=TaskEventHandler(func=func)) Task.watch() # initial run so we don't miss the first change return HAS_PYINOTIFY @classmethod def watch(cls): _changed = False for path in cls.tasks: if cls.is_changed(path): _changed = True func = cls.tasks[path] func and func() return _changed @classmethod def is_changed(cls, path): def is_file_changed(path): if not os.path.isfile(path): return False _, ext = os.path.splitext(path) if ext in IGNORE: return False modified = int(os.stat(path).st_mtime) if path not in cls._modified_times: cls._modified_times[path] = modified return True if path in cls._modified_times and \ cls._modified_times[path] != modified: logging.info('file changed: %s' % path) cls._modified_times[path] = modified cls.last_modified = path return True cls._modified_times[path] = modified return False def is_folder_changed(path): _changed = False for root, dirs, files in os.walk(path, followlinks=True): if '.git' in dirs: dirs.remove('.git') if '.hg' in dirs: dirs.remove('.hg') if '.svn' in dirs: dirs.remove('.svn') if '.cvs' in dirs: dirs.remove('.cvs') for f in files: if is_file_changed(os.path.join(root, f)): _changed = True return _changed def is_glob_changed(path): _changed = False for f in glob.glob(path): if is_file_changed(f): _changed = True return _changed if os.path.isfile(path): return is_file_changed(path) elif os.path.isdir(path): return is_folder_changed(path) else: return is_glob_changed(path) return False python-livereload-1.0.1/setup.py000066400000000000000000000032231220430545300166660ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- import os ROOT = os.path.dirname(__file__) import sys kwargs = {} kwargs['include_package_data'] = True major, minor = sys.version_info[:2] if major >= 3: kwargs['use_2to3'] = True from setuptools import setup, find_packages import livereload from email.utils import parseaddr author, author_email = parseaddr(livereload.__author__) setup( name='livereload', version=livereload.__version__, author=author, author_email=author_email, url=livereload.__homepage__, packages=find_packages(), description='Python LiveReload is an awesome tool for web developers', long_description=livereload.__doc__, entry_points={ 'console_scripts': ['livereload= livereload.cli:main'], }, install_requires=[ 'tornado', 'docopt', ], license=open(os.path.join(ROOT, 'LICENSE')).read(), 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 :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Software Development :: Build Tools', 'Topic :: Software Development :: Compilers', 'Topic :: Software Development :: Debuggers', ], **kwargs ) python-livereload-1.0.1/tests/000077500000000000000000000000001220430545300163165ustar00rootroot00000000000000python-livereload-1.0.1/tests/test_compiler.py000066400000000000000000000000221220430545300215330ustar00rootroot00000000000000#!/usr/bin/python