Marcin Kasperski avatar Marcin Kasperski committed 993e0ba

version number hook

Comments (0)

Files changed (3)

 # (c) 2010, Marcin Kasperski
 
 from setuptools import setup, find_packages
+import os
+execfile(os.path.join(os.path.dirname(__file__), "src", "mekk", "rtm", "version.py"))
 
-version = '0.6.0'
 long_description = open("README.txt").read()
 classifiers = [
     "Programming Language :: Python",
     ]
 
 setup(name='mekk.rtm',
-      version=version,
+      version=VERSION,
       description="RememberTheMilk client API and command line client",
       long_description=long_description,
       classifiers=classifiers,

src/mekk/rtm/version.py

+# -*- coding: utf-8 -*-
+
+"""
+Self-updating on "hg tag" version number.
+
+How to use
+===============
+
+1) Save this file somewhere inside package sources
+   (for example src/«MODULE»/version.py)
+
+2) Refer this file from setup.py::
+
+    import os
+    execfile(os.path.join(os.path.dirname(__file__), "src", "«MODULE»", "version.py"))
+
+    setup(
+       version=VERSION,
+       # ...
+    )
+
+3) Refer «MODULE».version.VERSION inside the module code (in case version
+   number is to be used for something there)
+
+4) In your .hg/hgrc (of your development repo, where you commit and tag)
+   write::
+
+    [hooks]
+    pre-tag=python:src/«MODULE»/version.py:version_update
+
+   Note: pre-tag, not pretag! In the latter the changeset being tagged
+   is already set.
+
+5) Unless you use "1.2.3" or "something-1.2.3" or "something_1-2-3"
+   format, review regexps in the code.
+
+What it does
+=============
+
+Before actual tag is created, this hook:
+
+- parses tag name, trying to extract version number from it,
+
+- updates it's own code ("VERSION=" line in this file)
+
+- commits the change
+
+Local tags and tags placed by revision are ignored.
+
+Diagnostics
+===========
+
+To see more details about hook work, add --verbose or --debug::
+
+    hg tag --verbose sometag-1.2.3
+
+"""
+
+VERSION = "1.2.3"
+
+def version_update(repo, ui, hooktype, pats, opts, **kwargs):
+    """
+    Method used in mercurial version-update hook. Don't call directly.
+    """
+    import re
+    import mercurial.commands
+
+    # Regexps for handled version number syntaxes
+    tag_regexps = [
+        # something_1-2-3, something-1.2.3 and similar
+        re.compile(r"[^-_0-9][-_](?P<major>[0-9]+)[-_\.](?P<minor>[0-9]+)[-_\.](?P<patch>[0-9]+)$"),
+        # 1.2.3, 1-2-3, 1_2_3
+        re.compile(r"^(?P<major>[0-9]+)[-_\.](?P<minor>[0-9]+)[-_\.](?P<patch>[0-9]+)$"),
+        ]
+    # Regexp for VERSION= line
+    version_regexp = re.compile(r"^VERSION *= *")
+
+    if opts.get('local'):
+        ui.note("Version updater: ignoring local tag\n")
+        return
+    if opts.get('remove'):
+        ui.note("Version updater: ignoring tag removal\n")
+        return
+    if opts.get('rev'):
+        ui.note("Version updater: ignoring tag placed by rev\n")
+        return
+
+    if len(pats) != 1:
+        ui.warn("Version updater: unexpected arguments, pats=%s\n" % pats)
+        return True # means fail
+
+    tag_name = pats[0]
+
+    version_no = None
+    for tag_regexp in tag_regexps:
+        m = tag_regexp.search(tag_name)
+        if m:
+            version_no = "{major:>s}.{minor:>s}.{patch:>s}".format(**m.groupdict())
+            break
+    if not version_no:
+        ui.warn("Version updater: Given tag does not seem to be versioned. Please make proper tags (1.2.3, xxxx_1-2-3, xaear-aera-1.2.3 or similar\n")
+        return True # means fail
+
+    if version_no == VERSION:
+        ui.note("Version updater: version number {0:>s} is already correct\n".format(version_no))
+        return False # means OK
+
+    my_name = __file__
+    if my_name.endswith(".pyc") or my_name.endswith(".pyo"):
+        my_name = my_name[:-1]
+
+    ui.status("Version updater: Replacing old version number {0:>s} with {1:>s} in {2}\n".format(
+        VERSION, version_no, my_name))
+
+    file_lines = []
+    changes = 0
+    with open(my_name, "r") as input:
+        for line in input.readlines():
+            if version_regexp.search(line):
+                file_lines.append('VERSION = "%s"\n' % version_no)
+                changes += 1
+            else:
+                file_lines.append(line)
+    if not changes:
+        ui.warn("Version updater: Line starting with VERSION= not found in {0:>s}.\nPlease correct this file and retry\n".format(my_name))
+        return True #means fail
+    with open(my_name, "w") as output:
+        output.writelines(file_lines)
+
+    ui.note("Commiting updated version number\n")
+    mercurial.commands.commit(
+        ui, repo,
+        my_name,
+        message="Version number set to %s" % version_no)
+    return False #means ok

tests/test_helpers.txt

+# -*- coding: utf-8 -*-
+
+"""
+Tests related to the public commands
+"""
+
+from mekk.rtm.helpers.run_tag import modify_tags
+
+from mock import Mock, patch
+from mekk.rtm.rtm_connector import RtmConnector
+from mekk.rtm.rtm_client import RtmClient, List, SmartList, TaskKey, Task, Note
+import httplib2
+import nose.tools as nt
+from nose import SkipTest
+from dateutil.parser import parse as dateutil_parse
+from dateutil.tz import tzutc, tzlocal
+import datetime
+
+def tagger_generate_reply(url, headers):
+    """
+    Helper method for mocking tag testing.
+    """
+    if "&method=rtm.tasks.getList&" in url:
+        return (
+            dict(status='200'),
+            r'{"rsp":{"stat":"ok","tasks":{"rev":"coe3rd0gvy0wwk4484so0o8ck0ogw0c","list":{"id":"16097855","taskseries":[{"id":"92957843","created":"2010-11-10T11:22:44Z","modified":"2010-11-10T11:22:52Z","name":"Write some unit tests","source":"api","url":"http:\/\/en.wikipedia.org\/wiki\/Unit_testing","location_id":"","rrule":{"every":"0","$t":"FREQ=DAILY;INTERVAL=3"},"tags":{"tag":["@dom","testy"]},"participants":[],"notes":{"note":[{"id":"17032129","created":"2010-11-10T11:22:52Z","modified":"2010-11-10T11:22:52Z","title":"Helper","$t":"And mock can help to wrap backend apis\nwithout calling them"},{"id":"17032125","created":"2010-11-10T11:22:51Z","modified":"2010-11-10T11:22:51Z","title":"Runner","$t":"Use nose to run them all\nIt is simplest"}]},"task":{"id":"138767354","due":"2010-11-29T23:00:00Z","has_due_time":"1","added":"2010-11-10T11:22:44Z","completed":"","deleted":"","priority":"3","postponed":"0","estimate":"1 day 10 hours"}},{"id":"92141798","created":"2010-11-04T00:56:49Z","modified":"2010-11-04T00:56:49Z","name":"Bardzo powa\u017cne zadanie","source":"api","url":"","location_id":"","tags":{"tag":["@dom","krokodyl"]},"participants":[],"notes":[],"task":{"id":"137446193","due":"","has_due_time":"0","added":"2010-11-04T00:56:49Z","completed":"","deleted":"","priority":"N","postponed":"0","estimate":""}},{"id":"92267415","created":"2010-11-05T00:12:37Z","modified":"2010-11-05T00:12:37Z","name":"Bardzo powa\u017cne zadanie","source":"api","url":"","location_id":"","tags":[],"participants":[],"notes":[],"task":{"id":"137654081","due":"","has_due_time":"0","added":"2010-11-05T00:12:37Z","completed":"","deleted":"","priority":"N","postponed":"0","estimate":""}}]}}}}'
+                )
+    elif "&method=rtm.timelines.create&" in url:
+        return (
+            dict(status='200'),
+            r'{"rsp":{"stat":"ok","timeline":"12345"}}'
+            )
+    elif "&method=rtm.tasks.setTags&" in url:
+        return (
+            dict(status='200'),
+            r'{"rsp":{"stat":"ok","transaction":{"id":"2532874950","undoable":"1"},"list":{"id":"16010288","taskseries":{"id":"92882408","created":"2010-11-09T20:25:24Z","modified":"2010-11-09T20:25:25Z","name":"Bardzo powa\u017cne zadanie","source":"api","url":"","location_id":"","tags":{"tag":["@dom","testy"]},"participants":[],"notes":[],"task":{"id":"138637958","due":"","has_due_time":"0","added":"2010-11-09T20:25:24Z","completed":"","deleted":"","priority":"N","postponed":"0","estimate":""}}}}}'
+            )
+    else:
+        raise Exception("Unexpected url: " + url)
+
+@patch.object(httplib2.Http, 'request')
+def test_addTags(mock_req):
+    connector = RtmConnector("api-key", "api-sec", "write", "33423")
+    client  = RtmClient(connector)
+
+    mock_req.side_effect = tagger_generate_reply
+
+    modify_tags(client, filter = u'status:incomplete and inlist:Test', 
+                add_tags = ['@dom', 'testy'],
+                remove_tags = [])
+
+    # Były trzy zadania ale jedno już ma te tagi. Do tego timeline i getlist
+    nt.assert_equal(mock_req.call_count, 4)
+
+    called_urls = [x[0][0] for x in mock_req.call_args_list]
+
+    nt.assert_true('&method=rtm.tasks.getList&' in called_urls[0])
+    nt.assert_true('&method=rtm.timelines.create&' in called_urls[1])
+
+    nt.assert_equal(called_urls[2], 'http://api.rememberthemilk.com/services/rest/?api_key=api-key&auth_token=33423&format=json&list_id=16097855&method=rtm.tasks.setTags&tags=%40dom%2C+testy%2C+krokodyl&task_id=137446193&taskseries_id=92141798&timeline=12345&api_sig=27c2fa71e333c1b26f5e097982f3c367')
+    nt.assert_equal(called_urls[3], 'http://api.rememberthemilk.com/services/rest/?api_key=api-key&auth_token=33423&format=json&list_id=16097855&method=rtm.tasks.setTags&tags=%40dom%2C+testy&task_id=137654081&taskseries_id=92267415&timeline=12345&api_sig=6a584fb6432bb9452676123e95acc572')
+
+@patch.object(httplib2.Http, 'request')
+def test_removeTags(mock_req):
+    connector = RtmConnector("api-key", "api-sec", "write", "33423")
+    client  = RtmClient(connector)
+
+    mock_req.side_effect = tagger_generate_reply
+
+    modify_tags(client, filter = u'status:incomplete and inlist:Test', 
+                remove_tags = ['@dom', 'krokodyl'])
+
+    # Były trzy zadania ale jedno bez tych . Do tego timeline i getlist
+    nt.assert_equal(mock_req.call_count, 4)
+
+    called_urls = [x[0][0] for x in mock_req.call_args_list]
+
+    nt.assert_true('&method=rtm.tasks.getList&' in called_urls[0])
+    nt.assert_true('&method=rtm.timelines.create&' in called_urls[1])
+
+    nt.assert_equal(called_urls[2], 'http://api.rememberthemilk.com/services/rest/?api_key=api-key&auth_token=33423&format=json&list_id=16097855&method=rtm.tasks.setTags&tags=testy&task_id=138767354&taskseries_id=92957843&timeline=12345&api_sig=137768b0489acf13f17a353f4fcf2894')
+    nt.assert_equal(called_urls[3], 'http://api.rememberthemilk.com/services/rest/?api_key=api-key&auth_token=33423&format=json&list_id=16097855&method=rtm.tasks.setTags&tags=&task_id=137446193&taskseries_id=92141798&timeline=12345&api_sig=7f59d878f9da0562c0fd024d4e8229ff')
+
+@patch.object(httplib2.Http, 'request')
+def test_addRemoveTags(mock_req):
+    connector = RtmConnector("api-key", "api-sec", "write", "33423")
+    client  = RtmClient(connector)
+
+    mock_req.side_effect = tagger_generate_reply
+
+    modify_tags(client, filter = u'status:incomplete and inlist:Test',
+                add_tags = ['testy'],
+                remove_tags = ['@dom', 'krokodyl'])
+
+    nt.assert_equal(mock_req.call_count, 5)
+
+    called_urls = [x[0][0] for x in mock_req.call_args_list]
+
+    nt.assert_true('&method=rtm.tasks.getList&' in called_urls[0])
+    nt.assert_true('&method=rtm.timelines.create&' in called_urls[1])
+
+    nt.assert_equal(called_urls[2], 'http://api.rememberthemilk.com/services/rest/?api_key=api-key&auth_token=33423&format=json&list_id=16097855&method=rtm.tasks.setTags&tags=testy&task_id=138767354&taskseries_id=92957843&timeline=12345&api_sig=137768b0489acf13f17a353f4fcf2894')
+    nt.assert_equal(called_urls[3], 'http://api.rememberthemilk.com/services/rest/?api_key=api-key&auth_token=33423&format=json&list_id=16097855&method=rtm.tasks.setTags&tags=testy&task_id=137446193&taskseries_id=92141798&timeline=12345&api_sig=36884dadc39306b7a7e07a2b5bfe3f3c')
+    nt.assert_equal(called_urls[4], 'http://api.rememberthemilk.com/services/rest/?api_key=api-key&auth_token=33423&format=json&list_id=16097855&method=rtm.tasks.setTags&tags=testy&task_id=137654081&taskseries_id=92267415&timeline=12345&api_sig=778dc222b9f9badec7b4b68e643a9497')
+
+@patch.object(httplib2.Http, 'request')
+def test_addTagsDryRun(mock_req):
+    connector = RtmConnector("api-key", "api-sec", "write", "33423")
+    client  = RtmClient(connector)
+
+    mock_req.side_effect = tagger_generate_reply
+
+    modify_tags(client, filter = u'status:incomplete and inlist:Test',
+                add_tags = ['testy'],
+                remove_tags = ['@dom', 'krokodyl'],
+                dry_run = True)
+
+    nt.assert_equal(mock_req.call_count, 1)
+
+    called_urls = [x[0][0] for x in mock_req.call_args_list]
+
+    nt.assert_true('&method=rtm.tasks.getList&' in called_urls[0])
+
+def tagger_alt_generate_reply(url, headers):
+    """
+    Helper method for mocking tag testing (alternative tasks)
+    """
+    if "&method=rtm.tasks.getList&" in url:
+        return (            
+            dict(status='200'),
+            r'{"rsp":{"stat":"ok","tasks":{"rev":"pzd6wt1m928kwoggk00o404ggs8sc84","list":{"id":"15938269","taskseries":[{"id":"91504390","created":"2010-10-29T18:08:12Z","modified":"2010-10-31T15:14:37Z","name":"Wzi\u0105\u0107 gdzie\u015b Ma","source":"api","url":"","location_id":"","tags":{"tag":"@@next"},"participants":[],"notes":[],"task":{"id":"136365343","due":"","has_due_time":"0","added":"2010-10-29T18:08:12Z","completed":"","deleted":"","priority":"N","postponed":"0","estimate":""}},{"id":"91504394","created":"2010-10-29T18:08:14Z","modified":"2010-10-30T23:53:08Z","name":"Rozpisa\u0107 urodziny i imieniny rodziny","source":"api","url":"","location_id":"","tags":{"tag":"@komputer"},"participants":[],"notes":[],"task":{"id":"136365348","due":"","has_due_time":"0","added":"2010-10-29T18:08:14Z","completed":"","deleted":"","priority":"N","postponed":"0","estimate":"15 minutes"}},{"id":"91504399","created":"2010-10-29T18:08:17Z","modified":"2010-10-31T20:11:46Z","name":"mnemosyne dla dzieci","source":"api","url":"","location_id":"","tags":{"tag":["grzes","kinga","komputer"]},"participants":[],"notes":[],"task":{"id":"136365354","due":"2011-02-17T23:00:00Z","has_due_time":"1","added":"2010-10-29T18:08:17Z","completed":"","deleted":"","priority":"N","postponed":"0","estimate":""}},{"id":"91504413","created":"2010-10-29T18:08:27Z","modified":"2010-10-31T20:11:28Z","name":"Urodziny Majki 20.II. Prezent","source":"api","url":"","location_id":"","rrule":{"every":"1","$t":"FREQ=YEARLY;INTERVAL=1"},"tags":{"tag":["maja","prezenty","zakupy"]},"participants":[],"notes":[],"task":{"id":"136365370","due":"2011-02-01T23:00:00Z","has_due_time":"1","added":"2010-10-29T18:08:27Z","completed":"","deleted":"","priority":"N","postponed":"0","estimate":""}},{"id":"91504417","created":"2010-10-29T18:08:31Z","modified":"2010-10-31T16:23:04Z","name":"Wizyta Kingi u ortodonty","source":"api","url":"","location_id":"732982","tags":{"tag":["kinga","lekarz"]},"participants":[],"notes":[],"task":{"id":"136365375","due":"2010-12-03T08:40:00Z","has_due_time":"1","added":"2010-10-29T18:08:31Z","completed":"","deleted":"","priority":"N","postponed":"0","estimate":"60 minutes"}},{"id":"91504385","created":"2010-10-29T18:08:08Z","modified":"2010-11-12T23:06:55Z","name":"Podkr\u0119ci\u0107 kindze aparat o 180 stopni  (p\u00f3\u0142 obrotu, nast. kropka do g\u00f3ry) wdg strza\u0142ki raz w tygodniu","source":"api","url":"","location_id":"","rrule":{"every":"1","$t":"FREQ=WEEKLY;INTERVAL=1;BYDAY=SA"},"tags":{"tag":"kinga"},"participants":[],"notes":[],"task":[{"id":"139306587","due":"2010-11-19T23:00:00Z","has_due_time":"1","added":"2010-11-12T23:06:55Z","completed":"","deleted":"","priority":"N","postponed":"0","estimate":""},{"id":"137856490","due":"2010-11-12T23:00:00Z","has_due_time":"1","added":"2010-11-05T23:05:46Z","completed":"","deleted":"","priority":"N","postponed":"0","estimate":""}]}]}}}}')
+
+
+
+@patch.object(httplib2.Http, 'request')
+def test_addTagsToSingle(mock_req):
+    connector = RtmConnector("api-key", "api-sec", "write", "33423")
+    client  = RtmClient(connector)
+
+    mock_req.side_effect = generate_reply
+
+    modify_tags(client, filter = u'status:incomplete and inlist:Test',
+                add_tags = ['testy'],
+                remove_tags = ['@dom', 'krokodyl'])
+
+    nt.assert_equal(mock_req.call_count, 5)
+
+    called_urls = [x[0][0] for x in mock_req.call_args_list]
+
+    nt.assert_true('&method=rtm.tasks.getList&' in called_urls[0])
+    nt.assert_true('&method=rtm.timelines.create&' in called_urls[1])
+
+    nt.assert_equal(called_urls[2], 'http://api.rememberthemilk.com/services/rest/?api_key=api-key&auth_token=33423&format=json&list_id=16097855&method=rtm.tasks.setTags&tags=testy&task_id=138767354&taskseries_id=92957843&timeline=12345&api_sig=137768b0489acf13f17a353f4fcf2894')
+    nt.assert_equal(called_urls[3], 'http://api.rememberthemilk.com/services/rest/?api_key=api-key&auth_token=33423&format=json&list_id=16097855&method=rtm.tasks.setTags&tags=testy&task_id=137446193&taskseries_id=92141798&timeline=12345&api_sig=36884dadc39306b7a7e07a2b5bfe3f3c')
+    nt.assert_equal(called_urls[4], 'http://api.rememberthemilk.com/services/rest/?api_key=api-key&auth_token=33423&format=json&list_id=16097855&method=rtm.tasks.setTags&tags=testy&task_id=137654081&taskseries_id=92267415&timeline=12345&api_sig=778dc222b9f9badec7b4b68e643a9497')
+
+
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.