merge feature/find fb_develop
authorfrostbane <frostbane@programmer.net>
Tue, 22 Nov 2016 11:18:59 +0900
branchfb_develop
changeset 86 10ea34bcd570
parent 81 a2bd41e34aca (current diff)
parent 85 fba51d84afda (diff)
child 87 368dea4e91e8
merge feature/find
--- a/.gitignore	Fri Mar 04 09:51:09 2016 +0900
+++ b/.gitignore	Tue Nov 22 11:18:59 2016 +0900
@@ -1,8 +1,7 @@
+*.pyo
 *.pyc
 artemis.egg-info/
 build/
 dist/
 .idea/*
-*.pyo
-*.pyc
 pycharm-debug.egg
--- a/.hgignore	Fri Mar 04 09:51:09 2016 +0900
+++ b/.hgignore	Tue Nov 22 11:18:59 2016 +0900
@@ -1,11 +1,10 @@
 syntax:glob
+*.pyo
 *.pyc
 artemis.egg-info/
 build/
 dist/
 .idea/*
-*.pyo
-*.pyc
 pycharm-debug.egg
 syntax: glob
 artemis.iml
--- a/README	Fri Mar 04 09:51:09 2016 +0900
+++ b/README	Tue Nov 22 11:18:59 2016 +0900
@@ -128,70 +128,100 @@
 --------
 
 `iadd` ``[ID] [COMMENT]``
-    Add an issue, or a comment to an existing issue or comment. The comment is
-    recorded as a reply to the particular message. `iadd` is the only command
-    that changes the state of the repository (by adding the new issue files to
-    the list of tracked files or updating some of them), however, it does not
-    perform an actual commit unless explicitly asked to do so.
+> Add an issue, or a comment to an existing issue or comment. The comment is
+> recorded as a reply to the particular message. `iadd` is the only command
+> that changes the state of the repository (by adding the new issue files to
+> the list of tracked files or updating some of them), however, it does not
+> perform an actual commit unless explicitly asked to do so.
 
-    `-p`, `--property`
-        update a property of the issue ``ID``, e.g. ``-p state=resolved -p resolution=fixed``
+
+options:
 
-    `-a`, `--attach`
-        attach a file to the message, e.g. ``-a filename1 -a filename2``
-
-    `-n`, `--no-property-comment`
-        do not launch an editor to record a comment (useful if only changing
-        properties)
+|     |                       | description                           |
+|-----|-----------------------|---------------------------------------|
+| -a  | --attach VALUE [+]    | attach file(s)                        |
+|     |                       | (e.g., -a filename1 -a filename2)     |
+| -p  | --property VALUE [+]  | update properties                     |
+|     |                       | (e.g. -p state=fixed,                 |
+|     |                       | -p state=resolved                     |
+|     |                       | -p resolution=fixed)                  |
+| -n  | --no-property-comment | do not add a comment about changed    |
+|     |                       | properties                            |
+| -m  | --message VALUE       | use <text> as an issue subject        |
+| -i  | --index VALUE         | 0 based index of the message to show  |
+|     |                       | (default: 0)                          |
+| -c  | --commit              | perform a commit after the addition   |
 
-    `-m`, `--message`
-        use ``text`` as an issue subject
-
-    `-c`, `--commit`
-        commit the issue after the addition (all changes to the issue will be
-        committed)
+[+] marked option can be specified multiple times
 
 
 `ilist`
-    List issues.
+> List issues associated with the project
 
-    `-a`, `--all`
-        list all issues (not just the `new` ones)
+options:
 
-    `-p`, `--property`
-        list issues with specific property values, e.g.
-        ``-p state=resolved -p category=documentation``;
-        if no property value is provided (e.g. ``-p category``), lists all
-        possible values for that property (among the issues that satisfy the
-        rest of the criteria)
+|     |                      | description                            |
+|-----|----------------------|----------------------------------------|
+| -a  | --all                | list all issues                        |
+|     |                      | (by default only those with state new) |
+| -p  | --property VALUE [+] | list issues with specific field values |
+|     |                      | (e.g., -p state=fixed,                 |
+|     |                      | -p state=resolved -p category=doc);    |
+|     |                      | lists all possible values of a         |
+|     |                      | property if no = sign is provided.     |
+|     |                      | (e.g. -p category)                     |
+|     | --all-properties     | list all available properties          |
+| -o  | --order VALUE        | order of the issues; choices: "new"    |
+|     |                      | (date submitted), "latest"             |
+|     |                      | (date of the last message)             |
+|     |                      | (default: new)                         |
+| -d  | --date VALUE         | restrict to issues matching the date   |
+|     |                      | (e.g., -d ">12/28/2007)"               |
+| -f  | --filter VALUE       | restrict to pre-defined filter         |
+|     |                      | (in .issues/.filter*)                  |
 
-    `-o`, `--order`
-        order of the issues; choices: "new" (date submitted), "latest" (date of
-        the most recent message)
-
-    `-d`, `--date`
-        restrict to issues matching the given date, e.g. ``-d ">1/1/2008"``
-
-    `-f`, `--filter`
-        restrict to a predefined filter, see Filters_ below
+[+] marked option can be specified multiple times
 
 
 `ishow` ``[ID] [COMMENT]``
-    Show an issue or a comment.
+> Shows issue ID, or possibly its comment COMMENT
+
+options:
 
-    `-a`, `--all`
-        list all comments to an issue (i.e. not just a single message, and a
-        thread of subjects of its replies)
+|     |                     | description                             |
+|-----|---------------------|-----------------------------------------|
+| -a  | --all               | list all comments                       |
+| -s  | --skip VALUE        | skip lines starting with a substring    |
+|     |                     | (default: >)                            |
+| -x  | --extract VALUE [+] | extract attachments                     |
+|     |                     | (provide attachment number as argument) |
+| -i  | --index VALUE       | 0 based index of the message to show    |
+|     |                     | (default: 0)                            |
+| -o  | --output VALUE      | extract output directory                |
+|     | --mutt              | use mutt to show issue                  |
+
+[+] marked option can be specified multiple times
+
 
-    `-s`, `--skip`
-        in the output skip lines of the messages starting with the given
-        substring, defaults to ``>``
+`ifind`
+
+> Shows a list of issues matching the specified QUERY
+
+options:
 
-    `-x`, `--extract`
-        extract attachments (given their numbers)
+|     |                   | description                               |
+|-----|-------------------|-------------------------------------------|
+| -p  | --property VALUE  | issue property to match                   |
+|     |                   | [state, from, subject, date, priority,    |
+|     |                   | [resolution, ticket, etc..]               |
+|     |                   | (default: subject)                        |
+| -c  | --case-sensitive  | case sensitive search                     |
+| -r  | --regex           | use regular expressions                   |
+|     |                   | (exact option will be ignored)            |
+| -e  | --exact           | use exact comparison                      |
+|     |                   | like comparison is used if uspecified     |
 
-    `--mutt`
-        use ``mutt`` to show issue
+[+] marked option can be specified multiple times
 
 
 Filters
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README.md	Tue Nov 22 11:18:59 2016 +0900
@@ -0,0 +1,365 @@
+# Artemis #
+-----------
+
+> [Artemis](http://www.mrzv.org/software/artemis/) is a lightweight distributed issue tracking extension for [Mercurial](http://www.selenic.com/mercurial/).
+
+* [Setup](#markdown-header-setup)
+* [Example](#markdown-header-example)
+* [Commands](#markdown-header-commands)
+* [Filters](#markdown-header-filters)
+* [Format](#markdown-header-format)
+
+
+Individual issues are stored in directories in an `.issues` subdirectory (overridable in a config file). Each one is a `Maildir` and each one is assumed to have a single root message. Various properties of an issue are stored in the headers of that message.
+
+One can obtain Artemis by cloning its [repository](http://hg.mrzv.org/Artemis/):
+
+```
+    $ hg clone http://hg.mrzv.org/Artemis/
+```
+
+or downloading the entire repository as a [tarball](http://hg.mrzv.org/Artemis/archive/tip.tar.gz).
+
+
+
+## Setup ##
+-----------
+
+In the `[extensions]` section of your `~/.hgrc` add:
+
+```
+    artemis = /path/to/Artemis/artemis
+```
+
+Optionally, provide a section `[artemis]` in your `~/.hgrc` file or the repository local `.hg/hgrc` file, and specify an alternative path for the issues subdirectory (instead of the default `.issues`):
+
+```
+    [artemis]
+    issues = _issues
+```
+
+Additionally, one can specify [filters](#markdown-header-filters) and output [formats](#markdown-header-format).
+
+### Windows ###
+---------------
+
+The TortoiseHg for windows comes with a sandboxed Python, that means that thg will not be using the Python installed in the system.
+
+If you get a `No module named mailbox!` error find the `mailbox.py` under `%PYTHON_PATH%\Lib` and add it to the `%HG_PATH%\lib\library.zip`. 
+
+
+[↑ back to top](#markdown-header-artemis)
+
+
+## Example ##
+-------------
+
+**Create an issue:**
+
+```
+    $ hg iadd
+    ... enter some text in an editor ...
+```
+.
+```
+    Added new issue 907ab57e04502afd
+```
+
+**List the issues:**
+
+```
+    $ hg ilist
+    907ab57e04502afd (  0) [new]: New issue
+```
+
+**Show an issue:**
+
+```
+    $ hg ishow 907ab57e04502afd
+    ======================================================================
+    From: ...
+    Date: ...
+    Subject: New issue
+    State: new
+
+    Detailed description.
+
+    ----------------------------------------------------------------------
+```
+
+**Add a comment to the issue:**
+
+```
+    $ hg iadd 907ab57e04502afd
+    ... enter the comment text
+```
+.
+```
+    ======================================================================
+    From: ...
+    [snip]
+    Detailed description.
+
+    ----------------------------------------------------------------------
+    Comments:
+      1: [dmitriy] Some comment
+    ----------------------------------------------------------------------
+```
+
+**And a comment to the comment:**
+
+```
+    $ hg iadd 907ab57e04502afd -i 1
+    ... enter the comment text ...
+```
+.
+```
+    ======================================================================
+    From: ...
+    [snip]
+    Detailed description.
+
+    ----------------------------------------------------------------------
+    Comments:
+      1: [dmitriy] Some comment
+        2: [dmitriy] Comment on a comment
+    ----------------------------------------------------------------------
+```
+
+**Close the issue:**
+
+```
+    $ hg iadd 907ab57e04502afd -p state=resolved -p resolution=fixed -n
+```
+.
+```
+    ======================================================================
+    From: ...
+    [snip]
+    Detailed description.
+
+    ----------------------------------------------------------------------
+    Comments:
+      1: [dmitriy] Some comment
+        2: [dmitriy] Comment on a comment
+      3: [dmitriy] changed properties (state=resolved, resolution=fixed)
+    ----------------------------------------------------------------------
+```
+
+**No more new issues, and one resolved issue:**
+
+```
+    $ hg ilist
+    $ hg ilist -a
+    907ab57e04502afd (  3) [resolved=fixed]: New issue
+```
+
+The fact that issues are Maildirs, allows one to look at them in, for example, `mutt` with predictable results:
+
+```
+    mutt -Rf .issues/907ab57e04502afd
+```
+
+**Search the issues:**
+
+Search a property using regular expressions
+```
+    hg ifind -p state -r "fix(ed)?|resolve(d)?"
+    907ab57e04502afd (  3) [resolved]: New issue
+    baca6256d98fb593 (  1) [resolved]: Another issue
+```
+
+Search the message for a string
+```
+    hg ifind -mn "comment" 
+    907ab57e04502afd (  3) [resolved]: New issue
+    baca6256d98fb593 (  1) [resolved]: Another issue
+    ba564b23fcff6358 (  1) [new]: New issue
+```
+
+
+
+[↑ back to top](#markdown-header-artemis)
+
+## Commands ##
+--------------
+
+**hg iadd [OPTIONS] [ID]**
+
+> Adds a new issue, or comment to an existing issue ID or its comment COMMENT
+
+
+options:
+
+|     |                       | description                           |
+|-----|-----------------------|---------------------------------------|
+| -a  | --attach VALUE [+]    | attach file(s)                        |
+|     |                       | (e.g., -a filename1 -a filename2)     |
+| -p  | --property VALUE [+]  | update properties                     |
+|     |                       | (e.g. -p state=fixed,                 |
+|     |                       | -p state=resolved                     |
+|     |                       | -p resolution=fixed)                  |
+| -n  | --no-property-comment | do not add a comment about changed    |
+|     |                       | properties                            |
+| -m  | --message VALUE       | use <text> as an issue subject        |
+| -i  | --index VALUE         | 0 based index of the message to show  |
+|     |                       | (default: 0)                          |
+| -c  | --commit              | perform a commit after the addition   |
+
+[+] marked option can be specified multiple times
+
+
+
+
+
+**hg ilist [OPTIONS]**
+
+> List issues associated with the project
+
+options:
+
+|     |                      | description                            |
+|-----|----------------------|----------------------------------------|
+| -a  | --all                | list all issues                        |
+|     |                      | (by default only those with state new) |
+| -p  | --property VALUE [+] | list issues with specific field values |
+|     |                      | (e.g., -p state=fixed,                 |
+|     |                      | -p state=resolved -p category=doc);    |
+|     |                      | lists all possible values of a         |
+|     |                      | property if no = sign is provided.     |
+|     |                      | (e.g. -p category)                     |
+|     | --all-properties     | list all available properties          |
+| -o  | --order VALUE        | order of the issues; choices: "new"    |
+|     |                      | (date submitted), "latest"             |
+|     |                      | (date of the last message)             |
+|     |                      | (default: new)                         |
+| -d  | --date VALUE         | restrict to issues matching the date   |
+|     |                      | (e.g., -d ">12/28/2007)"               |
+| -f  | --filter VALUE       | restrict to pre-defined filter         |
+|     |                      | (in .issues/.filter*)                  |
+
+[+] marked option can be specified multiple times
+
+
+
+**hg ishow [OPTIONS] ID**
+
+> Shows issue ID, or possibly its comment COMMENT
+
+options:
+
+|     |                     | description                             |
+|-----|---------------------|-----------------------------------------|
+| -a  | --all               | list all comments                       |
+| -s  | --skip VALUE        | skip lines starting with a substring    |
+|     |                     | (default: >)                            |
+| -x  | --extract VALUE [+] | extract attachments                     |
+|     |                     | (provide attachment number as argument) |
+| -i  | --index VALUE       | 0 based index of the message to show    |
+|     |                     | (default: 0)                            |
+| -o  | --output VALUE      | extract output directory                |
+|     | --mutt              | use mutt to show issue                  |
+
+[+] marked option can be specified multiple times
+
+
+
+**hg ifind [OPTIONS] QUERY**
+
+> Shows a list of issues matching the specified QUERY
+
+options:
+
+|     |                   | description                               |
+|-----|-------------------|-------------------------------------------|
+| -p  | --property VALUE  | issue property to match                   |
+|     |                   | [state, from, subject, date, priority,    |
+|     |                   | [resolution, ticket, etc..]               |
+|     |                   | (default: subject)                        |
+| -n  | --no-property     | Do not match the property. Use with       |
+|     |                   | --message to search only the message. The |
+|     |                   | --property will be ignored.               |
+| -m  | --message         | Search the message. If no match is found  |
+|     |                   | it will then search the property for a    |
+|     |                   | match. Use a blank property to ignore the | 
+|     |                   | property search.                          | 
+| -c  | --case-sensitive  | case sensitive search                     |
+| -r  | --regex           | use regular expressions                   |
+|     |                   | (exact option will be ignored)            |
+| -e  | --exact           | use exact comparison                      |
+|     |                   | like comparison is used if uspecified     |
+
+[+] marked option can be specified multiple times
+
+
+
+
+
+
+
+[↑ back to top](#markdown-header-artemis)
+
+
+## Filters ##
+-------------
+
+Artemis scans all files of the form `.issues/.filter*`, and processes them as config files. Section names become filter names, and the individual settings become properties. For example the following:
+
+```
+    [olddoc]
+    category=documentation
+    state=resolved
+```
+
+placed in a file `.issues/.filterMyCustom` creates a filter `olddoc` which can be invoked with the `ilist` command:
+
+```
+    hg ilist -f olddoc
+```
+
+
+[↑ back to top](#markdown-header-artemis)
+
+
+
+## Format ##
+------------
+
+One can specify the output format for the `ilist` command in the global hg configuration `~/.hgrc` or the repository local configuration `.hg/hgrc`.
+
+```
+    hg config --edit
+```
+
+The default looks like:
+
+```
+    [artemis]
+    format = %(id)s (%(len)3d) [%(state)s]: %(subject)s
+```
+
+Artemis passes a dictionary with the issue properties to the format string. (Plus `id` contains the issue id, and `len` contains the number of replies.)
+
+It's possible to specify different output formats depending on the properties of the issue. The conditions are encoded in the config variable names as follows:
+
+```
+    format:state*resolved&resolution*fixed  = %(id)s (%(len)3d) [fixed]: %(Subject)s
+    format:state*resolved                   = %(id)s (%(len)3d) [%(state)s=%(resolution)s]: %(Subject)s
+```
+
+The first rule matches issues with the state property set to resolved and resolution set to fixed; it abridges the output. The secod rule matches all the resolved issues (not matched by the first rule); it annotates the issue's state with its resolution.
+
+Finally, the dictionary passed to the format string contains a subset of [ANSI codes](http://en.wikipedia.org/wiki/ANSI_escape_code), so one could color the summary lines:
+
+```
+    format:state*new = %(red)s%(bold)s%(id)s (%(len)3d) [%(state)s]: %(Subject)s%(reset)s
+```
+
+
+[↑ back to top](#markdown-header-artemis)
+
+
+
+--------------------------------------------------------------------
+
+[http://www.mrzv.org/software/artemis/](http://www.mrzv.org/software/artemis/)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artemis/find.py	Tue Nov 22 11:18:59 2016 +0900
@@ -0,0 +1,218 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+                                   ..  ┌  -:  .
+                                  ([]▄▄├]▄▄▄¿┐`,
+    .¡                      ,. D{}▓███████████░█∩^}                       =
+    ,▓¼                      .▐▓▓█████████████▓██▓▓,                     (▓
+     █▌╕.                   ─@╣████████████████████▄─                   ./▀'
+    :▓█▌Ç...             └ '└▓██████████████████████Ö'`            ..  '└`'┌
+     \ g█████▄Ü┌        C g██████████████████████████╗─  ²   ²   t▄▓██ù- `╞
+      ¡ Å╠█▌╠█▓▓▓▓▌N@▌|   ╓██████████████████████████▌  .╣▓▓▓▓▓▓▓]▐╙ÅÅ∩!
+      ` ` ╫½┌▀▀▀▀▀▀░╫▌┘    ▀▀▀▀▀██████████████████▀█▀C  :╝▀▀▀▀▀▀▀.   `:
+          L ─ⁿ'``' ─       ''' ─█████████████████▌─█=       ╞º'`'º──
+                                    ..Å╫█╛.             ~
+                                   ╔▄▄▓██▓▄¿        .
+                            -   ]:├╣██▀▀▀▀▀▒Q¿.¿{  )=
+                            <─Ω╚█░▓███'──h⌂▓██╡██▌Ü─4
+                           `┌ ▐▓█▄▓██╟≤¡yQ▄███▓█▌▐▓M
+                           ^. └▀▀▄▓██▄▒▄▄╬▄████▀ └▀╙`
+                            «-'''█▓▓█▓██▓██▓▓██└``'⌐
+                                 #╛ µ:▓▄;xφ(⌠ █
+                                 ╗µ  ~╙'`   `┌█╣
+                                 ╝Ñ. ''  '   L▀▀
+                                    ~         ,
+                                    ^
+                                    ÷        ─.
+                                    N.   ∩ts  )
+                                 ∩    ²⌠  `   ─
+                                   (@   ~▒▒  .
+                                 ` └▀/ .>▀▀  :
+                                     ⌐   ,⌐
+
+
+
+ Artemis
+ Version 0.5.1
+ Copyright (c) 2016 Frostbane Ac
+ Apache-2.0, APL-1.0, 0BSD Licensed. ?
+ www.??.com
+ ?? inspired script.
+
+"""
+
+from mercurial import hg, util, commands, cmdutil
+import sys, os, time, random, mailbox, glob, socket, ConfigParser, re
+from properties import ArtemisProperties
+from artemis import Artemis
+
+__author__ = 'frostbane'
+__date__ = '2016/05/24'
+
+
+class ArtemisFind:
+    commands = [
+        # dashed options will be resolved to underscores
+        # case-sensitive => case_sensitive
+        ('p', 'property', "subject", 'Issue property to match. '
+                                     '[state, from, subject, date, '
+                                     'priority, resolution, etc..].'
+                                     '(e.g. hg isearch -p from me)'),
+        ('n', 'no-property', None, 'Do not match the property. Use '
+                                   'with --message to search only '
+                                   'the message. The --property will '
+                                   'be ignored.'),
+        ('m', 'message', None, 'Search the message. If no match is '
+                               'found it will then search the '
+                               'property for a match. Use '
+                               '--no-property or use a blank '
+                               'property to ignore the property '
+                               'search.'
+                               '(e.g. hg isearch -mp "" "the bug")'),
+        ('c', 'case-sensitive', None, 'Case sensitive search.'),
+        ('r', 'regex', None, 'Use regular expressions. '
+                             'Exact option will be ignored.'
+                             '(e.g. hg isearch -rmn "todo *(:)?"'),
+        ('e', 'exact', None, 'Use exact comparison. '
+                             'Like comparison is used if exact is'
+                             'uspecified.'),
+    ]
+
+    usage = 'hg ifind [OPTIONS] QUERY'
+    ui = None
+    repo = None
+    opts = []
+
+    def __init__(self):
+        pass
+
+    def __is_hit(self, query, search_string):
+        opts = self.opts
+
+        exact_comp = opts["exact"] and not opts["regex"]
+        regexp_comp = opts["regex"]
+
+        if regexp_comp:
+            re_pattern = re.compile(query)
+            return re_pattern.search(search_string)
+        elif exact_comp:
+            return query == search_string
+        else:
+            return query in search_string
+
+    def __get_payload(self, mbox, key):
+        # todo move to Artemis class since this is static, and can
+        #      be possible reused in other classes
+        payload = ""
+
+        for part in mbox[key].walk():
+            ctype = part.get_content_type()
+            maintype, subtype = ctype.split('/', 1)
+
+            if maintype == 'multipart':
+                continue
+
+            if ctype == 'text/plain':
+                payload = part.get_payload().strip()
+
+        return payload
+
+    def __search_payload(self, mbox, query, case_sens):
+        keys = mbox.keys()
+        for key in keys:
+            payload = self.__get_payload(mbox, key)
+
+            if not payload:
+                continue
+
+            if not case_sens:
+                payload = payload.lower()
+
+            if self.__is_hit(query, payload):
+                return True
+
+        return False
+
+    def __search_property(self, mbox, query, case_sens):
+        query_filter = self.opts["property"]
+
+        root = Artemis.find_root_key(mbox)
+        search_string = mbox[root][query_filter]
+
+        # non existing property
+        if not search_string:
+            return False
+
+        if not case_sens:
+            search_string = search_string.lower()
+
+        if self.__is_hit(query, search_string):
+            return True
+
+        return False
+
+    def __search_issues(self, query):
+        case_sens = self.opts["case_sensitive"]
+        search_payload = self.opts["message"]
+        no_property = self.opts["no_property"]
+
+        if not case_sens:
+            query = query.lower()
+
+        hits = []
+
+        mboxes = Artemis.get_all_mboxes(self.ui, self.repo)
+        for mbox in mboxes:
+            has_hit = False
+
+            if search_payload:
+                has_hit = self.__search_payload(mbox,
+                                                query,
+                                                case_sens)
+
+            if has_hit:
+                hits.append(mbox.issue)
+                # if the message has a hit there is no need to
+                # continue searching for a hit with the property
+                continue
+
+            if no_property:
+                # ignore the property search
+                continue
+
+            if self.__search_property(mbox, query, case_sens):
+                hits.append(mbox.issue)
+
+        return hits
+
+    def __show_results(self, issues):
+        ui = self.ui
+        repo = self.repo
+
+        for issue in issues:
+            mbox = mailbox.Maildir(issue,
+                                   factory=mailbox.MaildirMessage)
+            root = Artemis.find_root_key(mbox)
+            # print mbox.items()
+            # print mbox.keys()
+            # print mbox.values()
+
+            num_replies = str(len(mbox.keys()) - 1)
+            # print mbox[root]["message-id"]
+            ui.write("%s (%s) [%s]: %s\n" %
+                     (
+                         Artemis.get_issue_id(ui, repo, issue),
+                         num_replies.rjust(3, " "),
+                         mbox[root]["state"],
+                         mbox[root]["subject"])
+                     )
+
+    def find(self, ui, repo, query, **opts):
+        """Shows a list of issues matching the specified QUERY"""
+        self.opts = opts
+        self.ui = ui
+        self.repo = repo
+
+        issues = self.__search_issues(query)
+        self.__show_results(issues)
--- a/artemis/main.py	Fri Mar 04 09:51:09 2016 +0900
+++ b/artemis/main.py	Tue Nov 22 11:18:59 2016 +0900
@@ -17,21 +17,25 @@
 from list import ArtemisList
 from add import ArtemisAdd
 from show import ArtemisShow
+from find import ArtemisFind
 
 __author__ = 'frostbane'
 __date__ = '2016/03/02'
 
 
 cmdtable = {
-    'ilist'       : (ArtemisList().list,
-                     ArtemisList.commands,
-                     _(ArtemisList.usage)),
-    'iadd'        : (ArtemisAdd().add,
-                     ArtemisAdd.commands,
-                     _(ArtemisAdd.usage)),
-    'ishow'       : (ArtemisShow().show,
-                     ArtemisShow.commands,
-                     _(ArtemisShow.usage)),
+    'ilist' : (ArtemisList().list,
+               ArtemisList.commands,
+               _(ArtemisList.usage)),
+    'iadd'  : (ArtemisAdd().add,
+               ArtemisAdd.commands,
+               _(ArtemisAdd.usage)),
+    'ishow' : (ArtemisShow().show,
+               ArtemisShow.commands,
+               _(ArtemisShow.usage)),
+    'ifind' : (ArtemisFind().find,
+              ArtemisFind.commands,
+              _(ArtemisFind.usage)),
 }
 
 if __name__ == "__main__":