From aabde1ce65f034361889d1b8614fbf561704ba4a Mon Sep 17 00:00:00 2001 From: Alan Hamlett Date: Wed, 24 Aug 2016 01:54:36 +0200 Subject: [PATCH] v1.0.0 --- .coveragerc | 8 ++ .gitignore | 39 ++++++++++ .travis.yml | 19 +++++ AUTHORS | 13 ++++ HISTORY.rst | 9 +++ LICENSE | 28 +++++++ MANIFEST.in | 2 + README.rst | 67 ++++++++++++++++ dev-requirements.txt | 7 ++ readtime/__about__.py | 10 +++ readtime/__init__.py | 17 ++++ readtime/_compat.py | 51 ++++++++++++ readtime/api.py | 40 ++++++++++ readtime/result.py | 34 ++++++++ readtime/utils.py | 107 +++++++++++++++++++++++++ requirements.txt | 3 + setup.cfg | 6 ++ setup.py | 44 +++++++++++ tests/__init__.py | 0 tests/samples/html.html | 141 +++++++++++++++++++++++++++++++++ tests/samples/markdown.md | 147 +++++++++++++++++++++++++++++++++++ tests/samples/plain_text.txt | 103 ++++++++++++++++++++++++ tests/test_readtime.py | 50 ++++++++++++ tests/utils.py | 36 +++++++++ tox.ini | 7 ++ 25 files changed, 988 insertions(+) create mode 100644 .coveragerc create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 AUTHORS create mode 100644 HISTORY.rst create mode 100644 LICENSE create mode 100644 MANIFEST.in create mode 100644 README.rst create mode 100644 dev-requirements.txt create mode 100644 readtime/__about__.py create mode 100644 readtime/__init__.py create mode 100644 readtime/_compat.py create mode 100644 readtime/api.py create mode 100644 readtime/result.py create mode 100644 readtime/utils.py create mode 100644 requirements.txt create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 tests/__init__.py create mode 100644 tests/samples/html.html create mode 100644 tests/samples/markdown.md create mode 100644 tests/samples/plain_text.txt create mode 100644 tests/test_readtime.py create mode 100644 tests/utils.py create mode 100644 tox.ini diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..7fdd5aa --- /dev/null +++ b/.coveragerc @@ -0,0 +1,8 @@ +[run] +branch = false +omit = + readtime/__about__.py +[report] +omit = + readtime/__about__.py + */python?.?/* diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a8b8ca4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +*.py[cod] + +# C extensions +*.so + +# Packages +*.egg +*.egg-info +dist +build +eggs +parts +bin +var +sdist +develop-eggs +.installed.cfg +lib +lib64 + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.coverage +.tox +nosetests.xml + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +virtualenv +venv +.DS_Store diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c5b6d1a --- /dev/null +++ b/.travis.yml @@ -0,0 +1,19 @@ +language: python +python: + - "2.6" + - "2.7" + - "3.3" + - "3.4" + - "3.5" +# command to install dependencies +install: + - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then travis_retry pip install unittest2; fi + - travis_retry pip install -r dev-requirements.txt + - travis_retry pip install coveralls +# use new travis-ci container-based infrastructure +sudo: false +# command to run tests +script: nosetests +# command to run after tests +after_success: + - coveralls diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..d67a747 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,13 @@ +readtime is written and maintained by Alan Hamlett and +various contributors: + + +Development Lead +---------------- + +- Alan Hamlett + + +Patches and Suggestions +----------------------- + diff --git a/HISTORY.rst b/HISTORY.rst new file mode 100644 index 0000000..f6e70a8 --- /dev/null +++ b/HISTORY.rst @@ -0,0 +1,9 @@ + +History +------- + + +1.0.0 (2016-10-23) +++++++++++++++++++ + +- Birth. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7bbee8a --- /dev/null +++ b/LICENSE @@ -0,0 +1,28 @@ +BSD License +=========== + +Copyright (c) 2016 by the respective authors (see AUTHORS file). +All rights reserved. + +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. + +THIS SOFTWARE AND DOCUMENTATION 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. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..4414b97 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include README.rst LICENSE HISTORY.rst requirements.txt +recursive-include readtime *.py diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..d133e8b --- /dev/null +++ b/README.rst @@ -0,0 +1,67 @@ +readtime +======== + +.. image:: https://travis-ci.org/alanhamlett/readtime.svg?branch=master + :target: https://travis-ci.org/alanhamlett/readtime + :alt: Tests + +.. image:: https://coveralls.io/repos/github/alanhamlett/readtime/badge.svg?branch=master + :target: https://coveralls.io/github/alanhamlett/readtime?branch=master + :alt: Coverage + +Calculates the time some text takes the average human to read, based on +Medium's `read time forumula `_. + + +Installation +------------ + +:: + + virtualenv venv + . venv/bin/activate + pip install readtime + +Or if you like to live dangerously:: + + sudo pip install readtime + + +Usage +----- + +Import ``readtime`` and pass it some text, HTML, or Markdown to get back the +time it takes to read:: + + >>> import readtime + >>> result = readtime.of_text('The shortest blog post in the world!') + >>> result.seconds + 2 + >>> result.text + u'1 min' + +The result can also be used as a string:: + + >>> str(readtime.of_text('The shortest blog post in the world!')) + u'1 min read' + +To calculate read time of Markdown:: + + readtime.of_markdown('This is **Markdown**') + +To calculate read time of HTML:: + + readtime.of_html('This is HTML') + + +Contributing +------------ + +Before contributing a pull request, make sure tests pass:: + + virtualenv venv + . venv/bin/activate + pip install tox + tox + +Many thanks to all `contributors `_! diff --git a/dev-requirements.txt b/dev-requirements.txt new file mode 100644 index 0000000..74d1924 --- /dev/null +++ b/dev-requirements.txt @@ -0,0 +1,7 @@ +-r requirements.txt + +coverage==4.2 +nose==1.3.7 +nose-capturestderr==1.2 +tox==2.3.1 +-e git://github.com/alanhamlett/nose-exclude.git@f8ad6b1111e3927ecfaafd26f6952745c4b8df0c#egg=nose-exclude diff --git a/readtime/__about__.py b/readtime/__about__.py new file mode 100644 index 0000000..1ec8705 --- /dev/null +++ b/readtime/__about__.py @@ -0,0 +1,10 @@ +__title__ = 'readtime' +__description__ = 'Calculates the time some text takes the average human to ' \ + 'read, based on Medium\'s read time forumula' +__url__ = 'https://github.com/alanhamlett/readtime' +__version_info__ = ('1', '0', '0') +__version__ = '.'.join(__version_info__) +__author__ = 'Alan Hamlett' +__author_email__ = 'alan.hamlett@gmail.com' +__license__ = 'BSD' +__copyright__ = 'Copyright 2016 Alan Hamlett' diff --git a/readtime/__init__.py b/readtime/__init__.py new file mode 100644 index 0000000..2d66335 --- /dev/null +++ b/readtime/__init__.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +""" + readtime + ~~~~~~~~ + + Calculates the time some text takes the average human to read. + + :copyright: (c) 2016 Alan Hamlett. + :license: BSD, see LICENSE for more details. +""" + + +from .api import ( + of_text, + of_html, + of_markdown, +) diff --git a/readtime/_compat.py b/readtime/_compat.py new file mode 100644 index 0000000..4baad69 --- /dev/null +++ b/readtime/_compat.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +""" + readtime.compat + ~~~~~~~~~~~~~~~ + + For working with Python2 and Python3. + + :copyright: (c) 2016 Alan Hamlett. + :license: BSD, see LICENSE for more details. +""" + + +import sys + + +IS_PY2 = sys.version_info[0] == 2 + + +if IS_PY2: # pragma: nocover + + def u(text): + if text is None: + return None + try: + return text.decode('utf-8') + except: + try: + return text.decode(sys.getdefaultencoding()) + except: + try: + return unicode(text) + except: + return text + +else: # pragma: nocover + + def u(text): + if text is None: + return None + if isinstance(text, bytes): + try: + return text.decode('utf-8') + except: + try: + return text.decode(sys.getdefaultencoding()) + except: + pass + try: + return str(text) + except: + return text diff --git a/readtime/api.py b/readtime/api.py new file mode 100644 index 0000000..0cec45c --- /dev/null +++ b/readtime/api.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +""" + readtime.api + ~~~~~~~~~~~~ + + Contains public methods. + + :copyright: (c) 2016 Alan Hamlett. + :license: BSD, see LICENSE for more details. +""" + + +from . import utils + + +def of_text(text): + """Get the read time of some text. + + :param text: String of text (Assumes utf-8). + """ + + return utils.read_time(text, format='text') + + +def of_html(html): + """Get the read time of some HTML. + + :param html: String of HTML. + """ + + return utils.read_time(html, format='html') + + +def of_markdown(markdown): + """Get the read time of some Markdown. + + :param markdown: String of Markdown. + """ + + return utils.read_time(markdown, format='markdown') diff --git a/readtime/result.py b/readtime/result.py new file mode 100644 index 0000000..7705451 --- /dev/null +++ b/readtime/result.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +""" + readtime.result + ~~~~~~~~~~~~~~~ + + For returning read time results. + + :copyright: (c) 2016 Alan Hamlett. + :license: BSD, see LICENSE for more details. +""" + + +from __future__ import division + +from ._compat import u + + +class Result(object): + seconds = None + minutes = None + text = None + + def __init__(self, seconds): + self.seconds = seconds + self.minutes = int(round(seconds / 60)) + if self.minutes < 1: + self.minutes = 1 + self.text = u('{0} min').format(self.minutes) + + def __repr__(self): + return self.text + ' read' + + def __unicode__(self): + return self.__repr__() # pragma: nocover diff --git a/readtime/utils.py b/readtime/utils.py new file mode 100644 index 0000000..3aded0a --- /dev/null +++ b/readtime/utils.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- +""" + readtime.utils + ~~~~~~~~~~~~~~ + + Utility and non-public methods. + + :copyright: (c) 2016 Alan Hamlett. + :license: BSD, see LICENSE for more details. +""" + + +from __future__ import division + +import lxml +import markdown2 +import re +from pyquery import PyQuery as pq + +from .result import Result +from ._compat import u + + +READING_SPEED = 275 + + +def read_time(content, format=None): + """Returns the read time of some content. + + :param content: String of content. + :param format: Format of the content (html, markdown, or text). + """ + + try: + format = format.lower() + except: + pass + + if format == 'text': + seconds = read_time_as_seconds(content) + + elif format == 'markdown': + html = markdown2.markdown(content) + el = pq(html) + text, images = parse_html(el) + seconds = read_time_as_seconds(text, images=images) + + elif format == 'html': + el = pq(content) + text, images = parse_html(el) + seconds = read_time_as_seconds(text, images=images) + + else: + raise Exception(u('Unsupported format: {0}').format(format)) + + return Result(seconds) + + +def read_time_as_seconds(text, images=0): + """Returns the read time as seconds of some plain text. + + :param text: String of plain text. + :param images: The number of inline images in the text. + """ + + num_words = len(text.split()) + seconds = int(round(num_words / READING_SPEED * 60)) + + # add extra seconds for inline images + delta = 12 + for img in range(images): + seconds += delta + if delta > 3: + delta -= 1 + + return seconds + + +def parse_html(el): + """Converts HTML to plain text. + + Returns a tuple of (plain_text, num_images). + + :param el: A PyQuery DOM object. + """ + + text = [] + images = [] + paragraphs = ['h1', 'h2', 'h3', 'h4', 'h5'] + def add_text(tag, no_tail=False): + if tag.tag == 'img': + images.append(tag) + if tag.text and not isinstance(tag, lxml.etree._Comment): + text.append(tag.text) + for child in tag.getchildren(): + add_text(child) + if tag.tag in paragraphs and len(text) > 0 and not text[-1].strip().endswith('.'): + text.append('.') + if not no_tail and tag.tail: + text.append(tag.tail) + + for tag in el: + add_text(tag, no_tail=True) + + plain_text = re.sub(r'\s+', ' ', ''.join([t for t in text if t])).strip() + + return plain_text, len(images) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..fc418ca --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +beautifulsoup4==4.5.1 +markdown2==2.3.1 +pyquery==1.2.13 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..5a0f1e2 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,6 @@ +[nosetests] +with-coverage = 1 +cover-inclusive = 1 +cover-package = readtime +exclude-dir = + venv diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..2c03b70 --- /dev/null +++ b/setup.py @@ -0,0 +1,44 @@ +from setuptools import setup + +about = {} +with open('readtime/__about__.py') as f: + exec(f.read(), about) + +packages = [ + about['__title__'], +] + +install_requires = [x.strip() for x in open('requirements.txt').readlines()] + +setup( + name=about['__title__'], + version=about['__version__'], + license=about['__license__'], + description=about['__description__'], + long_description=open('README.rst').read(), + author=about['__author__'], + author_email=about['__author_email__'], + url=about['__url__'], + packages=packages, + package_dir={about['__title__']: about['__title__']}, + include_package_data=True, + zip_safe=False, + platforms='any', + install_requires=install_requires, + classifiers=( + 'Development Status :: 5 - Production/Stable', + 'Environment :: Console', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', + 'Natural Language :: English', + 'Topic :: Software Development :: Build Tools', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + ), +) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/samples/html.html b/tests/samples/html.html new file mode 100644 index 0000000..ed5bffb --- /dev/null +++ b/tests/samples/html.html @@ -0,0 +1,141 @@ +

Want to add a feature or automate something in your NetBeans IDE? Follow along as we write your first plugin for NetBeans.

+ +

Let's go beyond the simple Toolbar Example and create a plugin which can auto-update itself. +This code is based on the WakaTime plugin for NetBeans. Our example plugin will simply print a Hello World statement and update to new versions if available... just enough to get you started.

+ +

Create a new Plugin Project

+ +

Choose File -> New Project then NetBeans Modules -> Module as the project type.

+ +

Create Plugin Project

+ +

Name your project

+ +

Name Your Project

+ +

Choose a namespace or code name for your plugin

+ +

Namespace Your Project

+ +

Add a Java File

+ +

Create Java File

+ +

Name Java File

+ +

Plugin Starting Point

+ +

After creating the new Java Class file, make it extend ModuleInstall and wrap it with @OnShowing so it only runs after the GUI has loaded.

+ +

java +@OnShowing +public class MyPlugin extends ModuleInstall implements Runnable { +} +

+ +

Press ALT + ENTER with your cursor over OnShowing then select Search Module Dependency for OnShowing to import the Window System API into the project. This will add a new dependency to your project as well as add the necessary import statements to the top of your file. Also do this for ModuleInstall.

+ +

Search Module Dependency

+ +

Sometimes NetBeans misses the org.openide.util dependency, so you might have to add that one manually. To do that, right click on MyPlugin then select Properties.

+ +

Project Properties

+ +

Choose category Libraries then click Add.... Type org.openide.util then click OK. This will add the dependency to your project.xml file.

+ +

Project Properties Libraries

+ +

Add Utilities API

+ +

Press ALT + ENTER on your MyPlugin class, then choose Implement all abstract methods.

+ +

Implement Abstract Methods

+ +

One last thing, add this line to your manifest.mf file.

+ +

OpenIDE-Module-Install: org/myorg/myplugin/MyPlugin.class

+ +

OpenIDE Module Install

+ +

Now the run() method will execute after your plugin has loaded.

+ +

First Time Running

+ +

Logging

+ +

Let's make that println output to the NetBeans IDE log. First, setup the logger as an attribute of your MyPlugin class.

+ +

java +public static final Logger log = Logger.getLogger("MyPlugin"); +

+ +

Press ALT + ENTER to import java.util.logging.Logger.

+ +

Add Logger Import

+ +

Replace println with log.info("MyPlugin has loaded.");.

+ +

Log Line

+ +

Updating Your Plugin Automatically

+ +

Create a new Java file UpdateHandler.java inside your MyPlugin package.

+ +

Replace the contents of this file with UpdateHandler.java. Search the module dependency and add any missing dependencies by pressing ALT + ENTER over each import statement.

+ +

Add these lines to your manifest.mf file.

+ +

java +OpenIDE-Module-Layer: org/myorg/myplugin/layer.xml +OpenIDE-Module-Implementation-Version: 201501010101 +

+ +

Create a new XML document in your MyPlugin package.

+ +

New XML Document

+ +

Name XML Document

+ +

java +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE filesystem PUBLIC "-//NetBeans//DTD Filesystem 1.2//EN" "http://www.netbeans.org/dtds/filesystem-1_2.dtd"> +<filesystem> + <folder name="Services"> + <folder name="AutoupdateType"> + <file name="org_myorg_myplugin_update_center.instance"> + <attr name="displayName" bundlevalue="org.myorg.myplugin.Bundle#Services/AutoupdateType/org_myorg_myplugin_update_center.instance"/> + <attr name="enabled" boolvalue="true"/> + <attr name="instanceCreate" methodvalue="org.netbeans.modules.autoupdate.updateprovider.AutoupdateCatalogFactory.createUpdateProvider"/> + <attr name="instanceOf" stringvalue="org.netbeans.spi.autoupdate.UpdateProvider"/> + <attr name="url" bundlevalue="org.myorg.myplugin.Bundle#org_myorg_myplugin_update_center"/> + </file> + </folder> + </folder> +</filesystem> +

+ +

Add this code to your MyPlugin class inside the run() method.

+ +

java +WindowManager.getDefault().invokeWhenUIReady(new Runnable () { + @Override + public void run() { + UpdateHandler.checkAndHandleUpdates(); + } +}); +

+ +

Add these lines to your Bundle.properties file:

+ +

java +Services/AutoupdateType/org_myorg_myplugin_update_center.instance=MyPlugin +UpdateHandler.NewModules=false +org_myorg_myplugin_update_center=https\://example.com/updates.xml +

+ +

Now every time NetBeans restarts and launches your plugin, it will check for updates by downloading updates.xml from example.com.

+ +

Your updates.xml file tells NetBeans where to get the new NBM of your plugin. +To create an NBM for publishing your plugin, right click on your MyPlugin project and select Create NBM. The NBM file is what you will publish to the NetBeans Plugin Portal.

+ +

For an example of hosting updates.xml on GitHub, look at update.xml and corrosponding Bundle.properties from the WakaTime NetBeans plugin.

diff --git a/tests/samples/markdown.md b/tests/samples/markdown.md new file mode 100644 index 0000000..b92d374 --- /dev/null +++ b/tests/samples/markdown.md @@ -0,0 +1,147 @@ +Want to add a feature or automate something in your [NetBeans IDE](https://netbeans.org/)? Follow along as we write your first plugin for NetBeans. + +Let's go beyond the simple [Toolbar Example](https://platform.netbeans.org/tutorials/nbm-google.html) and create a plugin which can auto-update itself. +This code is based on the [WakaTime plugin for NetBeans](https://github.com/wakatime/netbeans-wakatime). Our example plugin will simply print a Hello World statement and update to new versions if available... just enough to get you started. + +## Create a new Plugin Project + +Choose `File` -> `New Project` then `NetBeans Modules` -> `Module` as the project type. + +![Create Plugin Project](https://wakatime.com/static/img/blog/create-plugin-project.png) + + +Name your project + +![Name Your Project](https://wakatime.com/static/img/blog/name-your-project.png) + + +Choose a namespace or code name for your plugin + +![Namespace Your Project](https://wakatime.com/static/img/blog/namespace-your-project.png) + + +## Add a Java File + +![Create Java File](https://wakatime.com/static/img/blog/create-java-file.png) + +![Name Java File](https://wakatime.com/static/img/blog/name-java-file.png) + + +## Plugin Starting Point + +After creating the new Java Class file, make it extend [ModuleInstall](http://bits.netbeans.org/7.4/javadoc/org-openide-modules/org/openide/modules/ModuleInstall.html) and wrap it with [@OnShowing](http://bits.netbeans.org/dev/javadoc/org-openide-windows/org/openide/windows/OnShowing.html) so it only runs after the GUI has loaded. + +```java +@OnShowing +public class MyPlugin extends ModuleInstall implements Runnable { +} +``` + +Press ALT + ENTER with your cursor over `OnShowing` then select `Search Module Dependency for OnShowing` to import the Window System API into the project. This will add a new dependency to your project as well as add the necessary import statements to the top of your file. Also do this for `ModuleInstall`. + +![Search Module Dependency](https://wakatime.com/static/img/blog/search-module-dependency.png) + +Sometimes NetBeans misses the `org.openide.util` dependency, so you might have to add that one manually. To do that, right click on MyPlugin then select `Properties`. + +![Project Properties](https://wakatime.com/static/img/blog/project-properties.png) + +Choose category `Libraries` then click `Add...`. Type `org.openide.util` then click `OK`. This will add the dependency to your `project.xml` file. + +![Project Properties Libraries](https://wakatime.com/static/img/blog/project-properties-libraries.png) + +![Add Utilities API](https://wakatime.com/static/img/blog/add-utilities-api.png) + +Press ALT + ENTER on your MyPlugin class, then choose `Implement all abstract methods`. + +![Implement Abstract Methods](https://wakatime.com/static/img/blog/implement-abstract-methods.png) + +One last thing, add this line to your `manifest.mf` file. + +`OpenIDE-Module-Install: org/myorg/myplugin/MyPlugin.class` + +![OpenIDE Module Install](https://wakatime.com/static/img/blog/openide-module-install.png) + +Now the `run()` method will execute after your plugin has loaded. + +![First Time Running](https://wakatime.com/static/img/blog/plugin-has-loaded.png) + + +## Logging + +Let's make that `println` output to the NetBeans IDE log. First, setup the logger as an attribute of your MyPlugin class. + +```java +public static final Logger log = Logger.getLogger("MyPlugin"); +``` + +Press ALT + ENTER to import [java.util.logging.Logger](https://encrypted.google.com/search?q=java.util.logging.Logger+site%3Ahttps%3A%2F%2Fdocs.oracle.com). + +![Add Logger Import](https://wakatime.com/static/img/blog/add-logger-import.png) + +Replace `println` with `log.info("MyPlugin has loaded.");`. + +![Log Line](https://wakatime.com/static/img/blog/log-line.png) + + +## Updating Your Plugin Automatically + +Create a new Java file `UpdateHandler.java` inside your MyPlugin package. + +Replace the contents of this file with [UpdateHandler.java](https://gist.github.com/alanhamlett/2a57ffb51f0850272d0d). Search the module dependency and add any missing dependencies by pressing ALT + ENTER over each import statement. + +Add these lines to your `manifest.mf` file. + +```java +OpenIDE-Module-Layer: org/myorg/myplugin/layer.xml +OpenIDE-Module-Implementation-Version: 201501010101 +``` + +Create a new XML document in your MyPlugin package. + +![New XML Document](https://wakatime.com/static/img/blog/new-xml-document.png) + +![Name XML Document](https://wakatime.com/static/img/blog/name-xml-document.png) + +```java + + + + + + + + + + + + + + + +``` + +Add this code to your MyPlugin class inside the `run()` method. + +```java +WindowManager.getDefault().invokeWhenUIReady(new Runnable () { + @Override + public void run() { + UpdateHandler.checkAndHandleUpdates(); + } +}); +``` + +Add these lines to your `Bundle.properties` file: + +```java +Services/AutoupdateType/org_myorg_myplugin_update_center.instance=MyPlugin +UpdateHandler.NewModules=false +org_myorg_myplugin_update_center=https\://example.com/updates.xml +``` + +Now every time NetBeans restarts and launches your plugin, it will check for updates by downloading `updates.xml` from example.com. + +Your updates.xml file tells NetBeans where to get the new NBM of your plugin. +To create an NBM for publishing your plugin, right click on your MyPlugin project and select `Create NBM`. The NBM file is what you will publish to the [NetBeans Plugin Portal](http://plugins.netbeans.org/). + +For an example of hosting `updates.xml` on GitHub, look at [update.xml](https://github.com/wakatime/netbeans-wakatime/blob/master/updates.xml) and corrosponding [Bundle.properties](https://github.com/wakatime/netbeans-wakatime/blob/master/src/org/wakatime/netbeans/plugin/Bundle.properties) from the [WakaTime NetBeans plugin](https://github.com/wakatime/netbeans-wakatime/). diff --git a/tests/samples/plain_text.txt b/tests/samples/plain_text.txt new file mode 100644 index 0000000..9ad368b --- /dev/null +++ b/tests/samples/plain_text.txt @@ -0,0 +1,103 @@ +Want to add a feature or automate something in your NetBeans IDE? Follow along as we write your first plugin for NetBeans. + +Let's go beyond the simple Toolbar Example and create a plugin which can auto-update itself. This code is based on the WakaTime plugin for NetBeans. Our example plugin will simply print a Hello World statement and update to new versions if available... just enough to get you started. + +Create a new Plugin Project + +Choose File -> New Project then NetBeans Modules -> Module as the project type. + +Create Plugin Project + +Name your project + +Name Your Project + +Choose a namespace or code name for your plugin + +Namespace Your Project + +Add a Java File + +Create Java File + +Name Java File + +Plugin Starting Point + +After creating the new Java Class file, make it extend ModuleInstall and wrap it with @OnShowing so it only runs after the GUI has loaded. + +java @OnShowing public class MyPlugin extends ModuleInstall implements Runnable { } + +Press ALT + ENTER with your cursor over OnShowing then select Search Module Dependency for OnShowing to import the Window System API into the project. This will add a new dependency to your project as well as add the necessary import statements to the top of your file. Also do this for ModuleInstall. + +Search Module Dependency + +Sometimes NetBeans misses the org.openide.util dependency, so you might have to add that one manually. To do that, right click on MyPlugin then select Properties. + +Project Properties + +Choose category Libraries then click Add.... Type org.openide.util then click OK. This will add the dependency to your project.xml file. + +Project Properties Libraries + +Add Utilities API + +Press ALT + ENTER on your MyPlugin class, then choose Implement all abstract methods. + +Implement Abstract Methods + +One last thing, add this line to your manifest.mf file. + +OpenIDE-Module-Install: org/myorg/myplugin/MyPlugin.class + +OpenIDE Module Install + +Now the run() method will execute after your plugin has loaded. + +First Time Running + +Logging + +Let's make that println output to the NetBeans IDE log. First, setup the logger as an attribute of your MyPlugin class. + +java public static final Logger log = Logger.getLogger("MyPlugin"); + +Press ALT + ENTER to import java.util.logging.Logger. + +Add Logger Import + +Replace println with log.info("MyPlugin has loaded.");. + +Log Line + +Updating Your Plugin Automatically + +Create a new Java file UpdateHandler.java inside your MyPlugin package. + +Replace the contents of this file with UpdateHandler.java. Search the module dependency and add any missing dependencies by pressing ALT + ENTER over each import statement. + +Add these lines to your manifest.mf file. + +java OpenIDE-Module-Layer: org/myorg/myplugin/layer.xml OpenIDE-Module-Implementation-Version: 201501010101 + +Create a new XML document in your MyPlugin package. + +New XML Document + +Name XML Document + +java + +Add this code to your MyPlugin class inside the run() method. + +java WindowManager.getDefault().invokeWhenUIReady(new Runnable () { @Override public void run() { UpdateHandler.checkAndHandleUpdates(); } }); + +Add these lines to your Bundle.properties file: + +java Services/AutoupdateType/org_myorg_myplugin_update_center.instance=MyPlugin UpdateHandler.NewModules=false org_myorg_myplugin_update_center=https\://example.com/updates.xml + +Now every time NetBeans restarts and launches your plugin, it will check for updates by downloading updates.xml from example.com. + +Your updates.xml file tells NetBeans where to get the new NBM of your plugin. To create an NBM for publishing your plugin, right click on your MyPlugin project and select Create NBM. The NBM file is what you will publish to the NetBeans Plugin Portal. + +For an example of hosting updates.xml on GitHub, look at update.xml and corrosponding Bundle.properties from the WakaTime NetBeans plugin. diff --git a/tests/test_readtime.py b/tests/test_readtime.py new file mode 100644 index 0000000..b74a2f2 --- /dev/null +++ b/tests/test_readtime.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- + + +import readtime + +from .utils import unittest, u, unicode + + +class BaseTestCase(unittest.TestCase): + + def test_plain_text_simple(self): + result = readtime.of_text('Some simple text') + self.assertEquals(result.seconds, 1) + self.assertEquals(result.text, u('1 min')) + self.assertEquals(u(result), u('1 min read')) + + def test_plain_text(self): + inp = open('tests/samples/plain_text.txt').read() + result = readtime.of_text(inp) + self.assertEquals(result.seconds, 127) + self.assertEquals(result.text, u('2 min')) + self.assertEquals(u(result), u('2 min read')) + + def test_markdown(self): + inp = open('tests/samples/markdown.md').read() + result = readtime.of_markdown(inp) + self.assertEquals(result.seconds, 210) + self.assertEquals(result.text, u('4 min')) + self.assertEquals(u(result), u('4 min read')) + + def test_html(self): + inp = open('tests/samples/html.html').read() + result = readtime.of_html(inp) + self.assertEquals(result.seconds, 210) + self.assertEquals(result.text, u('4 min')) + self.assertEquals(u(result), u('4 min read')) + + def test_plain_text_unicode(self): + result = readtime.of_text('Some simple text') + self.assertEquals(unicode(result), u('1 min read')) + + def test_unsupported_format(self): + with self.assertRaises(Exception) as e: + readtime.utils.read_time('Some simple text', format='foo') + self.assertEquals(str(e.exception), 'Unsupported format: foo') + + def test_invalid_format(self): + with self.assertRaises(Exception) as e: + readtime.utils.read_time('Some simple text', format=123) + self.assertEquals(str(e.exception), 'Unsupported format: 123') diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 0000000..9a5836f --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- + +import sys + +try: + # Python 2.6 + import unittest2 as unittest +except ImportError: + # Python >= 2.7 + import unittest + + +is_py2 = (sys.version_info[0] == 2) +is_py3 = (sys.version_info[0] == 3) + + +if is_py2: + def u(text): + try: + return text.decode('utf-8') + except: + try: + return unicode(text) + except: + return text + + unicode = unicode + + +elif is_py3: + def u(text): + if isinstance(text, bytes): + return text.decode('utf-8') + return str(text) + + unicode = str diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..60df7f9 --- /dev/null +++ b/tox.ini @@ -0,0 +1,7 @@ +[tox] +envlist = py26, py27, py33, py34, py35 +[testenv] +deps = + -rdev-requirements.txt + py26: unittest2 +commands = nosetests