├── watsongraph ├── __init__.py ├── node.py ├── item.py ├── event_insight_lib.py ├── user.py └── conceptmodel.py ├── requirements.txt ├── setup.cfg ├── docs ├── _build │ ├── html │ │ ├── _sources │ │ │ ├── modules.rst │ │ │ ├── index.txt │ │ │ └── watsongraph.rst │ │ ├── objects.inv │ │ ├── _static │ │ │ ├── up.png │ │ │ ├── down.png │ │ │ ├── file.png │ │ │ ├── plus.png │ │ │ ├── comment.png │ │ │ ├── minus.png │ │ │ ├── ajax-loader.gif │ │ │ ├── down-pressed.png │ │ │ ├── up-pressed.png │ │ │ ├── comment-bright.png │ │ │ ├── comment-close.png │ │ │ ├── pygments.css │ │ │ ├── doctools.js │ │ │ ├── underscore.js │ │ │ ├── alabaster.css │ │ │ ├── basic.css │ │ │ ├── searchtools.js │ │ │ └── websupport.js │ │ ├── .buildinfo │ │ ├── search.html │ │ ├── py-modindex.html │ │ ├── searchindex.js │ │ └── genindex.html │ └── doctrees │ │ ├── index.doctree │ │ └── environment.pickle ├── index.rst ├── make.bat ├── Makefile └── conf.py ├── setup.py ├── .gitignore ├── LICENSE ├── CONTRIBUTING.md └── README.md /watsongraph/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | networkx 2 | requests 3 | mwviews -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md -------------------------------------------------------------------------------- /docs/_build/html/_sources/modules.rst: -------------------------------------------------------------------------------- 1 | . 2 | = 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | watsongraph 8 | -------------------------------------------------------------------------------- /docs/_build/html/objects.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResidentMario/watsongraph/HEAD/docs/_build/html/objects.inv -------------------------------------------------------------------------------- /docs/_build/html/_static/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResidentMario/watsongraph/HEAD/docs/_build/html/_static/up.png -------------------------------------------------------------------------------- /docs/_build/html/_static/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResidentMario/watsongraph/HEAD/docs/_build/html/_static/down.png -------------------------------------------------------------------------------- /docs/_build/html/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResidentMario/watsongraph/HEAD/docs/_build/html/_static/file.png -------------------------------------------------------------------------------- /docs/_build/html/_static/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResidentMario/watsongraph/HEAD/docs/_build/html/_static/plus.png -------------------------------------------------------------------------------- /docs/_build/doctrees/index.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResidentMario/watsongraph/HEAD/docs/_build/doctrees/index.doctree -------------------------------------------------------------------------------- /docs/_build/html/_static/comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResidentMario/watsongraph/HEAD/docs/_build/html/_static/comment.png -------------------------------------------------------------------------------- /docs/_build/html/_static/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResidentMario/watsongraph/HEAD/docs/_build/html/_static/minus.png -------------------------------------------------------------------------------- /docs/_build/doctrees/environment.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResidentMario/watsongraph/HEAD/docs/_build/doctrees/environment.pickle -------------------------------------------------------------------------------- /docs/_build/html/_static/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResidentMario/watsongraph/HEAD/docs/_build/html/_static/ajax-loader.gif -------------------------------------------------------------------------------- /docs/_build/html/_static/down-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResidentMario/watsongraph/HEAD/docs/_build/html/_static/down-pressed.png -------------------------------------------------------------------------------- /docs/_build/html/_static/up-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResidentMario/watsongraph/HEAD/docs/_build/html/_static/up-pressed.png -------------------------------------------------------------------------------- /docs/_build/html/_static/comment-bright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResidentMario/watsongraph/HEAD/docs/_build/html/_static/comment-bright.png -------------------------------------------------------------------------------- /docs/_build/html/_static/comment-close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ResidentMario/watsongraph/HEAD/docs/_build/html/_static/comment-close.png -------------------------------------------------------------------------------- /docs/_build/html/.buildinfo: -------------------------------------------------------------------------------- 1 | # Sphinx build info version 1 2 | # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. 3 | config: e9e58a882f3d17ea207c451e65fde2f4 4 | tags: 645f666f9bcd5a90fca523b33c5a78b7 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | setup( 3 | name = 'watsongraph', 4 | packages = ['watsongraph'], # this must be the same as the name above 5 | install_requires=['networkx', 'requests', 'mwviews'], 6 | version = '0.2.2', 7 | description = 'Concept discovery and recommendation library built on top of the IBM Watson cognitive API.', 8 | author = 'Aleksey Bilogur', 9 | author_email = 'aleksey.bilogur@gmail.com', 10 | url = 'https://github.com/ResidentMario/watsongraph', 11 | download_url = 'https://github.com/ResidentMario/watsongraph/tarball/0.2.2', 12 | keywords = ['graph', 'networks', 'ibm watson', 'ibm', 'recommendation', 'bluemix'], # arbitrary keywords 13 | classifiers = [], 14 | ) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Default 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | 6 | # Distribution / packaging 7 | .Python 8 | env/ 9 | build/ 10 | develop-eggs/ 11 | dist/ 12 | downloads/ 13 | eggs/ 14 | .eggs/ 15 | lib/ 16 | lib64/ 17 | parts/ 18 | sdist/ 19 | var/ 20 | *.egg-info/ 21 | .installed.cfg 22 | *.egg 23 | MANIFEST 24 | 25 | # PyCharm project 26 | .idea 27 | 28 | # Mac junk file 29 | .DS_Store 30 | 31 | # Installer logs 32 | pip-log.txt 33 | pip-delete-this-directory.txt 34 | 35 | # Project notebook support 36 | /.ipynb_checkpoints 37 | 38 | # Project-specific 39 | 40 | # JSON files. 41 | concept_insight_credentials.json 42 | graphistry_credentials.json 43 | token.json 44 | accounts.json 45 | events.json 46 | model.json 47 | 48 | # Test files 49 | test.py 50 | model_test.py 51 | io_test.py 52 | test.py 53 | lesmiserables.csv 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Aleksey Bilogur 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. watsongraph documentation master file, created by 2 | sphinx-quickstart on Tue Jan 19 17:56:01 2016. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to watsongraph's documentation! 7 | ======================================= 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | .. module:: conceptmodel 15 | 16 | .. autoclass:: ConceptModel 17 | :members: __init__, concepts, edges, remove, neighborhood, set_property, map_property, concepts_by_property, set_view_counts, get_view_count, concepts_by_view_count, add, merge_with, copy, augment, abridge, explode, expand, add_edges, add_edge, explode_edges, to_json, load_from_json 18 | 19 | .. module:: item 20 | 21 | .. autoclass:: Item 22 | :members: __init__, concepts, relevancies, to_json, load_from_json, save, load 23 | 24 | .. module:: user 25 | 26 | .. autoclass:: User 27 | :members: __init__, concepts, interests, interest_in, get_best_item, express_interest, express_disinterest, input_interest, input_interests, save_user, load_user, update_user_credentials, delete_user 28 | 29 | Indices and tables 30 | ================== 31 | 32 | * :ref:`genindex` 33 | * :ref:`modindex` 34 | * :ref:`search` 35 | 36 | -------------------------------------------------------------------------------- /docs/_build/html/_sources/index.txt: -------------------------------------------------------------------------------- 1 | .. watsongraph documentation master file, created by 2 | sphinx-quickstart on Tue Jan 19 17:56:01 2016. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to watsongraph's documentation! 7 | ======================================= 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | .. module:: conceptmodel 15 | 16 | .. autoclass:: ConceptModel 17 | :members: __init__, concepts, edges, remove, neighborhood, set_property, map_property, concepts_by_property, set_view_counts, get_view_count, concepts_by_view_count, add, merge_with, copy, augment, abridge, explode, expand, add_edges, add_edge, explode_edges, to_json, load_from_json 18 | 19 | .. module:: item 20 | 21 | .. autoclass:: Item 22 | :members: __init__, concepts, relevancies, to_json, load_from_json, save, load 23 | 24 | .. module:: user 25 | 26 | .. autoclass:: User 27 | :members: __init__, concepts, interests, interest_in, get_best_item, express_interest, express_disinterest, input_interest, input_interests, save_user, load_user, update_user_credentials, delete_user 28 | 29 | Indices and tables 30 | ================== 31 | 32 | * :ref:`genindex` 33 | * :ref:`modindex` 34 | * :ref:`search` 35 | 36 | -------------------------------------------------------------------------------- /docs/_build/html/_sources/watsongraph.rst: -------------------------------------------------------------------------------- 1 | watsongraph package 2 | =================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | watsongraph.conceptmodel module 8 | ------------------------------- 9 | 10 | .. automodule:: watsongraph.conceptmodel 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | watsongraph.event_insight_lib module 16 | ------------------------------------ 17 | 18 | .. automodule:: watsongraph.event_insight_lib 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | watsongraph.item module 24 | ----------------------- 25 | 26 | .. automodule:: watsongraph.item 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | watsongraph.node module 32 | ----------------------- 33 | 34 | .. automodule:: watsongraph.node 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | watsongraph.test module 40 | ----------------------- 41 | 42 | .. automodule:: watsongraph.test 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | watsongraph.user module 48 | ----------------------- 49 | 50 | .. automodule:: watsongraph.user 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | 55 | 56 | Module contents 57 | --------------- 58 | 59 | .. automodule:: watsongraph 60 | :members: 61 | :undoc-members: 62 | :show-inheritance: 63 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | The `watsongraph`` library is currently in its first stable release, so it is still in a fairly early state of 4 | development: there are quite a large number of improvements and new features which could potentially be made. At the 5 | moment I am waiting for work to finish on the [Watson Developer Cloud Python SDK](https://github.com/watson-developer-cloud/python-sdk) 6 | so that I can make a large volume of low-level architectural improvements (and add a few new features) for the next 7 | planned stable release, `0.3.0`. You can see the milestone composite issues in this repository's 8 | [issue tracker](https://github.com/ResidentMario/watsongraph/issues?q=is%3Aopen+is%3Aissue+milestone%3A0.3.0). 9 | 10 | To pull the latest build onto your development machine, [clone](https://help.github.com/articles/cloning-a-repository/) this repository 11 | (`git clone https://github.com/ResidentMario/cultural-insight.git`) and follow the instructions in [setup](#Setup) to 12 | populate your access credentials. 13 | 14 | To submit a minor fix just submit a [pull request](https://help.github.com/articles/using-pull-requests/). Be sure 15 | to explain what problem your change addresses! 16 | 17 | If you are interested in contributing new features or major enhancements, we should talk! You can submit an [issue](https://guides.github.com/features/issues/) 18 | or [pull request](https://help.github.com/articles/using-pull-requests/) summarizing the work using the "Enhancement" 19 | label. You can also [filter](https://github.com/ResidentMario/watsongraph/labels/enhancement) 20 | to enhancements to see what's already on the radar. 21 | 22 | I am very receptive to feedback and would defintely like to see this code reviewed, you can reach out to me at 23 | `aleksey@residentmar.io`. -------------------------------------------------------------------------------- /docs/_build/html/search.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Search — watsongraph 0.2.2 documentation 10 | 11 | 12 | 13 | 14 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 |
43 |
44 |
45 |
46 | 47 |

Search

48 |
49 | 50 |

51 | Please activate JavaScript to enable the search 52 | functionality. 53 |

54 |
55 |

56 | From here you can search these documents. Enter your search 57 | words into the box below and click "search". Note that the search 58 | function will automatically search for all of the words. Pages 59 | containing fewer words won't appear in the result list. 60 |

61 |
62 | 63 | 64 | 65 |
66 | 67 |
68 | 69 |
70 | 71 |
72 |
73 |
74 | 84 |
85 |
86 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /docs/_build/html/_static/pygments.css: -------------------------------------------------------------------------------- 1 | .highlight .hll { background-color: #ffffcc } 2 | .highlight { background: #eeffcc; } 3 | .highlight .c { color: #408090; font-style: italic } /* Comment */ 4 | .highlight .err { border: 1px solid #FF0000 } /* Error */ 5 | .highlight .k { color: #007020; font-weight: bold } /* Keyword */ 6 | .highlight .o { color: #666666 } /* Operator */ 7 | .highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ 8 | .highlight .cp { color: #007020 } /* Comment.Preproc */ 9 | .highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ 10 | .highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ 11 | .highlight .gd { color: #A00000 } /* Generic.Deleted */ 12 | .highlight .ge { font-style: italic } /* Generic.Emph */ 13 | .highlight .gr { color: #FF0000 } /* Generic.Error */ 14 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 15 | .highlight .gi { color: #00A000 } /* Generic.Inserted */ 16 | .highlight .go { color: #333333 } /* Generic.Output */ 17 | .highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ 18 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 19 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 20 | .highlight .gt { color: #0044DD } /* Generic.Traceback */ 21 | .highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ 22 | .highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ 23 | .highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ 24 | .highlight .kp { color: #007020 } /* Keyword.Pseudo */ 25 | .highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ 26 | .highlight .kt { color: #902000 } /* Keyword.Type */ 27 | .highlight .m { color: #208050 } /* Literal.Number */ 28 | .highlight .s { color: #4070a0 } /* Literal.String */ 29 | .highlight .na { color: #4070a0 } /* Name.Attribute */ 30 | .highlight .nb { color: #007020 } /* Name.Builtin */ 31 | .highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ 32 | .highlight .no { color: #60add5 } /* Name.Constant */ 33 | .highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ 34 | .highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ 35 | .highlight .ne { color: #007020 } /* Name.Exception */ 36 | .highlight .nf { color: #06287e } /* Name.Function */ 37 | .highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ 38 | .highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ 39 | .highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ 40 | .highlight .nv { color: #bb60d5 } /* Name.Variable */ 41 | .highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ 42 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 43 | .highlight .mb { color: #208050 } /* Literal.Number.Bin */ 44 | .highlight .mf { color: #208050 } /* Literal.Number.Float */ 45 | .highlight .mh { color: #208050 } /* Literal.Number.Hex */ 46 | .highlight .mi { color: #208050 } /* Literal.Number.Integer */ 47 | .highlight .mo { color: #208050 } /* Literal.Number.Oct */ 48 | .highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ 49 | .highlight .sc { color: #4070a0 } /* Literal.String.Char */ 50 | .highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ 51 | .highlight .s2 { color: #4070a0 } /* Literal.String.Double */ 52 | .highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ 53 | .highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ 54 | .highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ 55 | .highlight .sx { color: #c65d09 } /* Literal.String.Other */ 56 | .highlight .sr { color: #235388 } /* Literal.String.Regex */ 57 | .highlight .s1 { color: #4070a0 } /* Literal.String.Single */ 58 | .highlight .ss { color: #517918 } /* Literal.String.Symbol */ 59 | .highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ 60 | .highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ 61 | .highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ 62 | .highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ 63 | .highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /docs/_build/html/py-modindex.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Python Module Index — watsongraph 0.2.2 documentation 10 | 11 | 12 | 13 | 14 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 36 | 37 | 38 | 39 | 40 | 41 |
42 |
43 |
44 |
45 | 46 | 47 |

Python Module Index

48 | 49 |
50 | c | 51 | i | 52 | u 53 |
54 | 55 | 56 | 57 | 59 | 60 | 61 | 64 | 65 | 67 | 68 | 69 | 72 | 73 | 75 | 76 | 77 | 80 |
 
58 | c
62 | conceptmodel 63 |
 
66 | i
70 | item 71 |
 
74 | u
78 | user 79 |
81 | 82 | 83 |
84 |
85 |
86 | 109 |
110 |
111 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /watsongraph/node.py: -------------------------------------------------------------------------------- 1 | from mwviews.api import PageviewsClient 2 | import hashlib 3 | import watsongraph.event_insight_lib 4 | 5 | 6 | class Node: 7 | """ 8 | The IBM Watson Concept Insights API is based on a weighted graph of Wikipedia pages. Each vertex in the graph 9 | corresponds to an individual article, referred to as a "node" or "label". This `Node` object class abstracts these 10 | nodes. They are the basis of the `ConceptModel` object. 11 | 12 | Nodes are internal representations which are never exposed to the end user. Developers work with Nodes only 13 | through the overall `ConceptModel`. 14 | """ 15 | 16 | """ 17 | The name of the Wikipedia article associated with the node, e.g. Apple, Apple Inc., Nirvana (band), etc. 18 | "Label" is the terminology used by the IBM Watson API for this attribute; "Concept" is the terminology used 19 | instead by this library (we are building a `ConceptModel()` not a `LabelModel()`!). 20 | """ 21 | concept = "" 22 | 23 | """ 24 | A dictionary of arbitrary parameter:value tuples. `view_count` and `relevance` are two such parameters which 25 | have baked-in support, but the point of this abstraction is that the user ought to be able to extend the data saved 26 | in the ConceptModel object however they want to. 27 | """ 28 | properties = None 29 | 30 | def __init__(self, concept, **kwargs): 31 | """ 32 | :param concept: The concept that is being wrapped by the Node. In Concept Insight terminology this concept 33 | is known as the "label", since it is expected to correspond one-to-one with the exact name of a Wikipedia 34 | article in Watson's `en-20120601` graph. For example, Apple Inc. is a valid concept/label while Apple ( 35 | company) is not. 36 | 37 | :param kwargs: A list of property:value tuples to be passed to the `properties` parameter. 38 | """ 39 | self.concept = concept 40 | self.properties = kwargs 41 | if not self.properties: 42 | self.properties = dict() 43 | 44 | def __eq__(self, other): 45 | """ 46 | Two `Node` objects are equal when their `concept` attributes are the same string. 47 | """ 48 | if self and other: 49 | return self.concept == other.concept 50 | else: 51 | return False 52 | 53 | def __hash__(self): 54 | """ 55 | Two concepts have an equivalent hash if their labels are equivalent. Comparison-by-hash is overwritten this 56 | way to support `nx.compose()` as used in `conceptmodel.merge_with()`. 57 | """ 58 | return int(hashlib.md5(self.concept.encode()).hexdigest(), 16) 59 | 60 | def set_view_count(self): 61 | """ 62 | Sets the view_count parameter appropriately, using a 30-day average. 63 | """ 64 | p = PageviewsClient().article_views("en.wikipedia", [self.concept.replace(' ', '_')]) 65 | p = [p[key][self.concept.replace(' ', '_')] for key in p.keys()] 66 | p = int(sum([daily_view_count for daily_view_count in p if daily_view_count])/len(p)) 67 | # self.view_count = p 68 | self.properties['view_count'] = p 69 | print(self.properties['view_count']) 70 | 71 | def set_relevance(self, relevance): 72 | """ 73 | :param relevance: Sets the concept's relevance parameter. 74 | """ 75 | # self.relevance = relevance 76 | self.set_property('relevance', relevance) 77 | 78 | def get_relevance(self): 79 | """ 80 | :return: The concept's relevance parameter. 81 | """ 82 | return self.get_property('relevance') 83 | 84 | def set_property(self, prop, value): 85 | """ 86 | :param prop: The property to be stored. 87 | :param value: The value being stored. 88 | """ 89 | self.properties.update({prop: value}) 90 | 91 | def get_property(self, prop): 92 | """ 93 | :param prop: The property to be retrieved. 94 | """ 95 | return self.properties[prop] 96 | 97 | 98 | def conceptualize(user_input): 99 | """ 100 | Attempts to map arbitrary textual input to a valid Concept. If the method is unsuccessful no Concept is 101 | returned. See also the similar `conceptmodel.model` static method, which binds arbitrary input to an entire 102 | ConceptModel instead. 103 | 104 | :param user_input: Arbitrary input, be it a name (e.g. Apple (company) -> Apple Inc.) or a text string (e.g. 105 | "the iPhone 5C, released this Thursday..." -> iPhone). 106 | """ 107 | # Fetch the precise name of the node (article title) associated with the institution. 108 | raw_concepts = watsongraph.event_insight_lib.annotate_text(user_input) 109 | # If the correction call is successful, keep going. 110 | if 'annotations' in raw_concepts.keys() and len(raw_concepts['annotations']) != 0: 111 | matched_concept_node_label = raw_concepts['annotations'][0]['concept']['label'] 112 | return matched_concept_node_label 113 | -------------------------------------------------------------------------------- /watsongraph/item.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | from watsongraph.conceptmodel import ConceptModel 4 | from watsongraph.conceptmodel import model as model_input 5 | 6 | # Every augmentation is the ConceptModel of a new user-indicated Item of interest. I have to come up with some sort 7 | # of mathematically justified way of merging this new Item into the old model: decaying the old nodes and reinforcing 8 | # the overlap. 9 | # Idea: every iteration the existing non-overlapping nodes lose 1/10 of their current relevance. Newly added nodes 10 | # come in at high relevance (.9?). Overlapping elements gain half of the distance between their sum and 1. 11 | 12 | # TODO: compare() method for measuring the overlap between two items. 13 | 14 | 15 | class Item: 16 | """ 17 | The Item object is a generic container for the objects that the application is trying to recommend to its users. 18 | """ 19 | description = "" 20 | name = "" 21 | model = None 22 | 23 | def __init__(self, name="", description=""): 24 | """ 25 | Loads an `Item` object from its description and an associated name. 26 | 27 | :param name: The Item's name. 28 | 29 | :param description: A textual description of what the Item is about or describes. This is mined at 30 | initialization for the concepts which are associated with this Item's ConceptModel(). 31 | 32 | """ 33 | self.name = name 34 | self.description = description 35 | if len(description) > 0: 36 | self.model = model_input(description) 37 | else: 38 | self.model = ConceptModel() 39 | 40 | def nodes(self): 41 | """ 42 | :return: The nodes in the Item model. 43 | """ 44 | return self.model.nodes() 45 | 46 | def concepts(self): 47 | """ 48 | :return: The concepts in the Item model. 49 | """ 50 | return self.model.concepts() 51 | 52 | def relevancies(self): 53 | """ 54 | :return: Sorted (relevance, concept) pairs associated with the Item. 55 | """ 56 | return sorted([("{0:.3f}".format(node.properties['relevance']), node.concept) for node in self.nodes()], 57 | reverse=True) 58 | 59 | def to_json(self): 60 | """ 61 | Returns a JSON of the Item object suitable for storage. Counter-operation to `load_from_json()`. 62 | 63 | :return: A JSON serialization of the Item object which is suitable for storage. 64 | """ 65 | return { 66 | "name": self.name, 67 | "model": self.model.to_json(), 68 | "description": self.description, 69 | } 70 | 71 | def load_from_json(self, data): 72 | """ 73 | Loads an Item from its JSON serialization. Counter-operation to `to_json()`. 74 | 75 | :param data: The JSON data being loaded into an Item object. 76 | """ 77 | self.model.load_from_json(data['model']) 78 | self.description = data['description'] 79 | 80 | def save(self, filename='items.json'): 81 | """ 82 | Saves the Item to a JSON representation. 83 | 84 | :param filename: The filename for the items storage file; `items.json` is the default. 85 | """ 86 | item_schema = self.to_json() 87 | if filename not in [f for f in os.listdir('.') if os.path.isfile(f)]: 88 | new_file_schema = { 89 | "items": 90 | [item_schema] 91 | } 92 | f = open(filename, 'w') 93 | f.write(json.dumps(new_file_schema, indent=4)) 94 | f.close() 95 | else: 96 | data = json.load(open(filename)) 97 | names = [item['name'] for item in data['items']] 98 | if self.name not in names: 99 | data['items'].append(item_schema) 100 | with open(filename, 'w') as outfile: 101 | json.dump(data, outfile, indent=4) 102 | if self.name in names: 103 | user_index = 0 104 | for i in range(0, len(data['items'])): 105 | if data['items'][i]['name'] == self.name: 106 | user_index = i 107 | break 108 | data['items'][user_index] = item_schema 109 | with open(filename, 'w') as outfile: 110 | json.dump(data, outfile, indent=4) 111 | 112 | def load(self, filename="items.json"): 113 | """ 114 | Loads the Item from a JSON representation. 115 | 116 | :param filename: The filename for the items storage file; `items.json` is the default. 117 | """ 118 | if filename not in [f for f in os.listdir('.') if os.path.isfile(f)]: 119 | raise IOError("The item definitions file" + filename + " appears to be missing!") 120 | list_of_items = json.load(open(filename))['items'] 121 | for item in list_of_items: 122 | if item['name'] != self: 123 | continue 124 | else: 125 | self.load_from_json(item) 126 | -------------------------------------------------------------------------------- /docs/_build/html/searchindex.js: -------------------------------------------------------------------------------- 1 | Search.setIndex({envversion:47,filenames:["index"],objects:{"":{conceptmodel:[0,0,0,"-"],item:[0,0,0,"-"],user:[0,0,0,"-"]},"conceptmodel.ConceptModel":{"__init__":[0,1,1,""],abridge:[0,1,1,""],add:[0,1,1,""],add_edge:[0,1,1,""],add_edges:[0,1,1,""],augment:[0,1,1,""],concepts:[0,1,1,""],concepts_by_property:[0,1,1,""],concepts_by_view_count:[0,1,1,""],copy:[0,1,1,""],edges:[0,1,1,""],expand:[0,1,1,""],explode:[0,1,1,""],explode_edges:[0,1,1,""],get_view_count:[0,1,1,""],load_from_json:[0,1,1,""],map_property:[0,1,1,""],merge_with:[0,1,1,""],neighborhood:[0,1,1,""],remove:[0,1,1,""],set_property:[0,1,1,""],set_view_counts:[0,1,1,""],to_json:[0,1,1,""]},"item.Item":{"__init__":[0,1,1,""],concepts:[0,1,1,""],load:[0,1,1,""],load_from_json:[0,1,1,""],relevancies:[0,1,1,""],save:[0,1,1,""],to_json:[0,1,1,""]},"user.User":{"__init__":[0,1,1,""],concepts:[0,1,1,""],delete_user:[0,1,1,""],express_disinterest:[0,1,1,""],express_interest:[0,1,1,""],get_best_item:[0,1,1,""],input_interest:[0,1,1,""],input_interests:[0,1,1,""],interest_in:[0,1,1,""],interests:[0,1,1,""],load_user:[0,1,1,""],save_user:[0,1,1,""],update_user_credentials:[0,1,1,""]},conceptmodel:{ConceptModel:[0,2,1,""]},item:{Item:[0,2,1,""]},user:{User:[0,2,1,""]}},objnames:{"0":["py","module","Python module"],"1":["py","method","Python method"],"2":["py","class","Python class"]},objtypes:{"0":"py:module","1":"py:method","2":"py:class"},terms:{"0x00000000042ec240":[],"0x00000000043e7cc0":[],"0x00000000060a73c8":[],"0x00000000060a9a90":[],"0x00000000060b8588":[],"0x0000000006107b00":[],"0x000000000619a780":[],"0x00000000061b2f98":[],"0x00000000061c5f98":[],"0x00000000061d1dd8":[],"0x00000000061d2438":[],"0x00000000061f0630":[],"__hash__":0,"__init__":0,"abstract":0,"case":0,"class":0,"default":0,"float":0,"function":0,"int":0,"return":0,"throw":0,"true":0,"try":0,"while":0,about:0,abov:[],abridg:0,abridge_by_nod:0,access:[],account:0,act:0,adapt:[],add:0,add_edg:0,adder:0,addit:[],against:[],all:0,alon:0,alreadi:0,also:0,altern:[],analyz:[],ani:0,annot:[],annotate_text:[],anoth:0,api:0,appl:0,applic:0,arbitrari:0,around:0,articl:0,assign:0,associ:0,augment:0,augment_by_nod:0,auto:0,autoclass:[],averag:[],awfjwajfalwnfa:[],back:0,base:0,basic:0,been:0,best:0,between:0,bind:0,bluemix:[],both:0,broadest:0,cachet:0,call:0,can:0,caus:0,chang:[],check:[],chosen:0,cognit:[],compar:0,comparison:0,complet:[],compos:0,concept:0,conceptmodel:0,concepts_by_properti:0,concepts_by_view_count:0,conceptu:[],connect:0,consid:0,construct:[],contain:0,content:0,content_typ:[],convert_concept_to_dict:[],copi:0,core:0,correl:0,correspond:0,could:[],count:[],counter:0,creat:0,credenti:[],current:0,cutoff:0,data:0,data_repr:0,debug:[],deep:0,delet:0,delete_us:0,depth:0,describ:0,descript:0,determin:0,develop:[],dictionari:0,differ:0,direct:[],directli:0,discov:0,disinterest:0,distort:0,doe:0,don:0,drawn:0,each:0,edg:0,either:0,els:[],empti:0,enough:0,entir:0,equal:0,error:0,etc:[],even:0,event:0,event_insight_lib:[],everi:0,everyth:0,examin:0,exampl:0,except:0,execut:[],exist:[],expand:0,expans:0,expect:0,experiment:[],expir:[],explicitli:0,explod:0,explode_edg:0,explos:[],express:0,express_disinterest:0,express_interest:0,extend:[],extens:0,extern:0,face:0,fail:0,fals:0,fast:[],fblwkaf:[],fetch:[],field:[],file:0,filenam:0,fine:0,focu:0,focus:0,fold:0,found:0,from:0,func:0,gener:0,generatetoken:[],genindex:[],get:0,get_best_item:0,get_nod:[],get_related_concept:[],get_relation_scor:[],get_token:[],get_view_count:0,giant:[],given:0,graph:0,hash:0,have:0,here:0,higher:0,highest:0,hour:[],html:[],human:0,hypothes:0,ibm:0,implement:[],inc:0,incur:[],index:0,individu:[],inform:0,init:[],initi:0,input:0,input_interest:0,insight:[],interest:0,interest_in:0,intern:0,intersect:0,intersection_with:[],intersection_with_by_nod:[],invers:0,issu:0,item:0,item_list:0,itself:0,jan:[],json:0,just:[],know:0,label:0,label_search:[],lack:0,larg:0,least:[],less:0,level:0,librari:0,like:0,limit:0,list:0,list_of_concept:0,list_of_target_concept:0,list_of_target_label:[],load:0,load_from_dict:0,load_from_json:0,load_us:0,local:[],lonely_concept:0,made:[],make:[],manipul:0,map:0,map_properti:0,master:[],match:0,maxdepth:[],meant:[],member:[],merg:0,merge_with:0,method:0,methodolog:[],microsoft:0,might:0,mine:0,minimum:0,mixin_concept_model:0,model:0,modindex:[],modul:0,more:0,most:0,much:0,must:[],name:0,need:0,neighbor:0,neighborhood:0,network:[],networkx:[],newli:0,node:0,none:0,note:0,now:[],number:0,oawbf:[],object:0,onc:0,onli:0,oper:0,option:[],order:0,other:0,otherwis:0,out:0,output:0,overhead:[],overlap:[],overwritten:0,own:0,page:0,pair:0,param:0,paramet:0,particular:0,pass:0,password:0,perform:0,place:0,plain:[],plaintext:[],plu:0,point:0,popular:0,precis:[],prefer:0,present:0,pretti:[],previou:[],primari:[],print:[],print_concept:[],print_edg:[],probabilist:[],process:[],prop:0,properti:0,provid:0,prune:0,python:[],queri:[],quickstart:[],rang:[],rate:0,raw:[],recip:[],recommend:0,reconstruct:[],redund:[],ref:[],relat:0,relation_scor:[],relev:0,relevance_edg:0,remov:0,repeatedli:[],repres:[],represent:0,requir:[],resolv:0,result:0,retriev:0,reus:[],root:[],same:0,save:0,save_us:0,scale:0,score:[],search:0,see:0,self:0,seper:0,seri:0,serial:0,servic:0,set:0,set_properti:0,set_view_count:0,should:[],simpl:0,singl:0,sizabl:0,slow:0,slower:[],sort:0,sourc:0,source_concept:0,sphinx:[],stall:[],standalon:[],still:[],storag:0,store:0,strength:0,string:0,submethod:[],suffici:0,suitabl:0,supposedli:0,system:0,take:0,talk:[],target:0,target_concept:0,team:[],terminolog:[],test:[],text:[],textual:0,than:0,thei:0,them:0,thi:0,though:0,throughout:[],to_dict:0,to_json:0,toctre:[],token:[],token_fil:[],total:0,tue:[],tupl:0,turn:0,uniqu:0,unnecessari:[],unprocess:[],updat:0,update_user_credenti:0,upon:0,user:0,user_id:0,valid:[],validatetoken:[],valu:0,veri:0,version:0,vertic:[],view:0,view_count:0,wai:0,want:[],warn:0,watson:0,weight:[],what:0,whatev:[],when:0,which:0,whom:[],whose:0,why:[],widest:0,wikipedia:0,within:0,without:0,work:0,would:[],wrap:[],wrapper:0,you:0,your:[]},titles:["Welcome to watsongraph’s documentation!"],titleterms:{document:0,indic:0,tabl:0,watsongraph:0,welcom:0}}) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Documentation Status](https://readthedocs.org/projects/watsongraph/badge/?version=latest)](http://watsongraph.readthedocs.org/en/latest/?badge=latest) 2 | [![PyPi version](https://img.shields.io/pypi/v/watsongraph.svg)](https://pypi.python.org/pypi/watsongraph/) 3 | 4 | # watsongraph 5 | 6 | ![Watsongraph Visualization](http://i.imgur.com/M1sahoT.png "Watsongraph Visualization") 7 | 8 | `watsongraph` is a concept discovery, graphing, and processing library written in `Python 3`. The library's 9 | core facility is the `ConceptModel` object, a conceptual graph constructed out of the individual concept nodes 10 | associated with labels from the IBM Watson `wikipedia/en-20120601` Wikipedia-derived conceptual graph. This graph is 11 | queried using the [Concept Insights API](http://www.ibm.com/smarterplanet/us/en/ibmwatson/developercloud/concept-insights.html) 12 | and then reconstructed locally as a `networkx`-based weighted conceptual graph: 13 | 14 | ``` 15 | >>> from watsongraph.conceptmodel import ConceptModel 16 | >>> ibm = ConceptModel(['IBM']) 17 | >>> ibm.explode() 18 | >>> ibm.concepts() 19 | ['.NET Framework', 'ARM architecture', 'Advanced Micro Devices', ...] 20 | >>> len(ibm.concepts()) 21 | 37 22 | >>> 'Server (computing)' in ibm.concepts() 23 | True 24 | >>> ibm.augment('Server (computing)') 25 | >>> len(ibm.concepts()) 26 | 58 27 | >>> ibm.edges() 28 | [(0.89564085, 'IBM', 'Digital Equipment Corporation'), 29 | (0.8793883, 'Solaris (operating system)', 'Server (computing)'), 30 | ... 31 | ] 32 | 33 | ``` 34 | 35 | The `ConceptModel` can then be associated with any number of applications. Basic bindings are provided, in 36 | particular, for a recommendation service using library-provided `Item` and `User` classes: 37 | 38 | ``` 39 | >>> from watsongraph.user import User 40 | >>> from watsongraph.item import Item 41 | >>> Bob = User(user_id="Bob") 42 | >>> Bob.input_interests(["Data science", "Machine learning", "Big data", "Cognitive science"]) 43 | >>> meetup = Item("Meetup", "This is a description of a pretty awesome event...") 44 | >>> relay = Item("Relay", "This is a description of another pretty awesome event...") 45 | >>> Bob.interest_in(meetup) 46 | 1.633861635 47 | >>> Bob.interest_in(relay) 48 | 1.54593405 49 | # Update the "Bob" model to account for our new information on Bob's preferences. 50 | >>> Bob.express_interest(meetup) 51 | ``` 52 | 53 | ## Setup 54 | 55 | `watsongraph` is [available on PyPi](https://pypi.python.org/pypi/watsongraph/) and can be downloaded locally with `pip 56 | install watsongraph`. 57 | 58 | However, in order to use IBM Watson cognitive APIs you **must** first register an account on 59 | [IBM Bluemix](https://console.ng.bluemix.net/). If you do not 60 | have an account already you may [register](https://console.ng.bluemix.net/registration/) for a free trial account. 61 | 62 | Once you are logged in, enter the catalog, scroll down to the "IBM Watson" section, and click through to create an 63 | instance of the 64 | [Concept Insights](http://www.ibm.com/smarterplanet/us/en/ibmwatson/developercloud/concept-insights.html) service. Go 65 | back to the dashboard, click on the newly populated service, and click through to "Service Credentials" on the 66 | sidebar to get your service credentials: copy-paste this `json` output and save it locally as 67 | `concept_insight_credentials.json`. Your credentials should look like this: 68 | 69 | ``` 70 | { 71 | "credentials": { 72 | "url": "https://gateway.watsonplatform.net/concept-insights/api", 73 | "username": "........-....-....-....-............", 74 | "password": "............" 75 | } 76 | } 77 | ``` 78 | 79 | Account access is provided on a thirty-day free trial basis by default, however there is free monthly allotment 80 | (25,000 queries), more than enough for experimental purposes. 81 | 82 | ## Documentation and examples 83 | 84 | * "[Exploring the IBM Watson Concept Insights service using watsongraph](http://www.residentmar.io/2016/02/11/watsongraph-visualization.html)" 85 | is a blog post on my personal website which explores the capacities and use cases for the `watsongraph` library. If 86 | you are curious about how it works, the visualizations here are the best place to start! 87 | * The [ConceptModel Jupyter notebook](http://nbviewer.jupyter.org/github/ResidentMario/watsongraph/blob/master/watsongraph%20-%20Concept%20Modeling.ipynb) 88 | provides a detailed walkthrough of basic `ConceptModel` operations. To learn how to use this library, start here, 89 | then move on to the two notebooks below. 90 | * The [Advanced Concept Modeling Jupyter notebook](http://nbviewer.jupyter.org/github/ResidentMario/watsongraph/blob/master/watsongraph%20-%20Advanced%20Concept%20Modeling.ipynb) 91 | provides a detailed walkthrough of advanced `ConceptModel` features as well as recommendations about how to use them 92 | for modeling. 93 | * The [Recommendations Modeling Jupyter notebook](http://nbviewer.jupyter.org/github/ResidentMario/watsongraph/blob/master/watsongraph%20-%20Recommendations.ipynb) 94 | applies `watsongraph` to user recommendation modeling. 95 | * The [Sphinx documentation](http://watsongraph.readthedocs.org/en/latest/) is the reference manual for all 96 | `watsongraph` methods. 97 | * For further inspiration you can also try out IBM's own 98 | [example application](https://concept-insights-demo.mybluemix.net/) (which predates this library). 99 | 100 | ## Contributing 101 | 102 | The `watsongraph` library is currently in its first stable release, so it is still in a fairly early state of 103 | development: there are quite a large number of improvements and new features which could potentially be made. At the 104 | moment I am waiting for work to finish on the [Watson Developer Cloud Python SDK](https://github.com/watson-developer-cloud/python-sdk) 105 | so that I can make a large volume of low-level architectural improvements (and add a few new features) for the next 106 | planned stable release, `0.3.0`. You can see the milestone composite issues in this repository's 107 | [issue tracker](https://github.com/ResidentMario/watsongraph/issues?q=is%3Aopen+is%3Aissue+milestone%3A0.3.0). 108 | 109 | To pull the latest build onto your development machine, [clone](https://help.github.com/articles/cloning-a-repository/) this repository 110 | (`git clone https://github.com/ResidentMario/cultural-insight.git`) and follow the instructions in [setup](#Setup) to 111 | populate your access credentials. 112 | 113 | To submit a minor fix just submit a [pull request](https://help.github.com/articles/using-pull-requests/). Be sure 114 | to explain what problem your change addresses! 115 | 116 | If you are interested in contributing new features or major enhancements, we should talk! You can submit an [issue](https://guides.github.com/features/issues/) 117 | or [pull request](https://help.github.com/articles/using-pull-requests/) summarizing the work using the "Enhancement" 118 | label. You can also [filter](https://github.com/ResidentMario/watsongraph/labels/enhancement) 119 | to enhancements to see what's already on the radar. 120 | 121 | I am very receptive to feedback and would defintely like to see this code reviewed by others, you can reach out to me 122 | at `aleksey@residentmar.io`. 123 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | echo. coverage to run coverage check of the documentation if enabled 41 | goto end 42 | ) 43 | 44 | if "%1" == "clean" ( 45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 46 | del /q /s %BUILDDIR%\* 47 | goto end 48 | ) 49 | 50 | 51 | REM Check if sphinx-build is available and fallback to Python version if any 52 | %SPHINXBUILD% 1>NUL 2>NUL 53 | if errorlevel 9009 goto sphinx_python 54 | goto sphinx_ok 55 | 56 | :sphinx_python 57 | 58 | set SPHINXBUILD=python -m sphinx.__init__ 59 | %SPHINXBUILD% 2> nul 60 | if errorlevel 9009 ( 61 | echo. 62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 63 | echo.installed, then set the SPHINXBUILD environment variable to point 64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 65 | echo.may add the Sphinx directory to PATH. 66 | echo. 67 | echo.If you don't have Sphinx installed, grab it from 68 | echo.http://sphinx-doc.org/ 69 | exit /b 1 70 | ) 71 | 72 | :sphinx_ok 73 | 74 | 75 | if "%1" == "html" ( 76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 80 | goto end 81 | ) 82 | 83 | if "%1" == "dirhtml" ( 84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 88 | goto end 89 | ) 90 | 91 | if "%1" == "singlehtml" ( 92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 93 | if errorlevel 1 exit /b 1 94 | echo. 95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 96 | goto end 97 | ) 98 | 99 | if "%1" == "pickle" ( 100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 101 | if errorlevel 1 exit /b 1 102 | echo. 103 | echo.Build finished; now you can process the pickle files. 104 | goto end 105 | ) 106 | 107 | if "%1" == "json" ( 108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 109 | if errorlevel 1 exit /b 1 110 | echo. 111 | echo.Build finished; now you can process the JSON files. 112 | goto end 113 | ) 114 | 115 | if "%1" == "htmlhelp" ( 116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 117 | if errorlevel 1 exit /b 1 118 | echo. 119 | echo.Build finished; now you can run HTML Help Workshop with the ^ 120 | .hhp project file in %BUILDDIR%/htmlhelp. 121 | goto end 122 | ) 123 | 124 | if "%1" == "qthelp" ( 125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 129 | .qhcp project file in %BUILDDIR%/qthelp, like this: 130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\watsongraph.qhcp 131 | echo.To view the help file: 132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\watsongraph.ghc 133 | goto end 134 | ) 135 | 136 | if "%1" == "devhelp" ( 137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. 141 | goto end 142 | ) 143 | 144 | if "%1" == "epub" ( 145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 149 | goto end 150 | ) 151 | 152 | if "%1" == "latex" ( 153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 157 | goto end 158 | ) 159 | 160 | if "%1" == "latexpdf" ( 161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 162 | cd %BUILDDIR%/latex 163 | make all-pdf 164 | cd %~dp0 165 | echo. 166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdfja" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf-ja 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "text" ( 181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 182 | if errorlevel 1 exit /b 1 183 | echo. 184 | echo.Build finished. The text files are in %BUILDDIR%/text. 185 | goto end 186 | ) 187 | 188 | if "%1" == "man" ( 189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 190 | if errorlevel 1 exit /b 1 191 | echo. 192 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 193 | goto end 194 | ) 195 | 196 | if "%1" == "texinfo" ( 197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 198 | if errorlevel 1 exit /b 1 199 | echo. 200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 201 | goto end 202 | ) 203 | 204 | if "%1" == "gettext" ( 205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 206 | if errorlevel 1 exit /b 1 207 | echo. 208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 209 | goto end 210 | ) 211 | 212 | if "%1" == "changes" ( 213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 214 | if errorlevel 1 exit /b 1 215 | echo. 216 | echo.The overview file is in %BUILDDIR%/changes. 217 | goto end 218 | ) 219 | 220 | if "%1" == "linkcheck" ( 221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 222 | if errorlevel 1 exit /b 1 223 | echo. 224 | echo.Link check complete; look for any errors in the above output ^ 225 | or in %BUILDDIR%/linkcheck/output.txt. 226 | goto end 227 | ) 228 | 229 | if "%1" == "doctest" ( 230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 231 | if errorlevel 1 exit /b 1 232 | echo. 233 | echo.Testing of doctests in the sources finished, look at the ^ 234 | results in %BUILDDIR%/doctest/output.txt. 235 | goto end 236 | ) 237 | 238 | if "%1" == "coverage" ( 239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 240 | if errorlevel 1 exit /b 1 241 | echo. 242 | echo.Testing of coverage in the sources finished, look at the ^ 243 | results in %BUILDDIR%/coverage/python.txt. 244 | goto end 245 | ) 246 | 247 | if "%1" == "xml" ( 248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 249 | if errorlevel 1 exit /b 1 250 | echo. 251 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 252 | goto end 253 | ) 254 | 255 | if "%1" == "pseudoxml" ( 256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 257 | if errorlevel 1 exit /b 1 258 | echo. 259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 260 | goto end 261 | ) 262 | 263 | :end 264 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help 23 | help: 24 | @echo "Please use \`make ' where is one of" 25 | @echo " html to make standalone HTML files" 26 | @echo " dirhtml to make HTML files named index.html in directories" 27 | @echo " singlehtml to make a single large HTML file" 28 | @echo " pickle to make pickle files" 29 | @echo " json to make JSON files" 30 | @echo " htmlhelp to make HTML files and a HTML help project" 31 | @echo " qthelp to make HTML files and a qthelp project" 32 | @echo " applehelp to make an Apple Help Book" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | @echo " coverage to run coverage check of the documentation (if enabled)" 49 | 50 | .PHONY: clean 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | .PHONY: html 55 | html: 56 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 57 | @echo 58 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 59 | 60 | .PHONY: dirhtml 61 | dirhtml: 62 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 63 | @echo 64 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 65 | 66 | .PHONY: singlehtml 67 | singlehtml: 68 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 69 | @echo 70 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 71 | 72 | .PHONY: pickle 73 | pickle: 74 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 75 | @echo 76 | @echo "Build finished; now you can process the pickle files." 77 | 78 | .PHONY: json 79 | json: 80 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 81 | @echo 82 | @echo "Build finished; now you can process the JSON files." 83 | 84 | .PHONY: htmlhelp 85 | htmlhelp: 86 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 87 | @echo 88 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 89 | ".hhp project file in $(BUILDDIR)/htmlhelp." 90 | 91 | .PHONY: qthelp 92 | qthelp: 93 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 94 | @echo 95 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 96 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 97 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/watsongraph.qhcp" 98 | @echo "To view the help file:" 99 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/watsongraph.qhc" 100 | 101 | .PHONY: applehelp 102 | applehelp: 103 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 104 | @echo 105 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 106 | @echo "N.B. You won't be able to view it unless you put it in" \ 107 | "~/Library/Documentation/Help or install it in your application" \ 108 | "bundle." 109 | 110 | .PHONY: devhelp 111 | devhelp: 112 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 113 | @echo 114 | @echo "Build finished." 115 | @echo "To view the help file:" 116 | @echo "# mkdir -p $$HOME/.local/share/devhelp/watsongraph" 117 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/watsongraph" 118 | @echo "# devhelp" 119 | 120 | .PHONY: epub 121 | epub: 122 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 123 | @echo 124 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 125 | 126 | .PHONY: latex 127 | latex: 128 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 129 | @echo 130 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 131 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 132 | "(use \`make latexpdf' here to do that automatically)." 133 | 134 | .PHONY: latexpdf 135 | latexpdf: 136 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 137 | @echo "Running LaTeX files through pdflatex..." 138 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 139 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 140 | 141 | .PHONY: latexpdfja 142 | latexpdfja: 143 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 144 | @echo "Running LaTeX files through platex and dvipdfmx..." 145 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 146 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 147 | 148 | .PHONY: text 149 | text: 150 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 151 | @echo 152 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 153 | 154 | .PHONY: man 155 | man: 156 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 157 | @echo 158 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 159 | 160 | .PHONY: texinfo 161 | texinfo: 162 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 163 | @echo 164 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 165 | @echo "Run \`make' in that directory to run these through makeinfo" \ 166 | "(use \`make info' here to do that automatically)." 167 | 168 | .PHONY: info 169 | info: 170 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 171 | @echo "Running Texinfo files through makeinfo..." 172 | make -C $(BUILDDIR)/texinfo info 173 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 174 | 175 | .PHONY: gettext 176 | gettext: 177 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 178 | @echo 179 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 180 | 181 | .PHONY: changes 182 | changes: 183 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 184 | @echo 185 | @echo "The overview file is in $(BUILDDIR)/changes." 186 | 187 | .PHONY: linkcheck 188 | linkcheck: 189 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 190 | @echo 191 | @echo "Link check complete; look for any errors in the above output " \ 192 | "or in $(BUILDDIR)/linkcheck/output.txt." 193 | 194 | .PHONY: doctest 195 | doctest: 196 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 197 | @echo "Testing of doctests in the sources finished, look at the " \ 198 | "results in $(BUILDDIR)/doctest/output.txt." 199 | 200 | .PHONY: coverage 201 | coverage: 202 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 203 | @echo "Testing of coverage in the sources finished, look at the " \ 204 | "results in $(BUILDDIR)/coverage/python.txt." 205 | 206 | .PHONY: xml 207 | xml: 208 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 209 | @echo 210 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 211 | 212 | .PHONY: pseudoxml 213 | pseudoxml: 214 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 215 | @echo 216 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 217 | -------------------------------------------------------------------------------- /docs/_build/html/_static/doctools.js: -------------------------------------------------------------------------------- 1 | /* 2 | * doctools.js 3 | * ~~~~~~~~~~~ 4 | * 5 | * Sphinx JavaScript utilities for all documentation. 6 | * 7 | * :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | /** 13 | * select a different prefix for underscore 14 | */ 15 | $u = _.noConflict(); 16 | 17 | /** 18 | * make the code below compatible with browsers without 19 | * an installed firebug like debugger 20 | if (!window.console || !console.firebug) { 21 | var names = ["log", "debug", "info", "warn", "error", "assert", "dir", 22 | "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", 23 | "profile", "profileEnd"]; 24 | window.console = {}; 25 | for (var i = 0; i < names.length; ++i) 26 | window.console[names[i]] = function() {}; 27 | } 28 | */ 29 | 30 | /** 31 | * small helper function to urldecode strings 32 | */ 33 | jQuery.urldecode = function(x) { 34 | return decodeURIComponent(x).replace(/\+/g, ' '); 35 | }; 36 | 37 | /** 38 | * small helper function to urlencode strings 39 | */ 40 | jQuery.urlencode = encodeURIComponent; 41 | 42 | /** 43 | * This function returns the parsed url parameters of the 44 | * current request. Multiple values per key are supported, 45 | * it will always return arrays of strings for the value parts. 46 | */ 47 | jQuery.getQueryParameters = function(s) { 48 | if (typeof s == 'undefined') 49 | s = document.location.search; 50 | var parts = s.substr(s.indexOf('?') + 1).split('&'); 51 | var result = {}; 52 | for (var i = 0; i < parts.length; i++) { 53 | var tmp = parts[i].split('=', 2); 54 | var key = jQuery.urldecode(tmp[0]); 55 | var value = jQuery.urldecode(tmp[1]); 56 | if (key in result) 57 | result[key].push(value); 58 | else 59 | result[key] = [value]; 60 | } 61 | return result; 62 | }; 63 | 64 | /** 65 | * highlight a given string on a jquery object by wrapping it in 66 | * span elements with the given class name. 67 | */ 68 | jQuery.fn.highlightText = function(text, className) { 69 | function highlight(node) { 70 | if (node.nodeType == 3) { 71 | var val = node.nodeValue; 72 | var pos = val.toLowerCase().indexOf(text); 73 | if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) { 74 | var span = document.createElement("span"); 75 | span.className = className; 76 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 77 | node.parentNode.insertBefore(span, node.parentNode.insertBefore( 78 | document.createTextNode(val.substr(pos + text.length)), 79 | node.nextSibling)); 80 | node.nodeValue = val.substr(0, pos); 81 | } 82 | } 83 | else if (!jQuery(node).is("button, select, textarea")) { 84 | jQuery.each(node.childNodes, function() { 85 | highlight(this); 86 | }); 87 | } 88 | } 89 | return this.each(function() { 90 | highlight(this); 91 | }); 92 | }; 93 | 94 | /* 95 | * backward compatibility for jQuery.browser 96 | * This will be supported until firefox bug is fixed. 97 | */ 98 | if (!jQuery.browser) { 99 | jQuery.uaMatch = function(ua) { 100 | ua = ua.toLowerCase(); 101 | 102 | var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || 103 | /(webkit)[ \/]([\w.]+)/.exec(ua) || 104 | /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || 105 | /(msie) ([\w.]+)/.exec(ua) || 106 | ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || 107 | []; 108 | 109 | return { 110 | browser: match[ 1 ] || "", 111 | version: match[ 2 ] || "0" 112 | }; 113 | }; 114 | jQuery.browser = {}; 115 | jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; 116 | } 117 | 118 | /** 119 | * Small JavaScript module for the documentation. 120 | */ 121 | var Documentation = { 122 | 123 | init : function() { 124 | this.fixFirefoxAnchorBug(); 125 | this.highlightSearchWords(); 126 | this.initIndexTable(); 127 | }, 128 | 129 | /** 130 | * i18n support 131 | */ 132 | TRANSLATIONS : {}, 133 | PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, 134 | LOCALE : 'unknown', 135 | 136 | // gettext and ngettext don't access this so that the functions 137 | // can safely bound to a different name (_ = Documentation.gettext) 138 | gettext : function(string) { 139 | var translated = Documentation.TRANSLATIONS[string]; 140 | if (typeof translated == 'undefined') 141 | return string; 142 | return (typeof translated == 'string') ? translated : translated[0]; 143 | }, 144 | 145 | ngettext : function(singular, plural, n) { 146 | var translated = Documentation.TRANSLATIONS[singular]; 147 | if (typeof translated == 'undefined') 148 | return (n == 1) ? singular : plural; 149 | return translated[Documentation.PLURALEXPR(n)]; 150 | }, 151 | 152 | addTranslations : function(catalog) { 153 | for (var key in catalog.messages) 154 | this.TRANSLATIONS[key] = catalog.messages[key]; 155 | this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); 156 | this.LOCALE = catalog.locale; 157 | }, 158 | 159 | /** 160 | * add context elements like header anchor links 161 | */ 162 | addContextElements : function() { 163 | $('div[id] > :header:first').each(function() { 164 | $('\u00B6'). 165 | attr('href', '#' + this.id). 166 | attr('title', _('Permalink to this headline')). 167 | appendTo(this); 168 | }); 169 | $('dt[id]').each(function() { 170 | $('\u00B6'). 171 | attr('href', '#' + this.id). 172 | attr('title', _('Permalink to this definition')). 173 | appendTo(this); 174 | }); 175 | }, 176 | 177 | /** 178 | * workaround a firefox stupidity 179 | * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 180 | */ 181 | fixFirefoxAnchorBug : function() { 182 | if (document.location.hash) 183 | window.setTimeout(function() { 184 | document.location.href += ''; 185 | }, 10); 186 | }, 187 | 188 | /** 189 | * highlight the search words provided in the url in the text 190 | */ 191 | highlightSearchWords : function() { 192 | var params = $.getQueryParameters(); 193 | var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; 194 | if (terms.length) { 195 | var body = $('div.body'); 196 | if (!body.length) { 197 | body = $('body'); 198 | } 199 | window.setTimeout(function() { 200 | $.each(terms, function() { 201 | body.highlightText(this.toLowerCase(), 'highlighted'); 202 | }); 203 | }, 10); 204 | $('') 206 | .appendTo($('#searchbox')); 207 | } 208 | }, 209 | 210 | /** 211 | * init the domain index toggle buttons 212 | */ 213 | initIndexTable : function() { 214 | var togglers = $('img.toggler').click(function() { 215 | var src = $(this).attr('src'); 216 | var idnum = $(this).attr('id').substr(7); 217 | $('tr.cg-' + idnum).toggle(); 218 | if (src.substr(-9) == 'minus.png') 219 | $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); 220 | else 221 | $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); 222 | }).css('display', ''); 223 | if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { 224 | togglers.click(); 225 | } 226 | }, 227 | 228 | /** 229 | * helper function to hide the search marks again 230 | */ 231 | hideSearchWords : function() { 232 | $('#searchbox .highlight-link').fadeOut(300); 233 | $('span.highlighted').removeClass('highlighted'); 234 | }, 235 | 236 | /** 237 | * make the url absolute 238 | */ 239 | makeURL : function(relativeURL) { 240 | return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; 241 | }, 242 | 243 | /** 244 | * get the current relative url 245 | */ 246 | getCurrentURL : function() { 247 | var path = document.location.pathname; 248 | var parts = path.split(/\//); 249 | $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { 250 | if (this == '..') 251 | parts.pop(); 252 | }); 253 | var url = parts.join('/'); 254 | return path.substring(url.lastIndexOf('/') + 1, path.length - 1); 255 | } 256 | }; 257 | 258 | // quick alias for translations 259 | _ = Documentation.gettext; 260 | 261 | $(document).ready(function() { 262 | Documentation.init(); 263 | }); 264 | -------------------------------------------------------------------------------- /watsongraph/event_insight_lib.py: -------------------------------------------------------------------------------- 1 | """event-insight.py 2 | This library provides a way to access IBM Watson Bluemix services. 3 | A Python Bluemix-Watson API which would make this module redundant is currently in stalled development by the 4 | Bluemix team. For now, this file's methodology is sufficient.""" 5 | 6 | import json 7 | import os 8 | import requests 9 | from time import gmtime 10 | import urllib 11 | 12 | 13 | def _import_credentials(filename='concept_insight_credentials.json'): 14 | """ 15 | Internal method which finds the credentials file describing the token that's needed to access Watson/Bluemix 16 | services. 17 | 18 | :param filename -- The filename at which Concept Insights service credentials are stored. Defaults to 19 | `concept_insight_credentials.json`. 20 | """ 21 | if filename in [f for f in os.listdir('.') if os.path.isfile(f)]: 22 | data = json.load(open(filename))['credentials'] 23 | return data 24 | else: 25 | raise IOError( 26 | 'This API requires a Bluemix/Watson credentials token to work. Did you forget to define ' 27 | 'one? For more information refer to:\n\nhttps://github.com/ResidentMario/watsongraph#setup') 28 | 29 | 30 | def _generate_token(filename='concept_insight_credentials.json', token_file='token.json'): 31 | """ 32 | Generate the Base64 token that IBM uses for API authorization. Wraps `import_credentials()`. Itself a submethod 33 | of the `getToken()` method. See also `validateToken()`. 34 | 35 | :param filename -- The filename at which Concept Insights service credentials are stored. Defaults to 36 | `concept_insight_credentials.json`. 37 | :param token_file -- The filename at which the token (which this application saves as a file, not an object) 38 | will be stored. Defaults to `token.json`. 39 | """ 40 | credentials = _import_credentials(filename) 41 | r = requests.get( 42 | "https://gateway.watsonplatform.net/authorization/api/v2/token\?url=https://stream.watsonplatform.net" + 43 | "/concept-insights/api", 44 | auth=(credentials['username'], credentials['password'])) 45 | if r.status_code == requests.codes.ok: 46 | f = open(token_file, 'w') 47 | f.write(json.dumps({'token': r.text, 'time': gmtime()}, indent=4)) 48 | return r.text 49 | else: 50 | raise RuntimeError( 51 | 'Could not resolve a Bluemix/Watson API token using the given credentials.' + 52 | 'Are your account credentials correct?') 53 | 54 | 55 | def _validate_token(token_file='token.json'): 56 | """ 57 | The generateToken() method creates a JSON file with both a token and a time parameter. 58 | This methods checks if the token file is still valid. Tokens live for an hour, so if the token was generated 59 | less than an hour ago it should work. In that case the additional overhead of regenerating the token is not 60 | necessary! This method is a submethod of the primary-use `getToken()` method. See also `generateToken()`, above. 61 | 62 | :param token_file -- The filename at which the token (which this application saves as a file, not an object) 63 | will be stored. Defaults to `token.json`. 64 | """ 65 | if token_file in [f for f in os.listdir('.') if os.path.isfile(f)]: 66 | """ 67 | The timestamp is in UTC. The timestamp is in the format [year, month, day, hour, minute, second, ..., ..., ...] 68 | Generated via `gmtime()` in `generateToken()`. See https://docs.python.org/2/library/time.html#time.struct_time 69 | In our case it's simplest to compare the hour parameter and make sure we haven't incremented into the next 70 | hour yet. 71 | """ 72 | timestamp = json.load(open(token_file))['time'] 73 | hourstamp = timestamp[3] 74 | if hourstamp - gmtime()[3] == 0: 75 | return True 76 | else: 77 | return False 78 | else: 79 | return False 80 | 81 | 82 | def get_token(token_file='token.json'): 83 | """ 84 | This is the primary-use access method meant to be used throughout the application. Implements `validateToken()` 85 | and `generateToken()` submethods, above. If a token exists that was created within the current hour, it is still 86 | valid, reused, and returned (fast). If a token exists but has expired, or does not exist at all, one is created 87 | and returned (requires networking, slower). 88 | 89 | :param token_file -- The filename at which the token (which this application saves as a file, not an object) 90 | will be stored. Defaults to `token.json`. 91 | """ 92 | if _validate_token(): 93 | return json.load(open(token_file))['token'] 94 | else: 95 | return _generate_token() 96 | 97 | 98 | def annotate_text(text, content_type='text/plain', token_file='token.json'): 99 | """ 100 | Given the text to be analyzed and a previous generated access token this method returns the result of a Watson 101 | API call to `annotate_text`. This method returns the raw, unprocessed result of an API call; its 102 | output is meant to be processed by other methods making use of API calls made using this method. 103 | 104 | :param text -- The text to be annotated. 105 | :param content_type -- This optional parameter defaults to `text/plain`, which expects plaintext input. 106 | `text/html` is the alternative option. 107 | :param token_file -- The filename at which the token (which this application saves as a file, not an object) 108 | will be stored. Defaults to `token.json`. 109 | """ 110 | token = get_token(token_file) 111 | base_url = 'https://gateway.watsonplatform.net/concept-insights/api/v2/graphs/wikipedia/en-20120601/annotate_text' 112 | headers = {'X-Watson-Authorization-Token': token, 'Content-Type': content_type, 'Accept': 'application/json'} 113 | dat = text.encode(encoding='UTF-8', errors='ignore') 114 | r = requests.post(base_url, headers=headers, data=dat) 115 | # Catch a recurring error of unknown server-side provenance. Further details: 116 | # https://github.com/ResidentMario/watsongraph/issues/6 117 | r.raise_for_status() 118 | return json.loads(r.text) 119 | 120 | 121 | def get_related_concepts(label, level=0, limit=10, token_file='token.json'): 122 | """ 123 | Given the name of a concept within the Wikipedia concept graph, returns the result of an API call to the 124 | `label_search` Watson method. This method requires the name of the concept that is to be searched for. 125 | That name must be precise. 126 | 127 | :param label -- The Concept label for whom relations are being fetched. Note that this method is executed on a 128 | label, not a Concept--executing it on a Concept would incur unnecessary additional overhead. 129 | :param level -- The limit placed on the depth of the graph. A limit of 0 is highest, corresponding with the 130 | most popular articles; a limit of 5 is the broadest and graphs to the widest cachet of articles. This 131 | parameter is a parameter that is passed directly to the IBM Watson API call. 132 | :param limit -- a cutoff placed on the number of related concepts to be returned. This parameter is passed 133 | directly to the IBM Watson API call. 134 | :param token_file -- The filename at which the token (which this application saves as a file, not an object) 135 | will be stored. Defaults to `token.json`. 136 | """ 137 | headers = {'X-Watson-Authorization-Token': get_token(token_file), 138 | 'Content-Type': 'text/plain', 139 | 'Accept': 'application/json'} 140 | # Percent encode the URI according to Wikipedia's encoding scheme. 141 | # noinspection PyUnresolvedReferences 142 | label = urllib.parse.quote(label.replace(' ', '_'), safe='_,') 143 | base_url = 'https://gateway.watsonplatform.net/concept-insights/api/v2/graphs/wikipedia/en-20120601' 144 | base_url += '/related_concepts?concepts=["/graphs/wikipedia/en-20120601/concepts/' + label 145 | base_url += '"]&level=' + str(level) + '&limit=' + str(limit) 146 | r = requests.get(base_url, headers=headers) 147 | r.raise_for_status() 148 | return json.loads(r.text) 149 | 150 | 151 | # noinspection PyUnresolvedReferences 152 | def get_relation_scores(label, list_of_target_labels, token_file='token.json'): 153 | """ 154 | Given the name of a concept within the Wikipedia concept graph and a list of other concepts to be checked against, 155 | returns the result of an API call to the `relation_scores` Watson method. This method requires the name of 156 | the concept that is to be searched for. That name must be precise. 157 | 158 | :param label -- The Concept label for whom relations are being fetched. Note that this method is executed on a 159 | label, not a Concept--executing it on a Concept would incur unnecessary additional overhead. 160 | :param list_of_target_labels -- A list of other labels for which matching correlations will be fetched. 161 | :param token_file -- The filename at which the token (which this application saves as a file, not an object) 162 | will be stored. Defaults to `token.json`. 163 | """ 164 | headers = {'X-Watson-Authorization-Token': get_token(token_file), 165 | 'Content-Type': 'text/plain', 166 | 'Accept': 'application/json'} 167 | # Percent encode the URI according to Wikipedia's encoding scheme. 168 | s_label = urllib.parse.quote(label.replace(' ', '_'), safe='_,') 169 | list_of_target_labels = [urllib.parse.quote(lb.replace(' ', '_'), safe='_,') for lb in list_of_target_labels] 170 | base_url = 'https://gateway.watsonplatform.net/concept-insights/api/v2/graphs/wikipedia/en-20120601' 171 | base_url += '/concepts/' + s_label + '/relation_scores?concepts=[' 172 | for t_label in list_of_target_labels: 173 | base_url += '"/graphs/wikipedia/en-20120601/concepts/' + t_label + '",' 174 | base_url = base_url[:-1] + ']' 175 | r = requests.get(base_url, headers=headers) 176 | r.raise_for_status() 177 | return json.loads(r.text) 178 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # watsongraph documentation build configuration file, created by 5 | # sphinx-quickstart on Tue Jan 19 17:56:01 2016. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | import sys 17 | import os 18 | 19 | 20 | # If extensions (or modules to document with autodoc) are in another directory, 21 | # add these directories to sys.path here. If the directory is relative to the 22 | # documentation root, use os.path.abspath to make it absolute, like shown here. 23 | sys.path.insert(0, os.path.abspath('..') + "/watsongraph") 24 | 25 | # -- General configuration ------------------------------------------------ 26 | 27 | # If your documentation needs a minimal Sphinx version, state it here. 28 | #needs_sphinx = '1.0' 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = [ 34 | 'sphinx.ext.autodoc', 35 | ] 36 | 37 | # Add any paths that contain templates here, relative to this directory. 38 | templates_path = ['_templates'] 39 | 40 | # The suffix(es) of source filenames. 41 | # You can specify multiple suffix as a list of string: 42 | # source_parsers = { 43 | # '.md': CommonMarkParser, 44 | # } 45 | 46 | source_suffix = ['.rst'] 47 | # source_suffix = ['.rst', '.md'] 48 | 49 | # The encoding of source files. 50 | #source_encoding = 'utf-8-sig' 51 | 52 | # The master toctree document. 53 | master_doc = 'index' 54 | 55 | # General information about the project. 56 | project = 'watsongraph' 57 | copyright = '2016, Aleksey Bilogur' 58 | author = 'Aleksey Bilogur' 59 | 60 | # The version info for the project you're documenting, acts as replacement for 61 | # |version| and |release|, also used in various other places throughout the 62 | # built documents. 63 | # 64 | # The short X.Y version. 65 | version = '0.2.2' 66 | # The full version, including alpha/beta/rc tags. 67 | release = '0.2.2' 68 | 69 | # The language for content autogenerated by Sphinx. Refer to documentation 70 | # for a list of supported languages. 71 | # 72 | # This is also used if you do content translation via gettext catalogs. 73 | # Usually you set "language" from the command line for these cases. 74 | language = None 75 | 76 | # There are two options for replacing |today|: either, you set today to some 77 | # non-false value, then it is used: 78 | #today = '' 79 | # Else, today_fmt is used as the format for a strftime call. 80 | #today_fmt = '%B %d, %Y' 81 | 82 | # List of patterns, relative to source directory, that match files and 83 | # directories to ignore when looking for source files. 84 | exclude_patterns = ['_build'] 85 | 86 | # The reST default role (used for this markup: `text`) to use for all 87 | # documents. 88 | #default_role = None 89 | 90 | # If true, '()' will be appended to :func: etc. cross-reference text. 91 | #add_function_parentheses = True 92 | 93 | # If true, the current module name will be prepended to all description 94 | # unit titles (such as .. function::). 95 | #add_module_names = True 96 | 97 | # If true, sectionauthor and moduleauthor directives will be shown in the 98 | # output. They are ignored by default. 99 | #show_authors = False 100 | 101 | # The name of the Pygments (syntax highlighting) style to use. 102 | pygments_style = 'sphinx' 103 | 104 | # A list of ignored prefixes for module index sorting. 105 | #modindex_common_prefix = [] 106 | 107 | # If true, keep warnings as "system message" paragraphs in the built documents. 108 | #keep_warnings = False 109 | 110 | # If true, `todo` and `todoList` produce output, else they produce nothing. 111 | todo_include_todos = False 112 | 113 | 114 | # -- Options for HTML output ---------------------------------------------- 115 | 116 | # The theme to use for HTML and HTML Help pages. See the documentation for 117 | # a list of builtin themes. 118 | html_theme = 'alabaster' 119 | 120 | # Theme options are theme-specific and customize the look and feel of a theme 121 | # further. For a list of options available for each theme, see the 122 | # documentation. 123 | #html_theme_options = {} 124 | 125 | # Add any paths that contain custom themes here, relative to this directory. 126 | #html_theme_path = [] 127 | 128 | # The name for this set of Sphinx documents. If None, it defaults to 129 | # " v documentation". 130 | #html_title = None 131 | 132 | # A shorter title for the navigation bar. Default is the same as html_title. 133 | #html_short_title = None 134 | 135 | # The name of an image file (relative to this directory) to place at the top 136 | # of the sidebar. 137 | #html_logo = None 138 | 139 | # The name of an image file (within the static path) to use as favicon of the 140 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 141 | # pixels large. 142 | #html_favicon = None 143 | 144 | # Add any paths that contain custom static files (such as style sheets) here, 145 | # relative to this directory. They are copied after the builtin static files, 146 | # so a file named "default.css" will overwrite the builtin "default.css". 147 | html_static_path = ['_static'] 148 | 149 | # Add any extra paths that contain custom files (such as robots.txt or 150 | # .htaccess) here, relative to this directory. These files are copied 151 | # directly to the root of the documentation. 152 | #html_extra_path = [] 153 | 154 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 155 | # using the given strftime format. 156 | #html_last_updated_fmt = '%b %d, %Y' 157 | 158 | # If true, SmartyPants will be used to convert quotes and dashes to 159 | # typographically correct entities. 160 | #html_use_smartypants = True 161 | 162 | # Custom sidebar templates, maps document names to template names. 163 | #html_sidebars = {} 164 | 165 | # Additional templates that should be rendered to pages, maps page names to 166 | # template names. 167 | #html_additional_pages = {} 168 | 169 | # If false, no module index is generated. 170 | #html_domain_indices = True 171 | 172 | # If false, no index is generated. 173 | #html_use_index = True 174 | 175 | # If true, the index is split into individual pages for each letter. 176 | #html_split_index = False 177 | 178 | # If true, links to the reST sources are added to the pages. 179 | #html_show_sourcelink = True 180 | 181 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 182 | #html_show_sphinx = True 183 | 184 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 185 | #html_show_copyright = True 186 | 187 | # If true, an OpenSearch description file will be output, and all pages will 188 | # contain a tag referring to it. The value of this option must be the 189 | # base URL from which the finished HTML is served. 190 | #html_use_opensearch = '' 191 | 192 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 193 | #html_file_suffix = None 194 | 195 | # Language to be used for generating the HTML full-text search index. 196 | # Sphinx supports the following languages: 197 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' 198 | # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr' 199 | #html_search_language = 'en' 200 | 201 | # A dictionary with options for the search language support, empty by default. 202 | # Now only 'ja' uses this config value 203 | #html_search_options = {'type': 'default'} 204 | 205 | # The name of a javascript file (relative to the configuration directory) that 206 | # implements a search results scorer. If empty, the default will be used. 207 | #html_search_scorer = 'scorer.js' 208 | 209 | # Output file base name for HTML help builder. 210 | htmlhelp_basename = 'watsongraphdoc' 211 | 212 | # -- Options for LaTeX output --------------------------------------------- 213 | 214 | latex_elements = { 215 | # The paper size ('letterpaper' or 'a4paper'). 216 | #'papersize': 'letterpaper', 217 | 218 | # The font size ('10pt', '11pt' or '12pt'). 219 | #'pointsize': '10pt', 220 | 221 | # Additional stuff for the LaTeX preamble. 222 | #'preamble': '', 223 | 224 | # Latex figure (float) alignment 225 | #'figure_align': 'htbp', 226 | } 227 | 228 | # Grouping the document tree into LaTeX files. List of tuples 229 | # (source start file, target name, title, 230 | # author, documentclass [howto, manual, or own class]). 231 | latex_documents = [ 232 | (master_doc, 'watsongraph.tex', 'watsongraph Documentation', 233 | 'Aleksey Bilogur', 'manual'), 234 | ] 235 | 236 | # The name of an image file (relative to this directory) to place at the top of 237 | # the title page. 238 | #latex_logo = None 239 | 240 | # For "manual" documents, if this is true, then toplevel headings are parts, 241 | # not chapters. 242 | #latex_use_parts = False 243 | 244 | # If true, show page references after internal links. 245 | #latex_show_pagerefs = False 246 | 247 | # If true, show URL addresses after external links. 248 | #latex_show_urls = False 249 | 250 | # Documents to append as an appendix to all manuals. 251 | #latex_appendices = [] 252 | 253 | # If false, no module index is generated. 254 | #latex_domain_indices = True 255 | 256 | 257 | # -- Options for manual page output --------------------------------------- 258 | 259 | # One entry per manual page. List of tuples 260 | # (source start file, name, description, authors, manual section). 261 | man_pages = [ 262 | (master_doc, 'watsongraph', 'watsongraph Documentation', 263 | [author], 1) 264 | ] 265 | 266 | # If true, show URL addresses after external links. 267 | #man_show_urls = False 268 | 269 | 270 | # -- Options for Texinfo output ------------------------------------------- 271 | 272 | # Grouping the document tree into Texinfo files. List of tuples 273 | # (source start file, target name, title, author, 274 | # dir menu entry, description, category) 275 | texinfo_documents = [ 276 | (master_doc, 'watsongraph', 'watsongraph Documentation', 277 | author, 'watsongraph', 'One line description of project.', 278 | 'Miscellaneous'), 279 | ] 280 | 281 | # Documents to append as an appendix to all manuals. 282 | #texinfo_appendices = [] 283 | 284 | # If false, no module index is generated. 285 | #texinfo_domain_indices = True 286 | 287 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 288 | #texinfo_show_urls = 'footnote' 289 | 290 | # If true, do not generate a @detailmenu in the "Top" node's menu. 291 | #texinfo_no_detailmenu = False 292 | -------------------------------------------------------------------------------- /docs/_build/html/_static/underscore.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.3.1 2 | // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. 3 | // Underscore is freely distributable under the MIT license. 4 | // Portions of Underscore are inspired or borrowed from Prototype, 5 | // Oliver Steele's Functional, and John Resig's Micro-Templating. 6 | // For all details and documentation: 7 | // http://documentcloud.github.com/underscore 8 | (function(){function q(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return a==String(c);case "[object Number]":return a!=+a?c!=+c:a==0?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source== 9 | c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&q(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&q(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c, 10 | h)&&!f--)break;g=!f}}d.pop();return g}var r=this,G=r._,n={},k=Array.prototype,o=Object.prototype,i=k.slice,H=k.unshift,l=o.toString,I=o.hasOwnProperty,w=k.forEach,x=k.map,y=k.reduce,z=k.reduceRight,A=k.filter,B=k.every,C=k.some,p=k.indexOf,D=k.lastIndexOf,o=Array.isArray,J=Object.keys,s=Function.prototype.bind,b=function(a){return new m(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else r._=b;b.VERSION="1.3.1";var j=b.each= 11 | b.forEach=function(a,c,d){if(a!=null)if(w&&a.forEach===w)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e2;a== 12 | null&&(a=[]);if(y&&a.reduce===y)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(z&&a.reduceRight===z)return e&&(c=b.bind(c,e)),f?a.reduceRight(c,d):a.reduceRight(c);var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect= 13 | function(a,c,b){var e;E(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(A&&a.filter===A)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(B&&a.every===B)return a.every(c,b);j(a,function(a,g,h){if(!(e= 14 | e&&c.call(b,a,g,h)))return n});return e};var E=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(C&&a.some===C)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return n});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return p&&a.indexOf===p?a.indexOf(c)!=-1:b=E(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck= 15 | function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;bd?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a, 17 | c,d){d||(d=b.identity);for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1));return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e=0;d--)b=[a[d].apply(this,b)];return b[0]}}; 24 | b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=J||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.defaults=function(a){j(i.call(arguments, 25 | 1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return q(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=o||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)}; 26 | b.isArguments=function(a){return l.call(a)=="[object Arguments]"};if(!b.isArguments(arguments))b.isArguments=function(a){return!(!a||!b.has(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"}; 27 | b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,b){return I.call(a,b)};b.noConflict=function(){r._=G;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.mixin=function(a){j(b.functions(a), 28 | function(c){K(c,b[c]=a[c])})};var L=0;b.uniqueId=function(a){var b=L++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var t=/.^/,u=function(a){return a.replace(/\\\\/g,"\\").replace(/\\'/g,"'")};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape||t,function(a,b){return"',_.escape("+ 29 | u(b)+"),'"}).replace(d.interpolate||t,function(a,b){return"',"+u(b)+",'"}).replace(d.evaluate||t,function(a,b){return"');"+u(b).replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e.call(this,a,b)}};b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var v=function(a,c){return c?b(a).chain():a},K=function(a,c){m.prototype[a]= 30 | function(){var a=i.call(arguments);H.call(a,this._wrapped);return v(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return v(d,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return v(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain= 31 | true;return this};m.prototype.value=function(){return this._wrapped}}).call(this); 32 | -------------------------------------------------------------------------------- /docs/_build/html/_static/alabaster.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | @import url("basic.css"); 19 | 20 | /* -- page layout ----------------------------------------------------------- */ 21 | 22 | body { 23 | font-family: 'goudy old style', 'minion pro', 'bell mt', Georgia, 'Hiragino Mincho Pro', serif; 24 | font-size: 17px; 25 | background-color: white; 26 | color: #000; 27 | margin: 0; 28 | padding: 0; 29 | } 30 | 31 | div.document { 32 | width: 940px; 33 | margin: 30px auto 0 auto; 34 | } 35 | 36 | div.documentwrapper { 37 | float: left; 38 | width: 100%; 39 | } 40 | 41 | div.bodywrapper { 42 | margin: 0 0 0 220px; 43 | } 44 | 45 | div.sphinxsidebar { 46 | width: 220px; 47 | } 48 | 49 | hr { 50 | border: 1px solid #B1B4B6; 51 | } 52 | 53 | div.body { 54 | background-color: #ffffff; 55 | color: #3E4349; 56 | padding: 0 30px 0 30px; 57 | } 58 | 59 | div.body > .section { 60 | text-align: left; 61 | } 62 | 63 | div.footer { 64 | width: 940px; 65 | margin: 20px auto 30px auto; 66 | font-size: 14px; 67 | color: #888; 68 | text-align: right; 69 | } 70 | 71 | div.footer a { 72 | color: #888; 73 | } 74 | 75 | 76 | div.relations { 77 | display: none; 78 | } 79 | 80 | 81 | div.sphinxsidebar a { 82 | color: #444; 83 | text-decoration: none; 84 | border-bottom: 1px dotted #999; 85 | } 86 | 87 | div.sphinxsidebar a:hover { 88 | border-bottom: 1px solid #999; 89 | } 90 | 91 | div.sphinxsidebar { 92 | font-size: 14px; 93 | line-height: 1.5; 94 | } 95 | 96 | div.sphinxsidebarwrapper { 97 | padding: 18px 10px; 98 | } 99 | 100 | div.sphinxsidebarwrapper p.logo { 101 | padding: 0; 102 | margin: -10px 0 0 0px; 103 | text-align: center; 104 | } 105 | 106 | div.sphinxsidebarwrapper h1.logo { 107 | margin-top: -10px; 108 | text-align: center; 109 | margin-bottom: 5px; 110 | text-align: left; 111 | } 112 | 113 | div.sphinxsidebarwrapper h1.logo-name { 114 | margin-top: 0px; 115 | } 116 | 117 | div.sphinxsidebarwrapper p.blurb { 118 | margin-top: 0; 119 | font-style: normal; 120 | } 121 | 122 | div.sphinxsidebar h3, 123 | div.sphinxsidebar h4 { 124 | font-family: 'Garamond', 'Georgia', serif; 125 | color: #444; 126 | font-size: 24px; 127 | font-weight: normal; 128 | margin: 0 0 5px 0; 129 | padding: 0; 130 | } 131 | 132 | div.sphinxsidebar h4 { 133 | font-size: 20px; 134 | } 135 | 136 | div.sphinxsidebar h3 a { 137 | color: #444; 138 | } 139 | 140 | div.sphinxsidebar p.logo a, 141 | div.sphinxsidebar h3 a, 142 | div.sphinxsidebar p.logo a:hover, 143 | div.sphinxsidebar h3 a:hover { 144 | border: none; 145 | } 146 | 147 | div.sphinxsidebar p { 148 | color: #555; 149 | margin: 10px 0; 150 | } 151 | 152 | div.sphinxsidebar ul { 153 | margin: 10px 0; 154 | padding: 0; 155 | color: #000; 156 | } 157 | 158 | div.sphinxsidebar ul li.toctree-l1 > a { 159 | font-size: 120%; 160 | } 161 | 162 | div.sphinxsidebar ul li.toctree-l2 > a { 163 | font-size: 110%; 164 | } 165 | 166 | div.sphinxsidebar input { 167 | border: 1px solid #CCC; 168 | font-family: 'goudy old style', 'minion pro', 'bell mt', Georgia, 'Hiragino Mincho Pro', serif; 169 | font-size: 1em; 170 | } 171 | 172 | div.sphinxsidebar hr { 173 | border: none; 174 | height: 1px; 175 | color: #AAA; 176 | background: #AAA; 177 | 178 | text-align: left; 179 | margin-left: 0; 180 | width: 50%; 181 | } 182 | 183 | /* -- body styles ----------------------------------------------------------- */ 184 | 185 | a { 186 | color: #004B6B; 187 | text-decoration: underline; 188 | } 189 | 190 | a:hover { 191 | color: #6D4100; 192 | text-decoration: underline; 193 | } 194 | 195 | div.body h1, 196 | div.body h2, 197 | div.body h3, 198 | div.body h4, 199 | div.body h5, 200 | div.body h6 { 201 | font-family: 'Garamond', 'Georgia', serif; 202 | font-weight: normal; 203 | margin: 30px 0px 10px 0px; 204 | padding: 0; 205 | } 206 | 207 | div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } 208 | div.body h2 { font-size: 180%; } 209 | div.body h3 { font-size: 150%; } 210 | div.body h4 { font-size: 130%; } 211 | div.body h5 { font-size: 100%; } 212 | div.body h6 { font-size: 100%; } 213 | 214 | a.headerlink { 215 | color: #DDD; 216 | padding: 0 4px; 217 | text-decoration: none; 218 | } 219 | 220 | a.headerlink:hover { 221 | color: #444; 222 | background: #EAEAEA; 223 | } 224 | 225 | div.body p, div.body dd, div.body li { 226 | line-height: 1.4em; 227 | } 228 | 229 | div.admonition { 230 | margin: 20px 0px; 231 | padding: 10px 30px; 232 | background-color: #FCC; 233 | border: 1px solid #FAA; 234 | } 235 | 236 | div.admonition tt.xref, div.admonition a tt { 237 | border-bottom: 1px solid #fafafa; 238 | } 239 | 240 | dd div.admonition { 241 | margin-left: -60px; 242 | padding-left: 60px; 243 | } 244 | 245 | div.admonition p.admonition-title { 246 | font-family: 'Garamond', 'Georgia', serif; 247 | font-weight: normal; 248 | font-size: 24px; 249 | margin: 0 0 10px 0; 250 | padding: 0; 251 | line-height: 1; 252 | } 253 | 254 | div.admonition p.last { 255 | margin-bottom: 0; 256 | } 257 | 258 | div.highlight { 259 | background-color: white; 260 | } 261 | 262 | dt:target, .highlight { 263 | background: #FAF3E8; 264 | } 265 | 266 | div.note { 267 | background-color: #EEE; 268 | border: 1px solid #CCC; 269 | } 270 | 271 | div.seealso { 272 | background-color: #EEE; 273 | border: 1px solid #CCC; 274 | } 275 | 276 | div.topic { 277 | background-color: #eee; 278 | } 279 | 280 | p.admonition-title { 281 | display: inline; 282 | } 283 | 284 | p.admonition-title:after { 285 | content: ":"; 286 | } 287 | 288 | pre, tt, code { 289 | font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; 290 | font-size: 0.9em; 291 | } 292 | 293 | .hll { 294 | background-color: #FFC; 295 | margin: 0 -12px; 296 | padding: 0 12px; 297 | display: block; 298 | } 299 | 300 | img.screenshot { 301 | } 302 | 303 | tt.descname, tt.descclassname, code.descname, code.descclassname { 304 | font-size: 0.95em; 305 | } 306 | 307 | tt.descname, code.descname { 308 | padding-right: 0.08em; 309 | } 310 | 311 | img.screenshot { 312 | -moz-box-shadow: 2px 2px 4px #eee; 313 | -webkit-box-shadow: 2px 2px 4px #eee; 314 | box-shadow: 2px 2px 4px #eee; 315 | } 316 | 317 | table.docutils { 318 | border: 1px solid #888; 319 | -moz-box-shadow: 2px 2px 4px #eee; 320 | -webkit-box-shadow: 2px 2px 4px #eee; 321 | box-shadow: 2px 2px 4px #eee; 322 | } 323 | 324 | table.docutils td, table.docutils th { 325 | border: 1px solid #888; 326 | padding: 0.25em 0.7em; 327 | } 328 | 329 | table.field-list, table.footnote { 330 | border: none; 331 | -moz-box-shadow: none; 332 | -webkit-box-shadow: none; 333 | box-shadow: none; 334 | } 335 | 336 | table.footnote { 337 | margin: 15px 0; 338 | width: 100%; 339 | border: 1px solid #EEE; 340 | background: #FDFDFD; 341 | font-size: 0.9em; 342 | } 343 | 344 | table.footnote + table.footnote { 345 | margin-top: -15px; 346 | border-top: none; 347 | } 348 | 349 | table.field-list th { 350 | padding: 0 0.8em 0 0; 351 | } 352 | 353 | table.field-list td { 354 | padding: 0; 355 | } 356 | 357 | table.field-list p { 358 | margin-bottom: 0.8em; 359 | } 360 | 361 | table.footnote td.label { 362 | width: 0px; 363 | padding: 0.3em 0 0.3em 0.5em; 364 | } 365 | 366 | table.footnote td { 367 | padding: 0.3em 0.5em; 368 | } 369 | 370 | dl { 371 | margin: 0; 372 | padding: 0; 373 | } 374 | 375 | dl dd { 376 | margin-left: 30px; 377 | } 378 | 379 | blockquote { 380 | margin: 0 0 0 30px; 381 | padding: 0; 382 | } 383 | 384 | ul, ol { 385 | margin: 10px 0 10px 30px; 386 | padding: 0; 387 | } 388 | 389 | pre { 390 | background: #EEE; 391 | padding: 7px 30px; 392 | margin: 15px 0px; 393 | line-height: 1.3em; 394 | } 395 | 396 | dl pre, blockquote pre, li pre { 397 | margin-left: 0; 398 | padding-left: 30px; 399 | } 400 | 401 | dl dl pre { 402 | margin-left: -90px; 403 | padding-left: 90px; 404 | } 405 | 406 | tt, code { 407 | background-color: #ecf0f3; 408 | color: #222; 409 | /* padding: 1px 2px; */ 410 | } 411 | 412 | tt.xref, code.xref, a tt { 413 | background-color: #FBFBFB; 414 | border-bottom: 1px solid white; 415 | } 416 | 417 | a.reference { 418 | text-decoration: none; 419 | border-bottom: 1px dotted #004B6B; 420 | } 421 | 422 | a.reference:hover { 423 | border-bottom: 1px solid #6D4100; 424 | } 425 | 426 | a.footnote-reference { 427 | text-decoration: none; 428 | font-size: 0.7em; 429 | vertical-align: top; 430 | border-bottom: 1px dotted #004B6B; 431 | } 432 | 433 | a.footnote-reference:hover { 434 | border-bottom: 1px solid #6D4100; 435 | } 436 | 437 | a:hover tt, a:hover code { 438 | background: #EEE; 439 | } 440 | 441 | 442 | @media screen and (max-width: 870px) { 443 | 444 | div.sphinxsidebar { 445 | display: none; 446 | } 447 | 448 | div.document { 449 | width: 100%; 450 | 451 | } 452 | 453 | div.documentwrapper { 454 | margin-left: 0; 455 | margin-top: 0; 456 | margin-right: 0; 457 | margin-bottom: 0; 458 | } 459 | 460 | div.bodywrapper { 461 | margin-top: 0; 462 | margin-right: 0; 463 | margin-bottom: 0; 464 | margin-left: 0; 465 | } 466 | 467 | ul { 468 | margin-left: 0; 469 | } 470 | 471 | .document { 472 | width: auto; 473 | } 474 | 475 | .footer { 476 | width: auto; 477 | } 478 | 479 | .bodywrapper { 480 | margin: 0; 481 | } 482 | 483 | .footer { 484 | width: auto; 485 | } 486 | 487 | .github { 488 | display: none; 489 | } 490 | 491 | 492 | 493 | } 494 | 495 | 496 | 497 | @media screen and (max-width: 875px) { 498 | 499 | body { 500 | margin: 0; 501 | padding: 20px 30px; 502 | } 503 | 504 | div.documentwrapper { 505 | float: none; 506 | background: white; 507 | } 508 | 509 | div.sphinxsidebar { 510 | display: block; 511 | float: none; 512 | width: 102.5%; 513 | margin: 50px -30px -20px -30px; 514 | padding: 10px 20px; 515 | background: #333; 516 | color: #FFF; 517 | } 518 | 519 | div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, 520 | div.sphinxsidebar h3 a { 521 | color: white; 522 | } 523 | 524 | div.sphinxsidebar a { 525 | color: #AAA; 526 | } 527 | 528 | div.sphinxsidebar p.logo { 529 | display: none; 530 | } 531 | 532 | div.document { 533 | width: 100%; 534 | margin: 0; 535 | } 536 | 537 | div.footer { 538 | display: none; 539 | } 540 | 541 | div.bodywrapper { 542 | margin: 0; 543 | } 544 | 545 | div.body { 546 | min-height: 0; 547 | padding: 0; 548 | } 549 | 550 | .rtd_doc_footer { 551 | display: none; 552 | } 553 | 554 | .document { 555 | width: auto; 556 | } 557 | 558 | .footer { 559 | width: auto; 560 | } 561 | 562 | .footer { 563 | width: auto; 564 | } 565 | 566 | .github { 567 | display: none; 568 | } 569 | } 570 | 571 | 572 | /* misc. */ 573 | 574 | .revsys-inline { 575 | display: none!important; 576 | } 577 | 578 | /* Make nested-list/multi-paragraph items look better in Releases changelog 579 | * pages. Without this, docutils' magical list fuckery causes inconsistent 580 | * formatting between different release sub-lists. 581 | */ 582 | div#changelog > div.section > ul > li > p:only-child { 583 | margin-bottom: 0; 584 | } 585 | 586 | /* Hide fugly table cell borders in ..bibliography:: directive output */ 587 | table.docutils.citation, table.docutils.citation td, table.docutils.citation th { 588 | border: none; 589 | /* Below needed in some edge cases; if not applied, bottom shadows appear */ 590 | -moz-box-shadow: none; 591 | -webkit-box-shadow: none; 592 | box-shadow: none; 593 | } -------------------------------------------------------------------------------- /watsongraph/user.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import statistics 4 | from watsongraph.conceptmodel import ConceptModel 5 | from watsongraph.node import conceptualize 6 | 7 | 8 | class User: 9 | """ 10 | This module abstracts the application's `User`, whose preferences are stored in their own `ConceptModel`. 11 | """ 12 | id = '' 13 | password = '' 14 | model = None 15 | """The list of events that the user has already expressed interest or disinterest in is stored in their 16 | 'exceptions' field, so as not to repeatedly return the same items to them.""" 17 | exceptions = [] 18 | 19 | def __init__(self, model=ConceptModel(), user_id='', exceptions=None, password=''): 20 | """ 21 | 22 | :param model: The ConceptModel() initially associated with the user. An empty one by default. 23 | 24 | :param user_id: The id associated with the user. An empty string by default. 25 | 26 | :param exceptions: The exceptions (items already viewed and either acted upon or passed on) associated with \ 27 | the user. 28 | 29 | :param password: The password associated with the user. An empty string by default. 30 | 31 | """ 32 | 33 | self.id = user_id 34 | self.model = model 35 | if exceptions: 36 | self.exceptions = exceptions 37 | self.password = password 38 | 39 | def nodes(self): 40 | """ 41 | :return: The Concept() objects associated with the user's model. 42 | """ 43 | return self.model.nodes() 44 | 45 | def concepts(self): 46 | """ 47 | :return: The labels of the concepts associated with the user's model. 48 | """ 49 | return self.model.concepts() 50 | 51 | def interests(self): 52 | """ 53 | :return: Returns (interest, concept) pair tuples associated with the user. 54 | """ 55 | return sorted([("{0:.3f}".format(node.get_relevance()), node.concept) for node in self.model.nodes()], 56 | reverse=True) 57 | 58 | def interest_in(self, item): 59 | """ 60 | :param item: An Item object to be compared to. 61 | 62 | :return: Returns a float that rates this user's hypothesized interest in the given event, based on the 63 | intersection between their own ConceptModel and that of the examined Event. 64 | 65 | """ 66 | intersection = self.model.intersection_with_by_nodes(item.model) 67 | if len(intersection) == 0: 68 | return 0 69 | else: 70 | return sum([concept_node.get_relevance() for concept_node in intersection]) 71 | 72 | def get_best_item(self, item_list): 73 | """ 74 | Retrieves the event within a list of events which is most relevant to the given user's interests. 75 | 76 | :param item_list: The list of Item objects to be examined. 77 | 78 | :return: The Item which best matches the user's interests. 79 | 80 | """ 81 | best_item = None 82 | highest_relevance = 0.0 83 | for item in item_list: 84 | interest = self.interest_in(item) 85 | if interest >= highest_relevance and item.name not in self.exceptions: 86 | best_item = item 87 | highest_relevance = interest 88 | return best_item 89 | 90 | def express_interest(self, item): 91 | """ 92 | Merges interest in an event into the user model. Adds the Item in which interest has been expressed to the 93 | exceptions. 94 | 95 | :param item: Event object the user is expressing interest in. 96 | 97 | """ 98 | # Raise correlated relevancies. 99 | item_copy = item.model.copy() 100 | # self.model.merge_with(item.model) 101 | for concept in [concept for concept in item.concepts() if concept in self.concepts()]: 102 | new_relevance = min(1.0, statistics.mean([self.model.get_node(concept).get_relevance(), 103 | item.model.get_node(concept).get_relevance()]) * 1.2) 104 | item_copy.get_node(concept).properties['relevance'] = new_relevance 105 | # Bump down uncorrelated relevancies. 106 | for concept in [concept for concept in self.concepts() if concept not in item_copy.concepts()]: 107 | self.model.get_node(concept).properties['relevance'] *= 0.9 108 | # Remove irrelevant concepts (to keep the model relatively clean). 109 | self.model.merge_with(item_copy) 110 | for concept in [concept for concept in self.concepts() if self.model.get_node(concept).get_relevance() <= 0.2]: 111 | self.model.remove(concept) 112 | self.exceptions.append(item.name) 113 | 114 | def express_disinterest(self, item): 115 | """ 116 | Merges disinterest in an event into the user model. Adds the Item in which interest has been expressed to the 117 | exceptions. 118 | 119 | :param item: Event object the user is expressing disinterest in. 120 | 121 | """ 122 | self.exceptions.append(item.name) 123 | # Scale down overlapping concepts. 124 | for concept in [concept for concept in item.concepts() if concept in self.concepts()]: 125 | self.model.get_node(concept).properties['relevance'] *= 0.75 126 | # Remove irrelevant concepts (to keep the model relatively clean). 127 | for concept in [concept for concept in self.concepts() if self.model.get_node(concept).get_relevance() <= 0.2]: 128 | self.model.remove(concept) 129 | 130 | def input_interest(self, interest, level=0, limit=20): 131 | """ 132 | Resolves arbitrary user input to concepts, explodes the resultant nodes, and adds the resultant graph to the 133 | user's present one. 134 | 135 | :param interest: Arbitrary user input. 136 | 137 | :param level: The limit placed on the depth of the graph. A limit of 0 is highest, corresponding with the 138 | most popular articles; a limit of 5 is the broadest and graphs to the widest cachet of articles. This 139 | parameter is a parameter that is passed directly to the IBM Watson API call. 140 | 141 | :param limit: a cutoff placed on the number of related concepts to be returned. This parameter is passed 142 | directly to the IBM Watson API call. 143 | 144 | """ 145 | mapped_concept = conceptualize(interest) 146 | if mapped_concept: 147 | mapped_model = ConceptModel([mapped_concept]) 148 | mapped_model.get_node(mapped_concept).properties['relevance'] = 1.0 149 | mapped_model.explode(level=level, limit=limit) 150 | # Set relevancies based on edge weights. 151 | for node in list(mapped_model.graph[mapped_model.get_node(mapped_concept)].keys()): 152 | node.properties['relevance'] = mapped_model.graph[mapped_model.get_node(mapped_concept)][node]['weight'] 153 | self.model.merge_with(mapped_model) 154 | 155 | def input_interests(self, interests, level=0, limit=20): 156 | """ 157 | Resolves a series of arbitrary user inputs to concepts, explodes the resultant nodes, and adds the resultant 158 | graph to the user's present one. 159 | 160 | :param interests: Arbitrary user input. 161 | 162 | :param level: The limit placed on the depth of the graph. A limit of 0 is highest, corresponding with the 163 | most popular articles; a limit of 5 is the broadest and graphs to the widest cachet of articles. This 164 | parameter is a parameter that is passed directly to the IBM Watson API call. 165 | 166 | :param limit: a cutoff placed on the number of related concepts to be returned. This parameter is passed 167 | directly to the IBM Watson API call. 168 | 169 | """ 170 | for interest in interests: 171 | self.input_interest(interest, level=level, limit=limit) 172 | 173 | ####################### 174 | # Read/write methods. # 175 | ####################### 176 | 177 | def save_user(self, filename='accounts.json'): 178 | """ 179 | Saves a user to a JSON file. 180 | 181 | :param self: The user to be saved to JSON. 182 | 183 | :param filename: The filename for the account storage file; `accounts.json` is the default. 184 | 185 | """ 186 | user_schema = { 187 | "password": self.password, 188 | "model": self.model.to_json(), 189 | "id": self.id, 190 | "exceptions": self.exceptions 191 | } 192 | if filename not in [f for f in os.listdir('.') if os.path.isfile(f)]: 193 | new_file_schema = { 194 | "accounts": 195 | [user_schema] 196 | } 197 | f = open(filename, 'w') 198 | f.write(json.dumps(new_file_schema, indent=4)) 199 | f.close() 200 | else: 201 | data = json.load(open(filename)) 202 | ids = [account['id'] for account in data['accounts']] 203 | if self.id not in ids: 204 | data['accounts'].append(user_schema) 205 | with open(filename, 'w') as outfile: 206 | json.dump(data, outfile, indent=4) 207 | if self.id in ids: 208 | user_index = 0 209 | for i in range(0, len(data['accounts'])): 210 | if data['accounts'][i]['id'] == self.id: 211 | user_index = i 212 | break 213 | data['accounts'][user_index] = user_schema 214 | with open(filename, 'w') as outfile: 215 | json.dump(data, outfile, indent=4) 216 | 217 | def load_user(self, filename='accounts.json'): 218 | """ 219 | Load a user from a JSON file. 220 | 221 | :param filename: The filename for the account storage file; `accounts.json` is the default. 222 | 223 | """ 224 | if filename not in [f for f in os.listdir('.') if os.path.isfile(f)]: 225 | raise IOError('Error: accounts file ' + filename + ' not found.') 226 | else: 227 | data = json.load(open(filename)) 228 | user_ids = [account['id'] for account in data['accounts']] 229 | if self.id not in user_ids: 230 | raise IOError('Error: User with the ID ' + self.id + ' not found in accounts file ' + filename) 231 | else: 232 | user_index = 0 233 | for i in range(0, len(data['accounts'])): 234 | if data['accounts'][i]['id'] == self.id: 235 | user_index = i 236 | break 237 | user_data = data['accounts'][user_index] 238 | self.id = user_data['id'] 239 | self.model.load_from_json(user_data['model']) 240 | self.exceptions = user_data['exceptions'] 241 | if not self.exceptions: 242 | self.exceptions = [] 243 | self.password = user_data['password'] 244 | 245 | def update_user_credentials(self, filename='accounts.json'): 246 | """ 247 | Updates User information in the JSON. This is a seperate method in order to account for password and id 248 | manipulation (otherwise `save_user()` alone works fine). 249 | 250 | :param filename: The filename for the account storage file; `accounts.json` is the default. 251 | 252 | """ 253 | self.delete_user(filename) 254 | self.save_user(filename) 255 | 256 | def delete_user(self, filename='accounts.json'): 257 | """ 258 | Deletes a User object from the JSON entirely. 259 | 260 | :param filename: The filename for the account storage file; `accounts.json` is the default. 261 | 262 | """ 263 | if filename not in [f for f in os.listdir('.') if os.path.isfile(f)]: 264 | raise IOError('Error: accounts file ' + filename + ' not found.') 265 | else: 266 | data = json.load(open(filename)) 267 | user_index = 0 268 | for i in range(0, len(data['accounts'])): 269 | if data['accounts'][i]['id'] == self.id: 270 | user_index = i 271 | break 272 | data['accounts'].pop(user_index) 273 | with open(filename, 'w') as outfile: 274 | json.dump(data, outfile, indent=4) 275 | -------------------------------------------------------------------------------- /docs/_build/html/_static/basic.css: -------------------------------------------------------------------------------- 1 | /* 2 | * basic.css 3 | * ~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- basic theme. 6 | * 7 | * :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | /* -- main layout ----------------------------------------------------------- */ 13 | 14 | div.clearer { 15 | clear: both; 16 | } 17 | 18 | /* -- relbar ---------------------------------------------------------------- */ 19 | 20 | div.related { 21 | width: 100%; 22 | font-size: 90%; 23 | } 24 | 25 | div.related h3 { 26 | display: none; 27 | } 28 | 29 | div.related ul { 30 | margin: 0; 31 | padding: 0 0 0 10px; 32 | list-style: none; 33 | } 34 | 35 | div.related li { 36 | display: inline; 37 | } 38 | 39 | div.related li.right { 40 | float: right; 41 | margin-right: 5px; 42 | } 43 | 44 | /* -- sidebar --------------------------------------------------------------- */ 45 | 46 | div.sphinxsidebarwrapper { 47 | padding: 10px 5px 0 10px; 48 | } 49 | 50 | div.sphinxsidebar { 51 | float: left; 52 | width: 230px; 53 | margin-left: -100%; 54 | font-size: 90%; 55 | } 56 | 57 | div.sphinxsidebar ul { 58 | list-style: none; 59 | } 60 | 61 | div.sphinxsidebar ul ul, 62 | div.sphinxsidebar ul.want-points { 63 | margin-left: 20px; 64 | list-style: square; 65 | } 66 | 67 | div.sphinxsidebar ul ul { 68 | margin-top: 0; 69 | margin-bottom: 0; 70 | } 71 | 72 | div.sphinxsidebar form { 73 | margin-top: 10px; 74 | } 75 | 76 | div.sphinxsidebar input { 77 | border: 1px solid #98dbcc; 78 | font-family: sans-serif; 79 | font-size: 1em; 80 | } 81 | 82 | div.sphinxsidebar #searchbox input[type="text"] { 83 | width: 170px; 84 | } 85 | 86 | div.sphinxsidebar #searchbox input[type="submit"] { 87 | width: 30px; 88 | } 89 | 90 | img { 91 | border: 0; 92 | max-width: 100%; 93 | } 94 | 95 | /* -- search page ----------------------------------------------------------- */ 96 | 97 | ul.search { 98 | margin: 10px 0 0 20px; 99 | padding: 0; 100 | } 101 | 102 | ul.search li { 103 | padding: 5px 0 5px 20px; 104 | background-image: url(file.png); 105 | background-repeat: no-repeat; 106 | background-position: 0 7px; 107 | } 108 | 109 | ul.search li a { 110 | font-weight: bold; 111 | } 112 | 113 | ul.search li div.context { 114 | color: #888; 115 | margin: 2px 0 0 30px; 116 | text-align: left; 117 | } 118 | 119 | ul.keywordmatches li.goodmatch a { 120 | font-weight: bold; 121 | } 122 | 123 | /* -- index page ------------------------------------------------------------ */ 124 | 125 | table.contentstable { 126 | width: 90%; 127 | } 128 | 129 | table.contentstable p.biglink { 130 | line-height: 150%; 131 | } 132 | 133 | a.biglink { 134 | font-size: 1.3em; 135 | } 136 | 137 | span.linkdescr { 138 | font-style: italic; 139 | padding-top: 5px; 140 | font-size: 90%; 141 | } 142 | 143 | /* -- general index --------------------------------------------------------- */ 144 | 145 | table.indextable { 146 | width: 100%; 147 | } 148 | 149 | table.indextable td { 150 | text-align: left; 151 | vertical-align: top; 152 | } 153 | 154 | table.indextable dl, table.indextable dd { 155 | margin-top: 0; 156 | margin-bottom: 0; 157 | } 158 | 159 | table.indextable tr.pcap { 160 | height: 10px; 161 | } 162 | 163 | table.indextable tr.cap { 164 | margin-top: 10px; 165 | background-color: #f2f2f2; 166 | } 167 | 168 | img.toggler { 169 | margin-right: 3px; 170 | margin-top: 3px; 171 | cursor: pointer; 172 | } 173 | 174 | div.modindex-jumpbox { 175 | border-top: 1px solid #ddd; 176 | border-bottom: 1px solid #ddd; 177 | margin: 1em 0 1em 0; 178 | padding: 0.4em; 179 | } 180 | 181 | div.genindex-jumpbox { 182 | border-top: 1px solid #ddd; 183 | border-bottom: 1px solid #ddd; 184 | margin: 1em 0 1em 0; 185 | padding: 0.4em; 186 | } 187 | 188 | /* -- general body styles --------------------------------------------------- */ 189 | 190 | a.headerlink { 191 | visibility: hidden; 192 | } 193 | 194 | h1:hover > a.headerlink, 195 | h2:hover > a.headerlink, 196 | h3:hover > a.headerlink, 197 | h4:hover > a.headerlink, 198 | h5:hover > a.headerlink, 199 | h6:hover > a.headerlink, 200 | dt:hover > a.headerlink, 201 | caption:hover > a.headerlink, 202 | p.caption:hover > a.headerlink, 203 | div.code-block-caption:hover > a.headerlink { 204 | visibility: visible; 205 | } 206 | 207 | div.body p.caption { 208 | text-align: inherit; 209 | } 210 | 211 | div.body td { 212 | text-align: left; 213 | } 214 | 215 | .field-list ul { 216 | padding-left: 1em; 217 | } 218 | 219 | .first { 220 | margin-top: 0 !important; 221 | } 222 | 223 | p.rubric { 224 | margin-top: 30px; 225 | font-weight: bold; 226 | } 227 | 228 | img.align-left, .figure.align-left, object.align-left { 229 | clear: left; 230 | float: left; 231 | margin-right: 1em; 232 | } 233 | 234 | img.align-right, .figure.align-right, object.align-right { 235 | clear: right; 236 | float: right; 237 | margin-left: 1em; 238 | } 239 | 240 | img.align-center, .figure.align-center, object.align-center { 241 | display: block; 242 | margin-left: auto; 243 | margin-right: auto; 244 | } 245 | 246 | .align-left { 247 | text-align: left; 248 | } 249 | 250 | .align-center { 251 | text-align: center; 252 | } 253 | 254 | .align-right { 255 | text-align: right; 256 | } 257 | 258 | /* -- sidebars -------------------------------------------------------------- */ 259 | 260 | div.sidebar { 261 | margin: 0 0 0.5em 1em; 262 | border: 1px solid #ddb; 263 | padding: 7px 7px 0 7px; 264 | background-color: #ffe; 265 | width: 40%; 266 | float: right; 267 | } 268 | 269 | p.sidebar-title { 270 | font-weight: bold; 271 | } 272 | 273 | /* -- topics ---------------------------------------------------------------- */ 274 | 275 | div.topic { 276 | border: 1px solid #ccc; 277 | padding: 7px 7px 0 7px; 278 | margin: 10px 0 10px 0; 279 | } 280 | 281 | p.topic-title { 282 | font-size: 1.1em; 283 | font-weight: bold; 284 | margin-top: 10px; 285 | } 286 | 287 | /* -- admonitions ----------------------------------------------------------- */ 288 | 289 | div.admonition { 290 | margin-top: 10px; 291 | margin-bottom: 10px; 292 | padding: 7px; 293 | } 294 | 295 | div.admonition dt { 296 | font-weight: bold; 297 | } 298 | 299 | div.admonition dl { 300 | margin-bottom: 0; 301 | } 302 | 303 | p.admonition-title { 304 | margin: 0px 10px 5px 0px; 305 | font-weight: bold; 306 | } 307 | 308 | div.body p.centered { 309 | text-align: center; 310 | margin-top: 25px; 311 | } 312 | 313 | /* -- tables ---------------------------------------------------------------- */ 314 | 315 | table.docutils { 316 | border: 0; 317 | border-collapse: collapse; 318 | } 319 | 320 | table caption span.caption-number { 321 | font-style: italic; 322 | } 323 | 324 | table caption span.caption-text { 325 | } 326 | 327 | table.docutils td, table.docutils th { 328 | padding: 1px 8px 1px 5px; 329 | border-top: 0; 330 | border-left: 0; 331 | border-right: 0; 332 | border-bottom: 1px solid #aaa; 333 | } 334 | 335 | table.field-list td, table.field-list th { 336 | border: 0 !important; 337 | } 338 | 339 | table.footnote td, table.footnote th { 340 | border: 0 !important; 341 | } 342 | 343 | th { 344 | text-align: left; 345 | padding-right: 5px; 346 | } 347 | 348 | table.citation { 349 | border-left: solid 1px gray; 350 | margin-left: 1px; 351 | } 352 | 353 | table.citation td { 354 | border-bottom: none; 355 | } 356 | 357 | /* -- figures --------------------------------------------------------------- */ 358 | 359 | div.figure { 360 | margin: 0.5em; 361 | padding: 0.5em; 362 | } 363 | 364 | div.figure p.caption { 365 | padding: 0.3em; 366 | } 367 | 368 | div.figure p.caption span.caption-number { 369 | font-style: italic; 370 | } 371 | 372 | div.figure p.caption span.caption-text { 373 | } 374 | 375 | 376 | /* -- other body styles ----------------------------------------------------- */ 377 | 378 | ol.arabic { 379 | list-style: decimal; 380 | } 381 | 382 | ol.loweralpha { 383 | list-style: lower-alpha; 384 | } 385 | 386 | ol.upperalpha { 387 | list-style: upper-alpha; 388 | } 389 | 390 | ol.lowerroman { 391 | list-style: lower-roman; 392 | } 393 | 394 | ol.upperroman { 395 | list-style: upper-roman; 396 | } 397 | 398 | dl { 399 | margin-bottom: 15px; 400 | } 401 | 402 | dd p { 403 | margin-top: 0px; 404 | } 405 | 406 | dd ul, dd table { 407 | margin-bottom: 10px; 408 | } 409 | 410 | dd { 411 | margin-top: 3px; 412 | margin-bottom: 10px; 413 | margin-left: 30px; 414 | } 415 | 416 | dt:target, .highlighted { 417 | background-color: #fbe54e; 418 | } 419 | 420 | dl.glossary dt { 421 | font-weight: bold; 422 | font-size: 1.1em; 423 | } 424 | 425 | .field-list ul { 426 | margin: 0; 427 | padding-left: 1em; 428 | } 429 | 430 | .field-list p { 431 | margin: 0; 432 | } 433 | 434 | .optional { 435 | font-size: 1.3em; 436 | } 437 | 438 | .sig-paren { 439 | font-size: larger; 440 | } 441 | 442 | .versionmodified { 443 | font-style: italic; 444 | } 445 | 446 | .system-message { 447 | background-color: #fda; 448 | padding: 5px; 449 | border: 3px solid red; 450 | } 451 | 452 | .footnote:target { 453 | background-color: #ffa; 454 | } 455 | 456 | .line-block { 457 | display: block; 458 | margin-top: 1em; 459 | margin-bottom: 1em; 460 | } 461 | 462 | .line-block .line-block { 463 | margin-top: 0; 464 | margin-bottom: 0; 465 | margin-left: 1.5em; 466 | } 467 | 468 | .guilabel, .menuselection { 469 | font-family: sans-serif; 470 | } 471 | 472 | .accelerator { 473 | text-decoration: underline; 474 | } 475 | 476 | .classifier { 477 | font-style: oblique; 478 | } 479 | 480 | abbr, acronym { 481 | border-bottom: dotted 1px; 482 | cursor: help; 483 | } 484 | 485 | /* -- code displays --------------------------------------------------------- */ 486 | 487 | pre { 488 | overflow: auto; 489 | overflow-y: hidden; /* fixes display issues on Chrome browsers */ 490 | } 491 | 492 | td.linenos pre { 493 | padding: 5px 0px; 494 | border: 0; 495 | background-color: transparent; 496 | color: #aaa; 497 | } 498 | 499 | table.highlighttable { 500 | margin-left: 0.5em; 501 | } 502 | 503 | table.highlighttable td { 504 | padding: 0 0.5em 0 0.5em; 505 | } 506 | 507 | div.code-block-caption { 508 | padding: 2px 5px; 509 | font-size: small; 510 | } 511 | 512 | div.code-block-caption code { 513 | background-color: transparent; 514 | } 515 | 516 | div.code-block-caption + div > div.highlight > pre { 517 | margin-top: 0; 518 | } 519 | 520 | div.code-block-caption span.caption-number { 521 | padding: 0.1em 0.3em; 522 | font-style: italic; 523 | } 524 | 525 | div.code-block-caption span.caption-text { 526 | } 527 | 528 | div.literal-block-wrapper { 529 | padding: 1em 1em 0; 530 | } 531 | 532 | div.literal-block-wrapper div.highlight { 533 | margin: 0; 534 | } 535 | 536 | code.descname { 537 | background-color: transparent; 538 | font-weight: bold; 539 | font-size: 1.2em; 540 | } 541 | 542 | code.descclassname { 543 | background-color: transparent; 544 | } 545 | 546 | code.xref, a code { 547 | background-color: transparent; 548 | font-weight: bold; 549 | } 550 | 551 | h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { 552 | background-color: transparent; 553 | } 554 | 555 | .viewcode-link { 556 | float: right; 557 | } 558 | 559 | .viewcode-back { 560 | float: right; 561 | font-family: sans-serif; 562 | } 563 | 564 | div.viewcode-block:target { 565 | margin: -1px -10px; 566 | padding: 0 10px; 567 | } 568 | 569 | /* -- math display ---------------------------------------------------------- */ 570 | 571 | img.math { 572 | vertical-align: middle; 573 | } 574 | 575 | div.body div.math p { 576 | text-align: center; 577 | } 578 | 579 | span.eqno { 580 | float: right; 581 | } 582 | 583 | /* -- printout stylesheet --------------------------------------------------- */ 584 | 585 | @media print { 586 | div.document, 587 | div.documentwrapper, 588 | div.bodywrapper { 589 | margin: 0 !important; 590 | width: 100%; 591 | } 592 | 593 | div.sphinxsidebar, 594 | div.related, 595 | div.footer, 596 | #top-link { 597 | display: none; 598 | } 599 | } -------------------------------------------------------------------------------- /docs/_build/html/genindex.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Index — watsongraph 0.2.2 documentation 11 | 12 | 13 | 14 | 15 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
36 |
37 |
38 |
39 | 40 | 41 |

Index

42 | 43 |
44 | _ 45 | | A 46 | | C 47 | | D 48 | | E 49 | | G 50 | | I 51 | | L 52 | | M 53 | | N 54 | | R 55 | | S 56 | | T 57 | | U 58 | 59 |
60 |

_

61 | 62 | 78 |
63 | 64 |
__init__() (conceptmodel.ConceptModel method) 65 |
66 | 67 |
68 | 69 |
(item.Item method) 70 |
71 | 72 | 73 |
(user.User method) 74 |
75 | 76 |
77 |
79 | 80 |

A

81 | 82 | 96 | 106 |
83 | 84 |
abridge() (conceptmodel.ConceptModel method) 85 |
86 | 87 | 88 |
add() (conceptmodel.ConceptModel method) 89 |
90 | 91 | 92 |
add_edge() (conceptmodel.ConceptModel method) 93 |
94 | 95 |
97 | 98 |
add_edges() (conceptmodel.ConceptModel method) 99 |
100 | 101 | 102 |
augment() (conceptmodel.ConceptModel method) 103 |
104 | 105 |
107 | 108 |

C

109 | 110 | 134 | 148 |
111 | 112 |
ConceptModel (class in conceptmodel) 113 |
114 | 115 | 116 |
conceptmodel (module) 117 |
118 | 119 | 120 |
concepts() (conceptmodel.ConceptModel method) 121 |
122 | 123 |
124 | 125 |
(item.Item method) 126 |
127 | 128 | 129 |
(user.User method) 130 |
131 | 132 |
133 |
135 | 136 |
concepts_by_property() (conceptmodel.ConceptModel method) 137 |
138 | 139 | 140 |
concepts_by_view_count() (conceptmodel.ConceptModel method) 141 |
142 | 143 | 144 |
copy() (conceptmodel.ConceptModel method) 145 |
146 | 147 |
149 | 150 |

D

151 | 152 | 158 |
153 | 154 |
delete_user() (user.User method) 155 |
156 | 157 |
159 | 160 |

E

161 | 162 | 176 | 190 |
163 | 164 |
edges() (conceptmodel.ConceptModel method) 165 |
166 | 167 | 168 |
expand() (conceptmodel.ConceptModel method) 169 |
170 | 171 | 172 |
explode() (conceptmodel.ConceptModel method) 173 |
174 | 175 |
177 | 178 |
explode_edges() (conceptmodel.ConceptModel method) 179 |
180 | 181 | 182 |
express_disinterest() (user.User method) 183 |
184 | 185 | 186 |
express_interest() (user.User method) 187 |
188 | 189 |
191 | 192 |

G

193 | 194 | 200 | 206 |
195 | 196 |
get_best_item() (user.User method) 197 |
198 | 199 |
201 | 202 |
get_view_count() (conceptmodel.ConceptModel method) 203 |
204 | 205 |
207 | 208 |

I

209 | 210 | 224 | 238 |
211 | 212 |
input_interest() (user.User method) 213 |
214 | 215 | 216 |
input_interests() (user.User method) 217 |
218 | 219 | 220 |
interest_in() (user.User method) 221 |
222 | 223 |
225 | 226 |
interests() (user.User method) 227 |
228 | 229 | 230 |
Item (class in item) 231 |
232 | 233 | 234 |
item (module) 235 |
236 | 237 |
239 | 240 |

L

241 | 242 | 258 | 264 |
243 | 244 |
load() (item.Item method) 245 |
246 | 247 | 248 |
load_from_json() (conceptmodel.ConceptModel method) 249 |
250 | 251 |
252 | 253 |
(item.Item method) 254 |
255 | 256 |
257 |
259 | 260 |
load_user() (user.User method) 261 |
262 | 263 |
265 | 266 |

M

267 | 268 | 274 | 280 |
269 | 270 |
map_property() (conceptmodel.ConceptModel method) 271 |
272 | 273 |
275 | 276 |
merge_with() (conceptmodel.ConceptModel method) 277 |
278 | 279 |
281 | 282 |

N

283 | 284 | 290 |
285 | 286 |
neighborhood() (conceptmodel.ConceptModel method) 287 |
288 | 289 |
291 | 292 |

R

293 | 294 | 300 | 306 |
295 | 296 |
relevancies() (item.Item method) 297 |
298 | 299 |
301 | 302 |
remove() (conceptmodel.ConceptModel method) 303 |
304 | 305 |
307 | 308 |

S

309 | 310 | 320 | 330 |
311 | 312 |
save() (item.Item method) 313 |
314 | 315 | 316 |
save_user() (user.User method) 317 |
318 | 319 |
321 | 322 |
set_property() (conceptmodel.ConceptModel method) 323 |
324 | 325 | 326 |
set_view_counts() (conceptmodel.ConceptModel method) 327 |
328 | 329 |
331 | 332 |

T

333 | 334 | 346 |
335 | 336 |
to_json() (conceptmodel.ConceptModel method) 337 |
338 | 339 |
340 | 341 |
(item.Item method) 342 |
343 | 344 |
345 |
347 | 348 |

U

349 | 350 | 360 | 366 |
351 | 352 |
update_user_credentials() (user.User method) 353 |
354 | 355 | 356 |
User (class in user) 357 |
358 | 359 |
361 | 362 |
user (module) 363 |
364 | 365 |
367 | 368 | 369 | 370 |
371 |
372 |
373 | 399 |
400 |
401 | 409 | 410 | 411 | 412 | 413 | 414 | -------------------------------------------------------------------------------- /docs/_build/html/_static/searchtools.js: -------------------------------------------------------------------------------- 1 | /* 2 | * searchtools.js_t 3 | * ~~~~~~~~~~~~~~~~ 4 | * 5 | * Sphinx JavaScript utilties for the full-text search. 6 | * 7 | * :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | 13 | /* Non-minified version JS is _stemmer.js if file is provided */ 14 | /** 15 | * Porter Stemmer 16 | */ 17 | var Stemmer = function() { 18 | 19 | var step2list = { 20 | ational: 'ate', 21 | tional: 'tion', 22 | enci: 'ence', 23 | anci: 'ance', 24 | izer: 'ize', 25 | bli: 'ble', 26 | alli: 'al', 27 | entli: 'ent', 28 | eli: 'e', 29 | ousli: 'ous', 30 | ization: 'ize', 31 | ation: 'ate', 32 | ator: 'ate', 33 | alism: 'al', 34 | iveness: 'ive', 35 | fulness: 'ful', 36 | ousness: 'ous', 37 | aliti: 'al', 38 | iviti: 'ive', 39 | biliti: 'ble', 40 | logi: 'log' 41 | }; 42 | 43 | var step3list = { 44 | icate: 'ic', 45 | ative: '', 46 | alize: 'al', 47 | iciti: 'ic', 48 | ical: 'ic', 49 | ful: '', 50 | ness: '' 51 | }; 52 | 53 | var c = "[^aeiou]"; // consonant 54 | var v = "[aeiouy]"; // vowel 55 | var C = c + "[^aeiouy]*"; // consonant sequence 56 | var V = v + "[aeiou]*"; // vowel sequence 57 | 58 | var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 59 | var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 60 | var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 61 | var s_v = "^(" + C + ")?" + v; // vowel in stem 62 | 63 | this.stemWord = function (w) { 64 | var stem; 65 | var suffix; 66 | var firstch; 67 | var origword = w; 68 | 69 | if (w.length < 3) 70 | return w; 71 | 72 | var re; 73 | var re2; 74 | var re3; 75 | var re4; 76 | 77 | firstch = w.substr(0,1); 78 | if (firstch == "y") 79 | w = firstch.toUpperCase() + w.substr(1); 80 | 81 | // Step 1a 82 | re = /^(.+?)(ss|i)es$/; 83 | re2 = /^(.+?)([^s])s$/; 84 | 85 | if (re.test(w)) 86 | w = w.replace(re,"$1$2"); 87 | else if (re2.test(w)) 88 | w = w.replace(re2,"$1$2"); 89 | 90 | // Step 1b 91 | re = /^(.+?)eed$/; 92 | re2 = /^(.+?)(ed|ing)$/; 93 | if (re.test(w)) { 94 | var fp = re.exec(w); 95 | re = new RegExp(mgr0); 96 | if (re.test(fp[1])) { 97 | re = /.$/; 98 | w = w.replace(re,""); 99 | } 100 | } 101 | else if (re2.test(w)) { 102 | var fp = re2.exec(w); 103 | stem = fp[1]; 104 | re2 = new RegExp(s_v); 105 | if (re2.test(stem)) { 106 | w = stem; 107 | re2 = /(at|bl|iz)$/; 108 | re3 = new RegExp("([^aeiouylsz])\\1$"); 109 | re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); 110 | if (re2.test(w)) 111 | w = w + "e"; 112 | else if (re3.test(w)) { 113 | re = /.$/; 114 | w = w.replace(re,""); 115 | } 116 | else if (re4.test(w)) 117 | w = w + "e"; 118 | } 119 | } 120 | 121 | // Step 1c 122 | re = /^(.+?)y$/; 123 | if (re.test(w)) { 124 | var fp = re.exec(w); 125 | stem = fp[1]; 126 | re = new RegExp(s_v); 127 | if (re.test(stem)) 128 | w = stem + "i"; 129 | } 130 | 131 | // Step 2 132 | re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; 133 | if (re.test(w)) { 134 | var fp = re.exec(w); 135 | stem = fp[1]; 136 | suffix = fp[2]; 137 | re = new RegExp(mgr0); 138 | if (re.test(stem)) 139 | w = stem + step2list[suffix]; 140 | } 141 | 142 | // Step 3 143 | re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; 144 | if (re.test(w)) { 145 | var fp = re.exec(w); 146 | stem = fp[1]; 147 | suffix = fp[2]; 148 | re = new RegExp(mgr0); 149 | if (re.test(stem)) 150 | w = stem + step3list[suffix]; 151 | } 152 | 153 | // Step 4 154 | re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; 155 | re2 = /^(.+?)(s|t)(ion)$/; 156 | if (re.test(w)) { 157 | var fp = re.exec(w); 158 | stem = fp[1]; 159 | re = new RegExp(mgr1); 160 | if (re.test(stem)) 161 | w = stem; 162 | } 163 | else if (re2.test(w)) { 164 | var fp = re2.exec(w); 165 | stem = fp[1] + fp[2]; 166 | re2 = new RegExp(mgr1); 167 | if (re2.test(stem)) 168 | w = stem; 169 | } 170 | 171 | // Step 5 172 | re = /^(.+?)e$/; 173 | if (re.test(w)) { 174 | var fp = re.exec(w); 175 | stem = fp[1]; 176 | re = new RegExp(mgr1); 177 | re2 = new RegExp(meq1); 178 | re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); 179 | if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) 180 | w = stem; 181 | } 182 | re = /ll$/; 183 | re2 = new RegExp(mgr1); 184 | if (re.test(w) && re2.test(w)) { 185 | re = /.$/; 186 | w = w.replace(re,""); 187 | } 188 | 189 | // and turn initial Y back to y 190 | if (firstch == "y") 191 | w = firstch.toLowerCase() + w.substr(1); 192 | return w; 193 | } 194 | } 195 | 196 | 197 | 198 | /** 199 | * Simple result scoring code. 200 | */ 201 | var Scorer = { 202 | // Implement the following function to further tweak the score for each result 203 | // The function takes a result array [filename, title, anchor, descr, score] 204 | // and returns the new score. 205 | /* 206 | score: function(result) { 207 | return result[4]; 208 | }, 209 | */ 210 | 211 | // query matches the full name of an object 212 | objNameMatch: 11, 213 | // or matches in the last dotted part of the object name 214 | objPartialMatch: 6, 215 | // Additive scores depending on the priority of the object 216 | objPrio: {0: 15, // used to be importantResults 217 | 1: 5, // used to be objectResults 218 | 2: -5}, // used to be unimportantResults 219 | // Used when the priority is not in the mapping. 220 | objPrioDefault: 0, 221 | 222 | // query found in title 223 | title: 15, 224 | // query found in terms 225 | term: 5 226 | }; 227 | 228 | 229 | /** 230 | * Search Module 231 | */ 232 | var Search = { 233 | 234 | _index : null, 235 | _queued_query : null, 236 | _pulse_status : -1, 237 | 238 | init : function() { 239 | var params = $.getQueryParameters(); 240 | if (params.q) { 241 | var query = params.q[0]; 242 | $('input[name="q"]')[0].value = query; 243 | this.performSearch(query); 244 | } 245 | }, 246 | 247 | loadIndex : function(url) { 248 | $.ajax({type: "GET", url: url, data: null, 249 | dataType: "script", cache: true, 250 | complete: function(jqxhr, textstatus) { 251 | if (textstatus != "success") { 252 | document.getElementById("searchindexloader").src = url; 253 | } 254 | }}); 255 | }, 256 | 257 | setIndex : function(index) { 258 | var q; 259 | this._index = index; 260 | if ((q = this._queued_query) !== null) { 261 | this._queued_query = null; 262 | Search.query(q); 263 | } 264 | }, 265 | 266 | hasIndex : function() { 267 | return this._index !== null; 268 | }, 269 | 270 | deferQuery : function(query) { 271 | this._queued_query = query; 272 | }, 273 | 274 | stopPulse : function() { 275 | this._pulse_status = 0; 276 | }, 277 | 278 | startPulse : function() { 279 | if (this._pulse_status >= 0) 280 | return; 281 | function pulse() { 282 | var i; 283 | Search._pulse_status = (Search._pulse_status + 1) % 4; 284 | var dotString = ''; 285 | for (i = 0; i < Search._pulse_status; i++) 286 | dotString += '.'; 287 | Search.dots.text(dotString); 288 | if (Search._pulse_status > -1) 289 | window.setTimeout(pulse, 500); 290 | } 291 | pulse(); 292 | }, 293 | 294 | /** 295 | * perform a search for something (or wait until index is loaded) 296 | */ 297 | performSearch : function(query) { 298 | // create the required interface elements 299 | this.out = $('#search-results'); 300 | this.title = $('

' + _('Searching') + '

').appendTo(this.out); 301 | this.dots = $('').appendTo(this.title); 302 | this.status = $('

').appendTo(this.out); 303 | this.output = $('