From 89feea10156b98c582ab9a1f41c989f2c9348c4f Mon Sep 17 00:00:00 2001 From: Sanhe Hu Date: Sat, 23 May 2020 19:12:01 -0400 Subject: [PATCH] 0.0.2 --- README.rst | 43 +++++++++++-- afwf_fts_anything/__init__.py | 6 +- afwf_fts_anything/dataset.py | 5 ++ afwf_fts_anything/fts_setting.py | 45 ++++++++++++-- afwf_fts_anything/handlers.py | 4 ++ alfred-readme.txt | 2 +- build.sh | 15 +++++ tests/test_dataset.py | 20 +++++-- tests/test_handlers.py | 100 ++++++++++++++++++++++++++----- 9 files changed, 203 insertions(+), 37 deletions(-) create mode 100644 build.sh diff --git a/README.rst b/README.rst index 459b4b8..6ea213a 100644 --- a/README.rst +++ b/README.rst @@ -36,6 +36,7 @@ The Alfred Workflow: Full Text Search Anything :local: :depth: 1 + .. _introduction: Introduction @@ -101,22 +102,22 @@ Search Setting (content of ``movie-setting.json``): "title_field": "{title} ({genres})", // title on Alfred drop down menu "subtitle_field": "description", // subtitle on Alfred drop down menu "arg_field": "movie_id", // argument for other workflow component - "autocomplete_field": "{movie_id} - {title}", // tab auto complete behavior + "autocomplete_field": "{title}", // tab auto complete behavior "icon_field": "/Users//.alfred-fts/movie-icon.png" } -Note: ``fts.anything`` support comment in json. +Note: ``fts.anything`` support comments in json. .. _install: -Install +1. Install ``alfred-fts`` ------------------------------------------------------------------------------ Go to `Release `_, download the latest ``Full-Text-Search-Anything.alfredworkflow``. And double click to install to alfred. -Usage Guide +2. Configure Alfred Workflow Settings ------------------------------------------------------------------------------ 1. Create an ``.alfred-fts`` directory in your ``${HOME}`` dir (``/Users/``). This is where you put your dataset file and setting file. @@ -131,7 +132,7 @@ Usage Guide .. image:: https://user-images.githubusercontent.com/6800411/50622685-41710d00-0edd-11e9-9ac9-c904ed0bfd4f.png -FTS Anything Setting File +3. Configure Dataset and Setting File ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ It is a dictonary with 6 fields: @@ -170,7 +171,7 @@ It is a dictonary with 6 fields: "title_field": "{title} ({genres})", // title on Alfred drop down menu "subtitle_field": "description", // subtitle on Alfred drop down menu "arg_field": "movie_id", // argument for other workflow component - "autocomplete_field": "{movie_id} - {title}", // tab auto complete behavior + "autocomplete_field": "{title}", // tab auto complete behavior "icon_field": "/Users//.alfred-fts/movie-icon.png" } @@ -218,3 +219,33 @@ FAQ - Q: Why it still returns old data after I updated the dataset? - A: Just delete the ``${HOME}/.alfred-fts/-whoosh_index`` directory. + + +Projects based on ``alfred-fts`` +------------------------------------------------------------------------------ + +- search AWS CloudFormation Resource and Property Reference, quickly jump to Official AWS CloudFormation Resource and Property Document: https://github.com/MacHu-GWU/alfred-cloudformation-resource-property-ref +- search Terraform AWS Resource Reference, quickly jump to Official Terraform AWS Resource Document: https://github.com/MacHu-GWU/alfred-terraform-resource-property-ref + + +Developer Guide +------------------------------------------------------------------------------ + + +How to Develop this library +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +First you need to prepare the data file ``${HOME}/.alfred-tfs/movie.json`` and ``${HOME}/.alfred-tfs/movie-setting.json``. + +Then use ``tests/test_handlers.py`` to implement test cases for input argument, returned items. + + +How to Release new version of Alfred Workflow using this library +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +1. Run ``build.sh``, Workflow artifacts will be packed into ``afwf_fts_anything-project/workflow/`` +2. Create a Empty Workflows. +3. Right Click on this Workflow, Click "Open in Finder". +4. Copy all content from workflow into the folder. +5. Right Click on this Workflow, Click "Export", it will be export to ``Full Text Search Anything.alfredworkflow`` file. +6. Issue a new GitHub Release, and upload the ``Full Text Search Anything.alfredworkflow``. diff --git a/afwf_fts_anything/__init__.py b/afwf_fts_anything/__init__.py index e97cf14..75539f4 100644 --- a/afwf_fts_anything/__init__.py +++ b/afwf_fts_anything/__init__.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- """ -Package Description. +Full text search workflow for Alfred. """ -__version__ = "0.0.1" -__short_description__ = "Package short description." +__version__ = "0.0.2" +__short_description__ = "Full text search workflow for Alfred." __license__ = "MIT" __author__ = "Sanhe Hu" __author_email__ = "husanhe@gmail.com" diff --git a/afwf_fts_anything/dataset.py b/afwf_fts_anything/dataset.py index 3c407f9..e02fb7e 100644 --- a/afwf_fts_anything/dataset.py +++ b/afwf_fts_anything/dataset.py @@ -1,5 +1,10 @@ # -*- coding: utf-8 -*- +""" +This module is an integration layer put full-text-search-settings, dataset, +whoosh schema all together. +""" + import attr import shutil from attrs_mate import AttrsClass diff --git a/afwf_fts_anything/fts_setting.py b/afwf_fts_anything/fts_setting.py index b4350aa..2e64293 100644 --- a/afwf_fts_anything/fts_setting.py +++ b/afwf_fts_anything/fts_setting.py @@ -1,5 +1,10 @@ # -*- coding: utf-8 -*- +""" +This module implements the abstraction of the dataset settings, and convert the +settings towhoosh.fields.SchemaClass. +""" + import six import attr from collections import OrderedDict @@ -12,6 +17,19 @@ @attr.s class ColumnSetting(AttrsClass): """ + Per Column Setting. + + :param type_is_store: if True, the value is only stored but not indexed for + search. Usually it can be used to dynamically construct value for argument + (the action when you press enter), or for auto complete (the action + when you press tab) + :param type_is_ngram: if True, the value is index using ngram. It matches + any character shorter than N characters. + https://whoosh.readthedocs.io/en/latest/ngrams.html. + :param type_is_phrase: if True, the value is indexed using phrase. Only + case insensitive phrase will be matched. + :param type_is_keyword: if True, the value is indexed using keyword. The + keyword has to be exactly matched. :param ngram_minsize: minimal number of character to match., default 2. :param ngram_maxsize: maximum number of character to match., default 10. :param keyword_lowercase: for keyword type field, is the match case sensitive? @@ -46,7 +64,8 @@ class Setting(AttrsClass): """ Defines how you want to index your dataset - :param title_field: which field is used as ``WorkflowItem.title``. + :param title_field: which field is used as ``WorkflowItem.title``. It displays + as the big title in alfred drop down menu. :param subtitle_field: which field is used as ``WorkflowItem.subtitle``. :param arg_field: which field is used as ``WorkflowItem.arg``. :param autocomplete_field: which field is used as ``WorkflowItem.autocomplete``. @@ -54,7 +73,6 @@ class Setting(AttrsClass): :param skip_post_init: implementation reserved attribute. :param _searchable_columns_cache: implementation reserved attribute. - """ columns = attr.ib( factory=list, converter=lambda columns: [ @@ -97,6 +115,10 @@ def keyword_columns(self): @property def searchable_columns(self): + """ + + :return: + """ if self._searchable_columns_cache is None: self._searchable_columns_cache = list() self._searchable_columns_cache.extend(self.ngram_columns) @@ -106,11 +128,19 @@ def searchable_columns(self): @property def column_names(self): + """ + + :rtype: List[str] + """ return [c_setting.name for c_setting in self.columns] def create_whoosh_schema(self): """ - Dynamically create whoosh schema. + Dynamically create whoosh.fields.SchemaClass schema object. + + It defines how you index your dataset. + + :rtype: SchemaClass """ schema_classname = "WhooshSchema" schema_classname = str(schema_classname) @@ -134,13 +164,12 @@ def create_whoosh_schema(self): field = fields.STORED() attrs[c_setting.name] = field SchemaClass = type(schema_classname, (fields.SchemaClass,), attrs) - schema = SchemaClass() + schema = SchemaClass() # type: SchemaClass return schema def convert_to_item(self, doc): """ - - By default into schedule + Convert dict data to ``WFItem`` for title, subtitle, arg, autocomplete field: @@ -149,6 +178,10 @@ def convert_to_item(self, doc): use that field. 3. if ``setting.title_field`` is a str, and not in any columns fields, it must be Python String Format Template. + + :type doc: dict + + :rtype: WFItem """ # whoosh 所返回的 doc 中并不一定所有项都有, 有的项可能没有, 我们先为这些 # 没有的项赋值 None diff --git a/afwf_fts_anything/handlers.py b/afwf_fts_anything/handlers.py index 72e582d..98cc423 100644 --- a/afwf_fts_anything/handlers.py +++ b/afwf_fts_anything/handlers.py @@ -1,5 +1,9 @@ # -*- coding: utf-8 -*- +""" +This is alfred workflow integration layer. +""" + from __future__ import unicode_literals from .dataset import DataSet from .icons import ICON_NOT_FOUND diff --git a/alfred-readme.txt b/alfred-readme.txt index b4b9d34..30c5adb 100644 --- a/alfred-readme.txt +++ b/alfred-readme.txt @@ -2,4 +2,4 @@ User manual and Document can be found at: -https://github.com/MacHu-GWU/afwf_fts_anything-project \ No newline at end of file +https://github.com/MacHu-GWU/afwf_fts_anything-project diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..ce4e13f --- /dev/null +++ b/build.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# -*- coding: utf-8 -*- + +if [ -n "${BASH_SOURCE}" ] +then + dir_here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +else + dir_here="$( cd "$(dirname "$0")" ; pwd -P )" +fi +dir_workflow="${dir_here}/workflow" +rm -r ${dir_workflow} +mkdir ${dir_workflow} +pip2.7 install Alfred-Workflow --target="${dir_workflow}" +pip2.7 install . --target="${dir_workflow}"/lib +cp ${dir_here}/main.py "${dir_workflow}"/main.py diff --git a/tests/test_dataset.py b/tests/test_dataset.py index 2c0160e..5af27d3 100644 --- a/tests/test_dataset.py +++ b/tests/test_dataset.py @@ -53,8 +53,8 @@ def setup_class(cls): "title_field": "{title} ({genres})", "subtitle_field": "description", "arg_field": "movie_id", - "autocomplete_field": "{movie_id} - {title}", - "icon_field": Path(ALFRED_FTS, "movie-icon.png").abspath, + "autocomplete_field": "{title}", + "icon_field": str(Path(ALFRED_FTS, "movie-icon.png").abspath), } movie_setting = Setting.from_dict(movie_setting_data) cls.dataset_name = dataset_name @@ -68,10 +68,18 @@ def setup_class(cls): index_dir = dataset.get_index_dir_path() if index_dir.exists(): shutil.rmtree(index_dir.abspath) - json.dump(movie_data, data_file_path.abspath, - indent=4, sort_keys=True, ensure_ascii=False, overwrite=True, verbose=False) - json.dump(movie_setting_data, setting_file_path.abspath, - indent=4, sort_keys=True, ensure_ascii=False, overwrite=True, verbose=False) + json.dump( + movie_data, + data_file_path.abspath, + indent=4, sort_keys=True, ensure_ascii=False, overwrite=True, + verbose=False + ) + json.dump( + movie_setting_data, + setting_file_path.abspath, + indent=4, sort_keys=True, ensure_ascii=False, overwrite=True, + verbose=False + ) def test_search(self): dataset = DataSet( diff --git a/tests/test_handlers.py b/tests/test_handlers.py index e339274..cb37649 100644 --- a/tests/test_handlers.py +++ b/tests/test_handlers.py @@ -1,6 +1,67 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +""" +Test handler using real data in $HOME/.alfred-fts/ + +content of ``$HOME/.alfred-fts/movie.json``: + +.. code-block:: javascript + + [ + { + "description": "Two imprisoned men bond over a number of years, finding solace and eventual redemption through acts of common decency.", + "genres": "Drama", + "movie_id": 1, + "title": "The Shawshank Redemption" + }, + { + "description": "The aging patriarch of an organized crime dynasty transfers control of his clandestine empire to his reluctant son.", + "genres": "Crime, Drama", + "movie_id": 2, + "title": "The Godfather" + }, + { + "description": "The early life and career of Vito Corleone in 1920s New York City is portrayed, while his son, Michael, expands and tightens his grip on the family crime syndicate.", + "genres": "Crime, Drama", + "movie_id": 3, + "title": "The Godfather: Part II" + } + ] + +content of ``$HOME/.alfred-fts/movie-setting.json``: + +.. code-block:: javascript + + { + "arg_field": "movie_id", + "autocomplete_field": "{movie_id} - {title}", + "columns": [ + { + "name": "movie_id", + "type_is_store": true + }, + { + "name": "title", + "ngram_maxsize": 10, + "ngram_minsize": 2, + "type_is_ngram": true + }, + { + "name": "description", + "type_is_phrase": true + }, + { + "keyword_lowercase": true, + "name": "genres", + "type_is_keyword": true + } + ], + "subtitle_field": "description", + "title_field": "{title} ({genres})" + } +""" + import pytest from workflow import Workflow3 from afwf_fts_anything.handlers import main @@ -24,40 +85,49 @@ def test_found_nothing(self): def test_found(self): wf = Workflow3() - main(wf, args=["movie", "redemption"]) + main(wf, args=["movie", "redempt"]) # match the ngram 'title' field assert len(wf._items) == 1 - for item in wf._items: - print(item.title) + assert wf._items[0].title == "The Shawshank Redemption (Drama)" + assert wf._items[0].arg == "1" + assert wf._items[0].autocomplete == "The Shawshank Redemption" wf = Workflow3() - main(wf, args=["movie", "godfather"]) + main(wf, args=["movie", "father"]) # match the ngram 'title' field assert len(wf._items) == 2 for item in wf._items: - print(item.title) + assert "The Godfather" in item.title wf = Workflow3() - main(wf, args=["movie", "empire"]) + main(wf, args=["movie", "EMPIRE"]) # match the phrase 'descrpition' field assert len(wf._items) == 1 - for item in wf._items: - print(item.title) + assert wf._items[0].title == "The Godfather (Crime, Drama)" wf = Workflow3() - main(wf, args=["movie", "family"]) + main(wf, args=["movie", "transfers", "control"]) # match the phrase 'descrpition' field assert len(wf._items) == 1 - for item in wf._items: - print(item.title) + assert wf._items[0].title == "The Godfather (Crime, Drama)" + assert wf._items[0].arg == "2" + assert wf._items[0].autocomplete == "The Godfather" + + wf = Workflow3() + main(wf, args=["movie", "empi"]) # match the phrase 'descrpition' field + assert len(wf._items) == 1 + assert wf._items[0].title == "Found Nothing" wf = Workflow3() - main(wf, args=["movie", "crime"]) + main(wf, args=["movie", "family"]) # match the phrase 'descrpition' field + assert len(wf._items) == 1 + assert wf._items[0].title == "The Godfather: Part II (Crime, Drama)" + + wf = Workflow3() + main(wf, args=["movie", "Drama", "Crime"]) # match the keyword 'genres' field assert len(wf._items) == 2 for item in wf._items: - print(item.title) + assert "The Godfather" in item.title wf = Workflow3() main(wf, args=["movie", "drama"]) assert len(wf._items) == 3 - for item in wf._items: - print(item.title) if __name__ == "__main__":