aboutsummaryrefslogtreecommitdiffstats
path: root/tagit
diff options
context:
space:
mode:
authorMatthias Baumgartner <dev@igsor.net>2023-01-13 09:49:10 +0100
committerMatthias Baumgartner <dev@igsor.net>2023-01-13 09:49:10 +0100
commit9c366758665d9cfee7796ee45a8167a5412ae9ae (patch)
treeb42e0a1fd4b1bd59fc31fad6267b83c2dc9a3a3b /tagit
parent8f2f697f7ed52b7e1c7a17411b2de526b6490691 (diff)
downloadtagit-9c366758665d9cfee7796ee45a8167a5412ae9ae.tar.gz
tagit-9c366758665d9cfee7796ee45a8167a5412ae9ae.tar.bz2
tagit-9c366758665d9cfee7796ee45a8167a5412ae9ae.zip
filter early port, parsing adaptions
Diffstat (limited to 'tagit')
-rw-r--r--tagit/actions/__init__.py14
-rw-r--r--tagit/actions/filter.py33
-rw-r--r--tagit/apps/port-config.yaml4
-rw-r--r--tagit/assets/icons/scalable/filter/add.svg102
-rw-r--r--tagit/assets/icons/scalable/filter/address.svg124
-rw-r--r--tagit/assets/icons/scalable/filter/go_back.svg158
-rw-r--r--tagit/assets/icons/scalable/filter/go_forth.svg154
-rw-r--r--tagit/assets/icons/scalable/filter/shingles.svg217
-rw-r--r--tagit/dialogues/__init__.py14
-rw-r--r--tagit/dialogues/autoinput.py73
-rw-r--r--tagit/dialogues/dialogue.kv114
-rw-r--r--tagit/dialogues/dialogue.py108
-rw-r--r--tagit/dialogues/error.py45
-rw-r--r--tagit/dialogues/license.t33
-rw-r--r--tagit/dialogues/simple_input.kv30
-rw-r--r--tagit/dialogues/simple_input.py55
-rw-r--r--tagit/dialogues/stoken.py40
-rw-r--r--tagit/parsing/__init__.py8
-rw-r--r--tagit/parsing/filter.py (renamed from tagit/parsing/search.py)169
-rw-r--r--tagit/parsing/sort.py17
-rw-r--r--tagit/utils/__init__.py1
-rw-r--r--tagit/utils/bsfs.py10
-rw-r--r--tagit/utils/namespaces.py30
-rw-r--r--tagit/utils/shared.py12
-rw-r--r--tagit/widgets/filter.py13
-rw-r--r--tagit/widgets/session.py4
26 files changed, 1430 insertions, 152 deletions
diff --git a/tagit/actions/__init__.py b/tagit/actions/__init__.py
index 034c4a1..c34cbe8 100644
--- a/tagit/actions/__init__.py
+++ b/tagit/actions/__init__.py
@@ -57,13 +57,13 @@ class ActionBuilder(BuilderBase):
'SelectInvert': browser.SelectInvert,
'Select': browser.Select,
## filter
- #'AddToken': filter.AddToken,
- #'RemoveToken': filter.RemoveToken,
- #'SetToken': filter.SetToken,
- #'EditToken': filter.EditToken,
- #'GoBack': filter.GoBack,
- #'GoForth': filter.GoForth,
- #'JumpToToken': filter.JumpToToken,
+ 'AddToken': filter.AddToken,
+ 'RemoveToken': filter.RemoveToken,
+ 'SetToken': filter.SetToken,
+ 'EditToken': filter.EditToken,
+ 'GoBack': filter.GoBack,
+ 'GoForth': filter.GoForth,
+ 'JumpToToken': filter.JumpToToken,
#'SearchByAddressOnce': filter.SearchByAddressOnce,
#'SearchmodeSwitch': filter.SearchmodeSwitch,
## grouping
diff --git a/tagit/actions/filter.py b/tagit/actions/filter.py
index 3702879..e878952 100644
--- a/tagit/actions/filter.py
+++ b/tagit/actions/filter.py
@@ -12,14 +12,12 @@ from kivy.lang import Builder
import kivy.properties as kp
# tagit imports
-from tagit import config
-from tagit import dialogues
-#from tagit.parsing import ParserError # FIXME: mb/port
-#from tagit.parsing.search import ast_from_string, ast_to_string, ast # FIXME: mb/port
-#from tagit.storage.base import ns # FIXME: mb/port
-from tagit.utils import Frame
-from tagit.widgets.bindings import Binding
+from tagit import config, dialogues
+from tagit.utils import errors, Frame
+from tagit.widgets import Binding
from tagit.widgets.filter import FilterAwareMixin
+#from tagit.parsing.search import ast_to_string, ast # FIXME: mb/port
+#from tagit.storage.base import ns # FIXME: mb/port
# inner-module imports
from .action import Action
@@ -54,7 +52,7 @@ class SetToken(Action):
with self.root.filter as filter:
try:
# parse filter into tokens
- tokens = list(ast_from_string(text))
+ tokens = list(self.root.session.filter_from_string(text))
# grab current frame
filter.f_head.append(self.root.browser.frame)
@@ -93,7 +91,10 @@ class AddToken(Action):
def apply(self, token=None):
if token is None:
- sugg = {node.label for node in self.root.session.storage.all(ns.tagit.storage.base.Tag)}
+ #sugg = {node.label for node in self.root.session.storage.all(ns.tagit.storage.base.Tag)}
+ # FIXME: mb/port/bsfs
+ #sugg = set(self.root.session.storage.all(ns.bsfs.Tag).label())
+ sugg = {'hello', 'world'}
dlg = dialogues.TokenEdit(suggestions=sugg)
dlg.bind(on_ok=lambda wx: self.add_from_string(wx.text))
dlg.open()
@@ -104,8 +105,8 @@ class AddToken(Action):
def add_from_string(self, text):
try:
- self.add_token(ast_from_string(text))
- except ParserError as e:
+ self.add_token(self.root.session.filter_from_string(text))
+ except errors.ParserError as e:
dialogues.Error(text=f'syntax error: {e}').open()
def add_token(self, tokens):
@@ -128,8 +129,10 @@ class EditToken(Action):
text = kp.StringProperty('Edit token')
def apply(self, token):
- sugg = {node.label for node in self.root.session.storage.all(ns.tagit.storage.base.Tag)}
- text = ast_to_string(token)
+ #sugg = {node.label for node in self.root.session.storage.all(ns.tagit.storage.base.Tag)} # FIXME: mb/port
+ sugg = {'hello', 'world'} # FIXME: mb/port
+ #text = ast_to_string(token)
+ text = 'hello world'
dlg = dialogues.TokenEdit(text=text, suggestions=sugg)
dlg.bind(on_ok=lambda obj: self.on_ok(token, obj))
dlg.open()
@@ -137,8 +140,8 @@ class EditToken(Action):
def on_ok(self, token, obj):
with self.root.filter as filter:
try:
- tokens_from_text = ast_from_string(obj.text)
- except ParserError as e:
+ tokens_from_text = self.root.session.filter_from_string(obj.text) # FIXME: mb/port
+ except errors.ParserError as e:
dialogues.Error(text=f'Invalid token: {e}').open()
return
diff --git a/tagit/apps/port-config.yaml b/tagit/apps/port-config.yaml
index 501eacd..d5c45e9 100644
--- a/tagit/apps/port-config.yaml
+++ b/tagit/apps/port-config.yaml
@@ -20,6 +20,7 @@ ui:
- ShowDashboard
#- AddTag
#- EditTag
+ - AddToken
#- CreateGroup
#- DissolveGroup
#- SelectAll
@@ -30,6 +31,9 @@ ui:
#- SelectSingle
#- SelectMulti
#- SelectRange
+ filter:
+ - AddToken
+ - EditToken
context:
app:
- ShowSettings
diff --git a/tagit/assets/icons/scalable/filter/add.svg b/tagit/assets/icons/scalable/filter/add.svg
new file mode 100644
index 0000000..a544053
--- /dev/null
+++ b/tagit/assets/icons/scalable/filter/add.svg
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="100mm"
+ height="100mm"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)"
+ sodipodi:docname="add.svg"
+ inkscape:export-filename="../../kivy/filter/add.png"
+ inkscape:export-xdpi="7.6199999"
+ inkscape:export-ydpi="7.6199999">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1.979899"
+ inkscape:cx="106.95662"
+ inkscape:cy="169.42756"
+ inkscape:document-units="mm"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:snap-global="true"
+ inkscape:snap-bbox="true"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:window-width="1920"
+ inkscape:window-height="1151"
+ inkscape:window-x="0"
+ inkscape:window-y="25"
+ inkscape:window-maximized="1"
+ inkscape:pagecheckerboard="true"
+ units="mm"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:snap-bbox-midpoints="true"
+ inkscape:object-paths="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:snap-midpoints="true"
+ inkscape:snap-object-midpoints="true"
+ inkscape:snap-center="true"
+ inkscape:snap-text-baseline="true"
+ inkscape:snap-page="true"
+ inkscape:lockguides="false">
+ <sodipodi:guide
+ position="188.97638,188.97638"
+ orientation="0,1"
+ id="guide1099"
+ inkscape:locked="false"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ <sodipodi:guide
+ position="188.97638,188.97638"
+ orientation="1,0"
+ id="guide1101"
+ inkscape:locked="false"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-167.28122,-322.85977)">
+ <path
+ style="fill:none;stroke:#c8c8c8;stroke-width:29.81036186;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 182.82699,337.92736 123.49426,144.97182 v 203.0089 L 406.37348,630.06527 V 482.89684 L 529.86773,337.92736 H 182.82699"
+ id="path851"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/tagit/assets/icons/scalable/filter/address.svg b/tagit/assets/icons/scalable/filter/address.svg
new file mode 100644
index 0000000..de30faa
--- /dev/null
+++ b/tagit/assets/icons/scalable/filter/address.svg
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="100mm"
+ height="100mm"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)"
+ sodipodi:docname="address.svg"
+ inkscape:export-filename="../../kivy/filter/address.png"
+ inkscape:export-xdpi="7.6199999"
+ inkscape:export-ydpi="7.6199999">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.9899495"
+ inkscape:cx="8.0179354"
+ inkscape:cy="311.11216"
+ inkscape:document-units="mm"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:snap-global="true"
+ inkscape:snap-bbox="true"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:window-width="1920"
+ inkscape:window-height="1151"
+ inkscape:window-x="0"
+ inkscape:window-y="25"
+ inkscape:window-maximized="1"
+ inkscape:pagecheckerboard="true"
+ units="mm"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:snap-bbox-midpoints="true"
+ inkscape:object-paths="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:snap-midpoints="true"
+ inkscape:snap-object-midpoints="true"
+ inkscape:snap-center="true"
+ inkscape:snap-text-baseline="true"
+ inkscape:snap-page="true"
+ inkscape:lockguides="false">
+ <sodipodi:guide
+ position="188.97638,188.97638"
+ orientation="0,1"
+ id="guide1099"
+ inkscape:locked="false"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ <sodipodi:guide
+ position="188.97638,188.97638"
+ orientation="1,0"
+ id="guide1101"
+ inkscape:locked="false"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-167.28122,-322.85977)">
+ <rect
+ style="opacity:1;fill:none;fill-opacity:1;stroke:#c8c8c8;stroke-width:17.85590553;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
+ id="rect817"
+ width="360.09683"
+ height="220.61481"
+ x="176.20917"
+ y="401.52875" />
+ <path
+ style="fill:none;stroke:#c8c8c8;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 364.4772,427.97815 v 173.777"
+ id="path833"
+ inkscape:connector-curvature="0" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:52.13865662px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.30346632"
+ x="195.92052"
+ y="571.07178"
+ id="text837"><tspan
+ sodipodi:role="line"
+ id="tspan835"
+ x="195.92052"
+ y="571.07178"
+ style="font-size:173.79553223px;fill:#ffffff;fill-opacity:1;stroke-width:1.30346632"><tspan
+ style="fill:#e6e6e6;fill-opacity:1;stroke-width:1.30346632"
+ id="tspan843">fo</tspan><tspan
+ style="fill:#828282;fill-opacity:1;stroke-width:1.30346632"
+ id="tspan841">o</tspan></tspan></text>
+ </g>
+</svg>
diff --git a/tagit/assets/icons/scalable/filter/go_back.svg b/tagit/assets/icons/scalable/filter/go_back.svg
new file mode 100644
index 0000000..a972c87
--- /dev/null
+++ b/tagit/assets/icons/scalable/filter/go_back.svg
@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="100mm"
+ height="100mm"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)"
+ sodipodi:docname="go_back.svg"
+ inkscape:export-filename="../../kivy/filter/go_back.png"
+ inkscape:export-xdpi="7.6199999"
+ inkscape:export-ydpi="7.6199999">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="2.8"
+ inkscape:cx="265.05952"
+ inkscape:cy="147.00905"
+ inkscape:document-units="mm"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:snap-global="true"
+ inkscape:snap-bbox="true"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:window-width="1920"
+ inkscape:window-height="1031"
+ inkscape:window-x="0"
+ inkscape:window-y="25"
+ inkscape:window-maximized="1"
+ inkscape:pagecheckerboard="true"
+ units="mm"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:snap-bbox-midpoints="true"
+ inkscape:object-paths="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:snap-midpoints="true"
+ inkscape:snap-object-midpoints="true"
+ inkscape:snap-center="true"
+ inkscape:snap-text-baseline="true"
+ inkscape:snap-page="true"
+ inkscape:lockguides="false">
+ <sodipodi:guide
+ orientation="0,1"
+ position="13.637059,643.40404"
+ id="guide3788"
+ inkscape:locked="false" />
+ <sodipodi:guide
+ position="188.97638,188.97638"
+ orientation="0,1"
+ id="guide1099"
+ inkscape:locked="false"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ <sodipodi:guide
+ position="188.97638,188.97638"
+ orientation="1,0"
+ id="guide1101"
+ inkscape:locked="false"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ <sodipodi:guide
+ position="233.588,370"
+ orientation="1,0"
+ id="guide1107"
+ inkscape:locked="false"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ <sodipodi:guide
+ position="144.36496,311.42857"
+ orientation="1,0"
+ id="guide1109"
+ inkscape:locked="false"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ <sodipodi:guide
+ position="-77.142857,144.36496"
+ orientation="0,1"
+ id="guide1111"
+ inkscape:locked="false"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ <sodipodi:guide
+ position="5.000315,233.58779"
+ orientation="0,1"
+ id="guide1113"
+ inkscape:locked="false"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-167.28122,-322.85977)">
+ <g
+ id="g883"
+ transform="matrix(-1,0,0,1,711.07945,-5.2302858e-6)">
+ <path
+ inkscape:transform-center-y="-2.0036887e-06"
+ inkscape:transform-center-x="-64.791235"
+ d="m 543.79823,511.83615 -107.19675,61.89008 -107.19675,61.89007 0,-123.78015 0,-123.78014 107.19676,61.89007 z"
+ inkscape:randomized="0"
+ inkscape:rounded="0"
+ inkscape:flatsided="false"
+ sodipodi:arg2="1.0471976"
+ sodipodi:arg1="0"
+ sodipodi:r2="71.4645"
+ sodipodi:r1="142.929"
+ sodipodi:cy="511.83615"
+ sodipodi:cx="400.86923"
+ sodipodi:sides="3"
+ id="path2995"
+ style="fill:#c8c8c8;fill-opacity:1;stroke:none;stroke-width:1.56328595;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ sodipodi:type="star" />
+ <rect
+ y="462.98917"
+ x="165.84547"
+ height="97.693985"
+ width="231.62929"
+ id="rect4750"
+ style="opacity:1;fill:#c8c8c8;fill-opacity:1;stroke:none;stroke-width:2.08440304;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:4.16880612, 2.08440306;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
+ </g>
+ </g>
+</svg>
diff --git a/tagit/assets/icons/scalable/filter/go_forth.svg b/tagit/assets/icons/scalable/filter/go_forth.svg
new file mode 100644
index 0000000..f4a246d
--- /dev/null
+++ b/tagit/assets/icons/scalable/filter/go_forth.svg
@@ -0,0 +1,154 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="100mm"
+ height="100mm"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)"
+ sodipodi:docname="go_forth.svg"
+ inkscape:export-filename="../../kivy/filter/go_forth.png"
+ inkscape:export-xdpi="7.6199999"
+ inkscape:export-ydpi="7.6199999">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1.4"
+ inkscape:cx="-59.38416"
+ inkscape:cy="86.716973"
+ inkscape:document-units="mm"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:snap-global="true"
+ inkscape:snap-bbox="true"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:window-width="1920"
+ inkscape:window-height="1031"
+ inkscape:window-x="0"
+ inkscape:window-y="25"
+ inkscape:window-maximized="1"
+ inkscape:pagecheckerboard="true"
+ units="mm"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:snap-bbox-midpoints="true"
+ inkscape:object-paths="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:snap-midpoints="true"
+ inkscape:snap-object-midpoints="true"
+ inkscape:snap-center="true"
+ inkscape:snap-text-baseline="true"
+ inkscape:snap-page="true"
+ inkscape:lockguides="false">
+ <sodipodi:guide
+ orientation="0,1"
+ position="13.637059,643.40404"
+ id="guide3788"
+ inkscape:locked="false" />
+ <sodipodi:guide
+ position="188.97638,188.97638"
+ orientation="0,1"
+ id="guide1099"
+ inkscape:locked="false"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ <sodipodi:guide
+ position="188.97638,188.97638"
+ orientation="1,0"
+ id="guide1101"
+ inkscape:locked="false"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ <sodipodi:guide
+ position="233.588,370"
+ orientation="1,0"
+ id="guide1107"
+ inkscape:locked="false"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ <sodipodi:guide
+ position="144.36496,311.42857"
+ orientation="1,0"
+ id="guide1109"
+ inkscape:locked="false"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ <sodipodi:guide
+ position="-77.142857,144.36496"
+ orientation="0,1"
+ id="guide1111"
+ inkscape:locked="false"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ <sodipodi:guide
+ position="5.000315,233.58779"
+ orientation="0,1"
+ id="guide1113"
+ inkscape:locked="false"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-167.28122,-322.85977)">
+ <path
+ sodipodi:type="star"
+ style="fill:#c8c8c8;fill-opacity:1;stroke:none;stroke-width:1.56328595;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="path2995"
+ sodipodi:sides="3"
+ sodipodi:cx="400.86923"
+ sodipodi:cy="511.83615"
+ sodipodi:r1="142.929"
+ sodipodi:r2="71.4645"
+ sodipodi:arg1="0"
+ sodipodi:arg2="1.0471976"
+ inkscape:flatsided="false"
+ inkscape:rounded="0"
+ inkscape:randomized="0"
+ d="m 543.79823,511.83615 -107.19675,61.89008 -107.19675,61.89007 0,-123.78015 0,-123.78014 107.19676,61.89007 z"
+ inkscape:transform-center-x="-64.791235"
+ inkscape:transform-center-y="-2.0036887e-06" />
+ <rect
+ style="opacity:1;fill:#c8c8c8;fill-opacity:1;stroke:none;stroke-width:2.07793283;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:4.15586594, 2.07793297;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
+ id="rect4750"
+ width="230.19354"
+ height="97.693985"
+ x="167.28122"
+ y="462.98917" />
+ </g>
+</svg>
diff --git a/tagit/assets/icons/scalable/filter/shingles.svg b/tagit/assets/icons/scalable/filter/shingles.svg
new file mode 100644
index 0000000..a07b6ea
--- /dev/null
+++ b/tagit/assets/icons/scalable/filter/shingles.svg
@@ -0,0 +1,217 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="100mm"
+ height="100mm"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)"
+ sodipodi:docname="shingles.svg"
+ inkscape:export-filename="../../kivy/filter/shingles.png"
+ inkscape:export-xdpi="7.6199999"
+ inkscape:export-ydpi="7.6199999">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.98994949"
+ inkscape:cx="61.463033"
+ inkscape:cy="211.38181"
+ inkscape:document-units="mm"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:snap-global="true"
+ inkscape:snap-bbox="true"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:window-width="1920"
+ inkscape:window-height="1151"
+ inkscape:window-x="0"
+ inkscape:window-y="25"
+ inkscape:window-maximized="1"
+ inkscape:pagecheckerboard="true"
+ units="mm"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:snap-bbox-midpoints="true"
+ inkscape:object-paths="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:snap-midpoints="true"
+ inkscape:snap-object-midpoints="true"
+ inkscape:snap-center="true"
+ inkscape:snap-text-baseline="true"
+ inkscape:snap-page="true"
+ inkscape:lockguides="false">
+ <sodipodi:guide
+ position="188.97638,188.97638"
+ orientation="0,1"
+ id="guide1099"
+ inkscape:locked="false"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ <sodipodi:guide
+ position="188.97638,188.97638"
+ orientation="1,0"
+ id="guide1101"
+ inkscape:locked="false"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-167.28122,-322.85977)">
+ <path
+ style="opacity:1;fill:none;fill-opacity:1;stroke:#c8c8c8;stroke-width:15.81019115;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
+ d="m 762.61553,105.82518 v 80.22827 c -0.003,0.0639 -0.007,0.12764 -0.0105,0.19146 -1.8e-4,30.94779 26.90552,56.03593 60.09519,56.03559 33.18967,3.4e-4 60.09537,-25.0878 60.0952,-56.03559 l 0.52701,-80.41973 z"
+ id="rect888-6-2-0-0"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccccc" />
+ <g
+ id="g1084"
+ transform="translate(139.39556,1.9249742)">
+ <g
+ transform="matrix(1.0298281,0,0,1.0298281,-320.57448,-232.49785)"
+ id="g1010-6" />
+ </g>
+ <g
+ id="g1077"
+ transform="translate(-135.36045,-275.77165)">
+ <g
+ transform="matrix(1.0298281,0,0,1.0298281,-106.16686,-28.823189)"
+ id="g1066" />
+ </g>
+ <g
+ id="g1077-9-8"
+ transform="translate(-235.90303,68.096044)">
+ <g
+ transform="matrix(1.0298281,0,0,1.0298281,-106.16686,-28.823189)"
+ id="g1066-2-9" />
+ </g>
+ <g
+ id="g1370"
+ transform="matrix(1.2737193,0,0,1.2737193,-97.514563,-83.896439)">
+ <path
+ sodipodi:nodetypes="cccccc"
+ inkscape:connector-curvature="0"
+ id="rect888-6-2-3"
+ d="m 175.74036,319.44885 v 40.0184 c -0.003,0.0639 -0.007,0.12764 -0.0105,0.19146 -1.8e-4,30.94779 26.90552,56.03593 60.09519,56.03559 33.18967,3.4e-4 60.09537,-25.0878 60.0952,-56.03559 l 0.52701,-40.20986"
+ style="opacity:1;fill:none;fill-opacity:1;stroke:#c8c8c8;stroke-width:15.81019115;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
+ <path
+ sodipodi:nodetypes="cccccc"
+ inkscape:connector-curvature="0"
+ id="rect888-6-2-3-2"
+ d="m 296.44726,319.44885 v 40.0184 c -0.003,0.0639 -0.007,0.12764 -0.0105,0.19146 -1.8e-4,30.94779 26.90552,56.03593 60.09519,56.03559 33.18967,3.4e-4 60.09537,-25.0878 60.0952,-56.03559 l 0.52701,-40.20986"
+ style="opacity:1;fill:none;fill-opacity:1;stroke:#c8c8c8;stroke-width:15.81019115;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
+ <path
+ sodipodi:nodetypes="cccccc"
+ inkscape:connector-curvature="0"
+ id="rect888-6-2-3-9"
+ d="m 417.15416,319.44885 v 40.0184 c -0.003,0.0639 -0.007,0.12764 -0.0105,0.19146 -1.8e-4,30.94779 26.90552,56.03593 60.09519,56.03559 33.18967,3.4e-4 60.09537,-25.0878 60.09517,-56.03559 l 0.527,-40.20986"
+ style="opacity:1;fill:none;fill-opacity:1;stroke:#c8c8c8;stroke-width:15.81019115;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
+ <path
+ sodipodi:nodetypes="cccccc"
+ inkscape:connector-curvature="0"
+ id="rect888-6-2-3-3"
+ d="m 235.82505,415.6943 v 40.0184 c -0.003,0.0639 -0.007,0.12764 -0.0105,0.19146 -1.8e-4,30.94779 26.90552,56.03593 60.09519,56.03559 33.18967,3.4e-4 60.09537,-25.0878 60.0952,-56.03559 l 0.52701,-40.20986"
+ style="opacity:1;fill:none;fill-opacity:1;stroke:#c8c8c8;stroke-width:15.81019115;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
+ <path
+ sodipodi:nodetypes="cccccc"
+ inkscape:connector-curvature="0"
+ id="rect888-6-2-3-1"
+ d="m 356.53195,415.6943 v 40.0184 c -0.003,0.0639 -0.007,0.12764 -0.0105,0.19146 -1.8e-4,30.94779 26.90552,56.03593 60.09519,56.03559 33.18967,3.4e-4 60.09537,-25.0878 60.0952,-56.03559 l 0.52701,-40.20986"
+ style="opacity:1;fill:none;fill-opacity:1;stroke:#c8c8c8;stroke-width:15.81019115;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
+ <path
+ sodipodi:nodetypes="cccccc"
+ inkscape:connector-curvature="0"
+ id="rect888-6-2-3-94"
+ d="m 477.23885,415.6943 v 40.0184 c -0.003,0.0639 -0.007,0.12764 -0.0105,0.19146 -1.8e-4,30.94779 26.90552,56.03593 60.09517,56.03559 33.1897,3.4e-4 60.0954,-25.0878 60.0952,-56.03559 l 0.527,-40.20986"
+ style="opacity:1;fill:none;fill-opacity:1;stroke:#c8c8c8;stroke-width:15.81019115;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
+ <path
+ sodipodi:nodetypes="cccccc"
+ inkscape:connector-curvature="0"
+ id="rect888-6-2-3-7"
+ d="m 115.11815,415.6943 v 40.0184 c -0.003,0.0639 -0.007,0.12764 -0.0105,0.19146 -1.8e-4,30.94779 26.90552,56.03593 60.09519,56.03559 33.18967,3.4e-4 60.09537,-25.0878 60.0952,-56.03559 l 0.52701,-40.20986"
+ style="opacity:1;fill:none;fill-opacity:1;stroke:#c8c8c8;stroke-width:15.81019115;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
+ <path
+ sodipodi:nodetypes="cccccc"
+ inkscape:connector-curvature="0"
+ id="rect888-6-2-3-8"
+ d="m 175.20284,511.93975 v 40.0184 c -0.003,0.0639 -0.007,0.12764 -0.0105,0.19146 -1.8e-4,30.94779 26.90552,56.03593 60.09519,56.03559 33.18967,3.4e-4 60.09537,-25.0878 60.0952,-56.03559 l 0.52701,-40.20986"
+ style="opacity:1;fill:none;fill-opacity:1;stroke:#c8c8c8;stroke-width:15.81019115;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
+ <path
+ sodipodi:nodetypes="cccccc"
+ inkscape:connector-curvature="0"
+ id="rect888-6-2-3-4"
+ d="m 295.90974,511.93975 v 40.0184 c -0.003,0.0639 -0.007,0.12764 -0.0105,0.19146 -1.8e-4,30.94779 26.90552,56.03593 60.09519,56.03559 33.18967,3.4e-4 60.09537,-25.0878 60.0952,-56.03559 l 0.52701,-40.20986"
+ style="opacity:1;fill:none;fill-opacity:1;stroke:#c8c8c8;stroke-width:15.81019115;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
+ <path
+ sodipodi:nodetypes="cccccc"
+ inkscape:connector-curvature="0"
+ id="rect888-6-2-3-5"
+ d="m 416.35313,512.03548 v 40.0184 c -0.003,0.0639 -0.007,0.12764 -0.0105,0.19146 -1.8e-4,30.94779 26.90552,56.03593 60.09519,56.03559 33.18966,3.4e-4 60.09539,-25.0878 60.09519,-56.03559 l 0.527,-40.20986"
+ style="opacity:1;fill:none;fill-opacity:1;stroke:#c8c8c8;stroke-width:15.81019115;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
+ <path
+ sodipodi:nodetypes="cccccc"
+ inkscape:connector-curvature="0"
+ id="rect888-6-2-3-0"
+ d="m 114.58063,608.1852 v 40.0184 c -0.003,0.0639 -0.007,0.12764 -0.0105,0.19146 -1.8e-4,30.94779 26.90552,56.03593 60.09519,56.03559 33.18967,3.4e-4 60.09537,-25.0878 60.0952,-56.03559 l 0.52701,-40.20986"
+ style="opacity:1;fill:none;fill-opacity:1;stroke:#c8c8c8;stroke-width:15.81019115;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
+ <path
+ sodipodi:nodetypes="cccccc"
+ inkscape:connector-curvature="0"
+ id="rect888-6-2-3-36"
+ d="m 235.02402,608.28093 v 40.0184 c -0.003,0.0639 -0.007,0.12764 -0.0105,0.19146 -1.8e-4,30.94779 26.90553,56.03593 60.09519,56.03559 33.18967,3.4e-4 60.09538,-25.0878 60.0952,-56.03559 l 0.52701,-40.20986"
+ style="opacity:1;fill:none;fill-opacity:1;stroke:#c8c8c8;stroke-width:15.81019115;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
+ <path
+ sodipodi:nodetypes="cccccc"
+ inkscape:connector-curvature="0"
+ id="rect888-6-2-3-10"
+ d="m 355.73092,608.28093 v 40.0184 c -0.003,0.0639 -0.007,0.12764 -0.0105,0.19146 -1.8e-4,30.94779 26.90552,56.03593 60.09519,56.03559 33.18967,3.4e-4 60.09537,-25.0878 60.0952,-56.03559 l 0.52701,-40.20986"
+ style="opacity:1;fill:none;fill-opacity:1;stroke:#c8c8c8;stroke-width:15.81019115;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
+ <path
+ sodipodi:nodetypes="cccccc"
+ inkscape:connector-curvature="0"
+ id="rect888-6-2-3-6"
+ d="m 476.43782,608.28093 v 40.0184 c -0.003,0.0639 -0.007,0.12764 -0.0105,0.19146 -1.8e-4,30.94779 26.90552,56.03593 60.0952,56.03559 33.1897,3.4e-4 60.0954,-25.0878 60.0952,-56.03559 l 0.527,-40.20986"
+ style="opacity:1;fill:none;fill-opacity:1;stroke:#c8c8c8;stroke-width:15.81019115;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
+ </g>
+ <path
+ style="fill:none;stroke:#c8c8c8;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 126.32932,322.99173 H 587.5694"
+ id="path1391"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/tagit/dialogues/__init__.py b/tagit/dialogues/__init__.py
index bee5bf4..d7792cb 100644
--- a/tagit/dialogues/__init__.py
+++ b/tagit/dialogues/__init__.py
@@ -18,11 +18,11 @@ import typing
# inner-module imports
##from .spash import Splash
-#from .autoinput import AutoTextInput
+from .autoinput import AutoTextInput
#from .console import Console
#from .dir_creator import DirCreator
#from .dir_picker import DirPicker
-#from .error import Error
+from .error import Error
#from .export import Export
#from .file_creator import FileCreator
#from .file_picker import FilePicker
@@ -32,8 +32,8 @@ import typing
#from .path_picker import PathPicker
#from .progress import Progress
#from .project import Project
-#from .simple_input import SimpleInput
-#from .stoken import TokenEdit
+from .simple_input import SimpleInput
+from .stoken import TokenEdit
#from .yesno import YesNo
# exports
@@ -41,7 +41,7 @@ __all__: typing.Sequence[str] = (
#'Console',
#'DirCreator',
#'DirPicker',
- #'Error',
+ 'Error',
#'Export',
#'FileCreator',
#'FilePicker',
@@ -51,8 +51,8 @@ __all__: typing.Sequence[str] = (
#'PathPicker',
#'Progress',
#'Project',
- #'SimpleInput',
- #'TokenEdit',
+ 'SimpleInput',
+ 'TokenEdit',
#'YesNo',
)
diff --git a/tagit/dialogues/autoinput.py b/tagit/dialogues/autoinput.py
new file mode 100644
index 0000000..a036ed4
--- /dev/null
+++ b/tagit/dialogues/autoinput.py
@@ -0,0 +1,73 @@
+"""This is a simple example of how to use suggestion text.
+
+In this example you setup a word_list at the begining. In this case
+'the the quick brown fox jumps over the lazy old dog'. This list along
+with any new word written word in the textinput is available as a
+suggestion when you are typing. You can press tab to auto complete the text.
+
+Based on & thanks to akshayaurora:
+ https://gist.github.com/akshayaurora/fa5a68980af585e355668e5adce5f98b
+
+Part of the tagit module.
+A copy of the license is provided with the project.
+Modifications authored by: Matthias Baumgartner, 2022
+"""
+# standard imports
+from bisect import bisect
+
+# kivy imports
+from kivy.uix.textinput import TextInput
+import kivy.properties as kp
+
+# exports
+__all__ = ('AutoTextInput', )
+
+
+## code ##
+
+class AutoTextInput(TextInput):
+
+ sep = kp.StringProperty(',')
+ suffix = kp.StringProperty(' ')
+ vocabulary = kp.ListProperty()
+
+ def on_suggestion_text(self, wx, value):
+ if not value:
+ return
+
+ super(AutoTextInput, self).on_suggestion_text(wx, value)
+
+ def keyboard_on_key_down(self, window, keycode, text, modifiers):
+ if self.suggestion_text and keycode[1] == 'tab': # complete suggestion_text
+ self.insert_text(self.suggestion_text + self.sep + self.suffix)
+ self.suggestion_text = ''
+ return True
+ return super(AutoTextInput, self).keyboard_on_key_down(window, keycode, text, modifiers)
+
+ def on_text(self, wx, value):
+ # include all current text from textinput into the word list
+ # the kind of behavior sublime text has
+
+ # what's on the current line
+ temp = value[:value.rfind(self.sep)].split(self.sep)
+ temp = [s.strip() for s in temp]
+ # combine with static vocabulary
+ wordlist = sorted(set(self.vocabulary + temp))
+
+ # get prefix
+ prefix = value[value.rfind(self.sep)+1:].strip()
+ if not prefix:
+ return
+
+ # binary search on (sorted) wordlist
+ pos = bisect(wordlist, prefix)
+
+ # check if matching string found
+ if pos == len(wordlist) or not wordlist[pos].startswith(prefix):
+ self.suggestion_text = ''
+ return
+
+ # fetch suffix from wordlist
+ self.suggestion_text = wordlist[pos][len(prefix):]
+
+## EOF ##
diff --git a/tagit/dialogues/dialogue.kv b/tagit/dialogues/dialogue.kv
new file mode 100644
index 0000000..e23f0db
--- /dev/null
+++ b/tagit/dialogues/dialogue.kv
@@ -0,0 +1,114 @@
+#:import get_root tagit.utils.get_root
+# FIXME: remove need for get_root
+
+<-Dialogue>:
+ auto_dismiss: True
+ ok_on_enter: True
+
+<DialogueContentBase>:
+
+ orientation: 'vertical'
+ padding: '12dp'
+ size_hint: 0.66, None
+ height: self.minimum_height
+
+ canvas:
+ # mask main window
+ Color:
+ rgba: 0,0,0, 0.7 * self.parent._anim_alpha
+ Rectangle:
+ size: self.parent._window.size if self.parent._window else (0, 0)
+
+ # solid background color
+ Color:
+ rgb: 1, 1, 1
+ BorderImage:
+ source: self.parent.background
+ border: self.parent.border
+ pos: self.pos
+ size: self.size
+
+
+<DialogueContentNoTitle@DialogueContentBase>:
+ # nothing to do
+
+<DialogueContentTitle@DialogueContentBase>:
+ title: ''
+ title_color: 1,1,1,1
+
+ Label:
+ text: root.title
+ size_hint_y: None
+ height: self.texture_size[1] + dp(16)
+ text_size: self.width - dp(16), None
+ font_size: '16sp'
+ color: root.title_color
+ bold: True
+ halign: 'center'
+ valing: 'middle'
+
+ canvas.before:
+ # Background
+ Color:
+ rgb: 0.2, 0.2, 0.2
+ Rectangle:
+ size: self.size
+ pos: self.pos
+
+ # top border
+ #Color:
+ # rgb: 0.5, 0.5, 0.5
+ #Line:
+ # points: self.x, self.y + self.height, self.x + self.width, self.y + self.height
+ # width: 2
+
+ # bottom border
+ #Color:
+ # rgb: 0.5, 0.5, 0.5
+ #Line:
+ # points: self.x, self.y, self.x + self.width, self.y
+ # width: 2
+
+ # small space
+ Label:
+ size_hint_y: None
+ height: 12
+
+
+<DialogueButtons>:
+ orientation: 'vertical'
+ size_hint_y: None
+ height: dp(48+8)
+
+ # small space
+ Label:
+ size_hint_y: None
+ height: dp(8)
+
+ # here come the buttons
+
+
+<DialogueButtons_One@DialogueButtons>:
+ ok_text: 'OK'
+
+ Button:
+ text: root.ok_text
+ on_press: get_root(self).ok()
+
+
+<DialogueButtons_Two@DialogueButtons>:
+ cancel_text: 'Cancel'
+ ok_text: 'OK'
+ ok_enabled: True
+
+ BoxLayout:
+ orientation: 'horizontal'
+ Button:
+ text: root.cancel_text
+ on_press: get_root(self).cancel()
+ Button:
+ text: root.ok_text
+ on_press: get_root(self).ok()
+ disabled: not root.ok_enabled
+
+## EOF ##
diff --git a/tagit/dialogues/dialogue.py b/tagit/dialogues/dialogue.py
new file mode 100644
index 0000000..1aa0e9a
--- /dev/null
+++ b/tagit/dialogues/dialogue.py
@@ -0,0 +1,108 @@
+"""Popup dialogue.
+
+Rougly based on code from https://gist.github.com/kived/742397a80d61e6be225a
+by Ryan Pessa. The license is provided in the source folder.
+
+Part of the tagit module.
+A copy of the license is provided with the project.
+Modifications authored by: Matthias Baumgartner, 2022
+"""
+# standard imports
+import os
+
+# kivy imports
+from kivy.lang import Builder
+from kivy.uix.boxlayout import BoxLayout
+from kivy.uix.popup import Popup
+import kivy.properties as kp
+
+# exports
+__all__ = ('Dialogue', )
+
+
+## code ##
+
+# Load kv
+Builder.load_file(os.path.join(os.path.dirname(__file__), 'dialogue.kv'))
+
+# classes
+class Dialogue(Popup):
+ """Popup dialogue base class.
+
+ Use like below:
+
+ >>> dlg = Dialogue()
+ >>> dlg.bind(on_ok=....)
+ >>> dlg.open()
+
+ """
+
+ ok_on_enter = kp.BooleanProperty()
+
+ __events__ = ('on_ok', 'on_cancel')
+
+ def __init__(self, *args, **kwargs):
+ super(Dialogue, self).__init__(*args, **kwargs)
+ from kivy.core.window import Window
+ # assumes that the first widget created controls the keyboard
+ #Window.children[-1].request_exclusive_keyboard()
+ # Alternatively, you can bind a function (self._on_keyboard) to on_keyboard
+ # which returns True. This stops the event from being processed by the main
+ # window.
+ # However, this still does not keep the 'enter' from 'on_text_validate' from
+ # being processed by the main window.
+ Window.bind(on_keyboard=self._on_keyboard)
+ # By binding to on_key_down, the <enter> key can trigger the ok action.
+ # This also prevents the enter event to be processed by the main window,
+ # unlike the 'on_text_validate' of TextInput.
+ Window.bind(on_key_down=self._key_down)
+
+ def _on_keyboard(self, *args, **kwargs):
+ # block events from processing in the main window
+ return True
+
+ def _key_down(self, instance, key, scancode, codepoint, modifiers):
+ if key == 13 and self.ok_on_enter:
+ self.ok()
+ return True
+ # must not stop other events such that ctrl up/down reach the browser
+
+ def ok(self):
+ """User pressed the OK button."""
+ self.dispatch('on_ok')
+ self.dismiss()
+
+ def cancel(self):
+ """User pressed the Cancel button."""
+ self.dispatch('on_cancel')
+ self.dismiss()
+
+ def on_dismiss(self):
+ from kivy.core.window import Window
+ # assumes that the first widget created controls the keyboard
+ #Window.children[-1].release_exclusive_keyboard()
+ Window.unbind(on_keyboard=self._on_keyboard)
+ Window.unbind(on_key_down=self._key_down)
+ super(Dialogue, self).on_dismiss()
+
+ def on_ok(self):
+ """Event prototype."""
+ pass
+
+ def on_cancel(self):
+ """Event prototype."""
+ pass
+
+# helper classes
+
+# content bases
+class DialogueContentBase(BoxLayout): pass
+class DialogueContentTitle(DialogueContentBase): pass
+class DialogueContentNoTitle(DialogueContentBase): pass
+
+# buttons
+class DialogueButtons(BoxLayout): pass
+class DialogueButtons_One(DialogueButtons): pass
+class DialogueButtons_Two(DialogueButtons): pass
+
+## EOF ##
diff --git a/tagit/dialogues/error.py b/tagit/dialogues/error.py
new file mode 100644
index 0000000..d93f853
--- /dev/null
+++ b/tagit/dialogues/error.py
@@ -0,0 +1,45 @@
+"""Dialogue to show an error message.
+
+Part of the tagit module.
+A copy of the license is provided with the project.
+Author: Matthias Baumgartner, 2022
+"""
+# kivy imports
+from kivy.lang import Builder
+import kivy.properties as kp
+
+# inner-module imports
+from .dialogue import Dialogue
+
+# exports
+__all__ = ('Error', )
+
+
+## code ##
+
+# load kv
+Builder.load_string('''
+<Error>:
+ text: ''
+ ok_on_enter: False
+
+ DialogueContentNoTitle:
+
+ Label:
+ markup: True
+ text: root.text
+ size_hint_y: None
+ color: 1, 0, 0, 1
+ height: self.texture_size[1] + dp(16)
+ text_size: self.width - dp(16), None
+ halign: 'center'
+
+ DialogueButtons_One:
+''')
+
+# classes
+class Error(Dialogue):
+ """Error message."""
+ text = kp.StringProperty('')
+
+## EOF ##
diff --git a/tagit/dialogues/license.t b/tagit/dialogues/license.t
new file mode 100644
index 0000000..bbd2830
--- /dev/null
+++ b/tagit/dialogues/license.t
@@ -0,0 +1,33 @@
+
+The dialogues are based on the following code:
+
+https://gist.github.com/kived/742397a80d61e6be225a
+
+It ships with license, provided below:
+
+>>> The following license shall apply to all Public Gists owned by account. It
+>>> shall never apply to any Secret Gists, for which no license of any sort is
+>>> granted.
+>>>
+>>> Copyright (c) 2015- Ryan Pessa
+>>>
+>>> Permission is hereby granted, free of charge, to any person obtaining a copy
+>>> of this software and associated documentation files (the "Software"), to deal
+>>> in the Software without restriction, including without limitation the rights
+>>> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+>>> copies of the Software, and to permit persons to whom the Software is
+>>> furnished to do so, subject to the following conditions:
+>>>
+>>> The above copyright notice and this permission notice shall be included in
+>>> all copies or substantial portions of the Software.
+>>>
+>>> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+>>> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+>>> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+>>> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+>>> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+>>> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+>>> THE SOFTWARE.
+
+Code modification are subject to the license of the tagit software.
+
diff --git a/tagit/dialogues/simple_input.kv b/tagit/dialogues/simple_input.kv
new file mode 100644
index 0000000..b7deb9c
--- /dev/null
+++ b/tagit/dialogues/simple_input.kv
@@ -0,0 +1,30 @@
+
+#:import AutoTextInput tagit.dialogues
+
+<SimpleInput>:
+ text: ''
+ ok_on_enter: True
+ cancel_on_defocus: True
+
+ DialogueContentNoTitle:
+
+ #AutoTextInput:
+ TextInput:
+ vocabulary: root.suggestions
+ sep: root.suggestion_sep
+ suffix: root.suggestion_suffix
+ focus: True
+ text: root.text
+ size_hint_y: None
+ multiline: False
+ height: self.minimum_height
+ text_size: self.width - dp(16), None
+ halign: 'center'
+
+ on_text: root.text = self.text
+ on_focus: root.on_text_focus(*args)
+ #on_text_validate: root.ok() # handled via the ok_on_enter mechanism
+
+ DialogueButtons_Two:
+
+## EOF ##
diff --git a/tagit/dialogues/simple_input.py b/tagit/dialogues/simple_input.py
new file mode 100644
index 0000000..d7cc69f
--- /dev/null
+++ b/tagit/dialogues/simple_input.py
@@ -0,0 +1,55 @@
+"""Dialogue with a single-line text input field.
+
+Part of the tagit module.
+A copy of the license is provided with the project.
+Author: Matthias Baumgartner, 2022
+"""
+# standard imports
+import os
+
+# kivy imports
+from kivy.lang import Builder
+import kivy.properties as kp
+
+# inner-module imports
+from .dialogue import Dialogue
+
+# exports
+__all__ = ('SimpleInput', )
+
+
+## code ##
+
+# load kv
+Builder.load_file(os.path.join(os.path.dirname(__file__), 'simple_input.kv'))
+
+# classes
+class SimpleInput(Dialogue):
+ """Dialogue with a single-line text input field.
+
+ Pass the default text as **text**.
+
+ >>> SimpleInput(text='Hello world').open()
+
+ In case of touch events, they need to be inhibited to change the focus.
+
+ >>> FocusBehavior.ignored_touch.append(touch)
+
+ """
+
+ # Defocus problem:
+ # Buttons defocus when on_press, but on_release is ok.
+ # Touch events must be blocked via FocusBehavior
+
+ text = kp.StringProperty('')
+ cancel_on_defocus = kp.BooleanProperty(True)
+ suggestions = kp.ListProperty()
+ suggestion_sep = kp.StringProperty(',')
+ suggestion_suffix = kp.StringProperty(' ')
+
+
+ def on_text_focus(self, instance, focus):
+ if not focus and self.cancel_on_defocus:
+ self.dismiss()
+
+## EOF ##
diff --git a/tagit/dialogues/stoken.py b/tagit/dialogues/stoken.py
new file mode 100644
index 0000000..6e5427a
--- /dev/null
+++ b/tagit/dialogues/stoken.py
@@ -0,0 +1,40 @@
+"""Search token editor
+
+Part of the tagit module.
+A copy of the license is provided with the project.
+Author: Matthias Baumgartner, 2022
+
+"""
+# kivy imports
+from kivy.lang import Builder
+import kivy.properties as kp
+
+# inner-module imports
+from .simple_input import SimpleInput
+
+# exports
+__all__ = ('TokenEdit', )
+
+
+## code ##
+
+# Load kv
+Builder.load_string('''
+#:import AutoTextInput tagit.dialogues
+
+<TokenEdit>:
+ text: ''
+ ok_on_enter: True
+ cancel_on_defocus: True
+''')
+
+# classes
+class TokenEdit(SimpleInput):
+ """Search token editor
+ """
+ # TODO: Currently this is no different than SimpleInput.
+ # It should be extend to specify the type and getting help
+ # with editing ranges and alternative selection.
+ pass
+
+## EOF ##
diff --git a/tagit/parsing/__init__.py b/tagit/parsing/__init__.py
index 1c431a4..0070bf9 100644
--- a/tagit/parsing/__init__.py
+++ b/tagit/parsing/__init__.py
@@ -6,14 +6,14 @@ Author: Matthias Baumgartner, 2022
"""
# inner-module imports
from .datefmt import parse_datetime
-from .search import ast_from_string
-from .sort import sort_from_string
+from .filter import Filter
+from .sort import Sort
# exports
__all__ = (
- 'ast_from_string',
+ 'Filter',
+ 'Sort',
'parse_datetime',
- 'sort_from_string',
)
## EOF ##
diff --git a/tagit/parsing/search.py b/tagit/parsing/filter.py
index 10d0e7c..ea8df51 100644
--- a/tagit/parsing/search.py
+++ b/tagit/parsing/filter.py
@@ -1,7 +1,7 @@
"""User-specified search query parsing.
>>> q = "has mime / tag in (november, october) / ! Apfel / time < 10.10.2004 / iso in (100, 200)"
->>> ast = ast_from_string(q)
+>>> ast = filter_from_string(q)
Part of the tagit module.
A copy of the license is provided with the project.
@@ -14,37 +14,29 @@ from datetime import datetime
from pyparsing import CaselessKeyword, Combine, Group, Optional, Or, Word, delimitedList, nums, oneOf, ParseException, Literal, QuotedString, alphanums, alphas8bit, punc8bit
# tagit imports
-from tagit.utils import errors, ttime
+from tagit.utils import bsfs, errors, ns, ttime
+from tagit.utils.bsfs import ast
# inner-module imports
-from . import datefmt
-
-# exports
-__all__ = (
- 'ast_from_string',
- )
+from .datefmt import parse_datetime
# constants
SEARCH_DELIM = '/'
VALUE_DELIM = ','
-DEFAULT_PREDICATE = 'tag'
+# exports
+__all__ = (
+ 'Filter',
+ )
-## code ##
-class SearchParser():
+## code ##
- # valid predicates per type
- _PREDICATES_CATEGORICAL = None
- _PREDICATES_CONTINUOUS = None
- _PREDICATES_DATETIME = None
+class Filter():
# parsers
- _CATEGORICAL = None
- _CONTINUOUS = None
- _EXISTENCE = None
+ _DATETIME_PREDICATES = None
_QUERY = None
- _TAG = None
def __init__(self, schema: bsfs.schema.Schema):
self.schema = schema
@@ -61,9 +53,6 @@ class SearchParser():
def build_parser(self):
"""
"""
- # The *predicate* argument is for compatibility with predicate listener.
- # It's not actually used here.
-
# valid predicates per type, as supplied by tagit.library
# FIXME:
# * range / type constraints
@@ -79,9 +68,21 @@ class SearchParser():
> Target: Entity (allow others?) -> rfds:domain
> Require: searchable as specified in backend AND user-searchable as specified in frontend
"""
- self._PREDICATES_CATEGORICAL = self.schema.predicates(searchable=True, range=self.schema.tm.categorical) # FIXME!
- self._PREDICATES_CONTINUOUS = self.schema.predicates(searchable=True, range=self.schema.tm.numerical) # FIXME!
- self._PREDICATES_DATETIME = self.schema.predicates(searchable=True, range=self.schema.tm.datetime) # FIXME!
+ # all relevant predicates
+ predicates = {pred for pred in self.schema.predicates() if pred.domain <= self.schema.node(ns.bsfs.Entity)}
+ # filter through accept/reject lists
+ ... # FIXME
+ # shortcuts
+ self._abb2uri = {pred.uri.fragment: pred.uri for pred in predicates} # FIXME: tie-breaking for duplicates
+ self._uri2abb = {uri: fragment for fragment, uri in self._abb2uri.items()}
+ # all predicates
+ _PREDICATES = {self._uri2abb[pred.uri] for pred in predicates}
+ # numeric predicates
+ _PREDICATES_NUMERIC = {self._uri2abb[pred.uri] for pred in predicates if isinstance(pred.range, bsfs.schema.Literal) and pred.range <= self.schema.literal(ns.bsfs.Number)} # FIXME: type check might become unnecessary
+ # datetime predicates
+ self._DATETIME_PREDICATES = {pred.uri for pred in predicates if isinstance(pred.range, bsfs.schema.Literal) and pred.range <= self.schema.literal(ns.bsfs.Time)} # FIXME: type check might become unnecessary
+ _PREDICATES_DATETIME = {self._uri2abb[pred] for pred in self._DATETIME_PREDICATES}
+
# terminal symbols
number = Group(Optional(oneOf('- +')) \
@@ -93,11 +94,11 @@ class SearchParser():
# FIXME: Non-ascii characters
# predicates
- predicate = Or([CaselessKeyword(p) for p in self._PREDICATES_CATEGORICAL]).setResultsName(
+ predicate = Or([CaselessKeyword(p) for p in _PREDICATES]).setResultsName(
'predicate')
- date_predicate = Or([CaselessKeyword(p) for p in self._PREDICATES_DATETIME]).setResultsName(
+ date_predicate = Or([CaselessKeyword(p) for p in _PREDICATES_DATETIME]).setResultsName(
'predicate')
- num_predicate = Or([CaselessKeyword(p) for p in self._PREDICATES_CONTINUOUS]).setResultsName(
+ num_predicate = Or([CaselessKeyword(p) for p in _PREDICATES_NUMERIC]).setResultsName(
'predicate')
# existence
@@ -106,7 +107,7 @@ class SearchParser():
PREDICATE := [predicate]
"""
op = (CaselessKeyword('has') ^ CaselessKeyword('has no') ^ CaselessKeyword('has not')).setResultsName('op')
- self._EXISTENCE = Group(op + predicate).setResultsName('existence')
+ _EXISTENCE = Group(op + predicate).setResultsName('existence')
# continuous
@@ -127,7 +128,7 @@ class SearchParser():
bclose = oneOf(') ] [').setResultsName('bclose')
bopen = oneOf('( [ ]').setResultsName('bopen')
op = Or([':', '=', 'in']).setResultsName('op')
- datefmt = datefmt.parse_datetime.DATETIME
+ datefmt = parse_datetime.DATETIME
rngn = num_predicate + op + bopen + number('lo') + rsepn + number('hi') + bclose ^ \
num_predicate + op + bopen + rsepn + number('hi') + bclose ^ \
num_predicate + op + bopen + number('lo') + rsepn + bclose
@@ -143,7 +144,7 @@ class SearchParser():
datefmt('vleft') + cmp('cleft') + date_predicate ^ \
datefmt('vleft') + cmp('cleft') + date_predicate + cmp('cright') + datefmt('vright')
# combined
- self._CONTINUOUS = Group(
+ _CONTINUOUS = Group(
Group(eqn).setResultsName('eq') ^
Group(eqd).setResultsName('eq') ^
Group(rngn).setResultsName('range') ^ \
@@ -161,7 +162,7 @@ class SearchParser():
"""
op = (CaselessKeyword('in') ^ CaselessKeyword('not in') ^ ':' ^ '=' ^ '!=' ^ '~' ^ '!~').setResultsName('op')
value = delimitedList(words, delim=VALUE_DELIM).setResultsName('value')
- self._CATEGORICAL = Group(predicate + op + ('(' + value + ')' | value) ).setResultsName('categorical')
+ _CATEGORICAL = Group(predicate + op + ('(' + value + ')' | value) ).setResultsName('categorical')
# tag shortcuts
@@ -173,35 +174,17 @@ class SearchParser():
"""
op = oneOf('! ~ !~').setResultsName('op')
value = delimitedList(words, delim=VALUE_DELIM).setResultsName('value')
- self._TAG = Group(Optional(op) + '(' + value + ')' ^ Optional(op) + value).setResultsName('tag')
+ _TAG = Group(Optional(op) + '(' + value + ')' ^ Optional(op) + value).setResultsName('tag')
# overall query
"""
QUERY := QUERY / QUERY | EXPR
"""
- self._QUERY = delimitedList(self._EXISTENCE | self._CONTINUOUS | self._CATEGORICAL | self._TAG, delim=SEARCH_DELIM)
+ self._QUERY = delimitedList(_EXISTENCE | _CONTINUOUS | _CATEGORICAL | _TAG, delim=SEARCH_DELIM)
return self
- def __del__(self):
- if self._QUERY is not None: # remove listener
- try:
- self.predicates.ignore(self.build_parser)
- except ImportError:
- # The import fails if python is shutting down.
- # In that case, the ignore becomes unnecessary anyway.
- pass
-
def __call__(self, search):
- # FIXME: mb/port/parsing
- #if self._QUERY is None:
- # # parsers were not initialized yet
- # self.build_parser()
- # # attach listener to receive future updates
- # self.predicates.listen(self.build_parser)
- # # FIXME: Additional filters would be handy
- # #self.predicates.listen(self.build_parser, self.predicates.scope.library)
-
try:
parsed = self._QUERY.parseString(search, parseAll=True)
except ParseException as e:
@@ -211,61 +194,58 @@ class SearchParser():
tokens = []
for exp in parsed:
if exp.getName() == 'existence':
+ pred = self._abb2uri[exp.predicate.lower()]
if 'op' not in exp: # prevented by grammar
raise errors.ParserError('Missing operator', exp)
elif exp.op == 'has':
- cond = ast.Existence()
+ tok = ast.filter.Has(pred)
elif exp.op in ('has no', 'has not'):
- cond = ast.Inexistence()
+ tok = ast.filter.Not(ast.filter.Has(pred))
else: # prevented by grammar
raise errors.ParserError('Invalid operator ({})'.format(exp.op), exp)
-
- tokens.append(
- ast.Token(exp.predicate.lower(), cond))
+ tokens.append(tok)
elif exp.getName() == 'categorical':
+ pred = self._abb2uri[exp.predicate.lower()]
+ approx = False
values = [s.strip() for s in exp.value]
if 'op' not in exp: # prevented by grammar
raise errors.ParserError('Missing operator', exp)
- elif exp.op in (':', '=', 'in'):
- cond = ast.SetInclude(values)
- elif exp.op in ('!=', 'not in'):
- cond = ast.SetExclude(values)
- elif exp.op == '~':
- cond = ast.SetInclude(values, approximate=True)
- elif exp.op == '!~':
- cond = ast.SetExclude(values, approximate=True)
+ if exp.op in ('~' '!~'):
+ approx = True
+ if exp.op in (':', '=', '~', 'in'):
+ tok = ast.filter.Any(pred, ast.filter.Includes(*values, approx=approx))
+ elif exp.op in ('!=', '!~', 'not in'):
+ tok = ast.filter.All(pred, ast.filter.Excludes(*values, approx=approx))
else: # prevented by grammar
raise errors.ParserError('Invalid operator ({})'.format(exp.op), exp)
-
- tokens.append(
- ast.Token(exp.predicate.lower(), cond))
+ tokens.append(tok)
elif exp.getName() == 'tag':
values = [s.strip() for s in exp.value]
if 'op' not in exp:
- cond = ast.SetInclude(values)
+ outer = ast.filter.Any
+ cond = ast.filter.Includes(*values)
elif exp.op == '~':
- cond = ast.SetInclude(values, approximate=True)
+ outer = ast.filter.Any
+ cond = ast.filter.Includes(*values, approx=True)
elif exp.op == '!':
- cond = ast.SetExclude(values)
+ outer = ast.filter.All
+ cond = ast.filter.Excludes(*values)
elif exp.op == '!~':
- cond = ast.SetExclude(values, approximate=True)
+ outer = ast.filter.All
+ cond = ast.filter.Excludes(*values, approx=True)
else: # prevented by grammar
raise errors.ParserError('Invalid operator ({})'.format(exp.op), exp)
+ tokens.append(outer(ns.bse.tag, ast.filter.Any(ns.bst.label, cond)))
- tokens.append(
- ast.Token(DEFAULT_PREDICATE, cond))
-
- elif exp.getName() == 'continuous':
-
+ elif exp.getName() == 'continuous': # FIXME: simplify and adapt bsfs.query.ast.filter.Between accordingly!
lo, hi = None, None
lo_inc, hi_inc = False, False
predicate = None
-
if 'eq' in exp:
# equation style
- predicate = exp.eq.predicate.lower()
+ predicate = self._abb2uri[exp.eq.predicate.lower()]
if ('>' in exp.eq.cleft and '<' in exp.eq.cright) or \
('<' in exp.eq.cleft and '>' in exp.eq.cright) or \
@@ -294,7 +274,7 @@ class SearchParser():
lo_inc = hi_inc = True
elif 'range' in exp: # value in [lo:hi]
- predicate = exp.range.predicate.lower()
+ predicate = self._abb2uri[exp.range.predicate.lower()]
if 'lo' in exp.range:
lo = exp.range.lo
@@ -307,7 +287,7 @@ class SearchParser():
raise errors.ParserError('Expression is neither a range nor an equation', exp)
# interpret values
- if predicate in set([p.lower() for p in self._PREDICATES_DATETIME]):
+ if predicate in self._DATETIME_PREDICATES:
# turn into datetime
lo, lfmt = datefmt.guess_datetime(lo) if lo is not None else (None, None)
@@ -357,7 +337,8 @@ class SearchParser():
raise errors.ParserError('Lower bound must not exceed upper bound', (lo, hi))
tokens.append(
- ast.Token(predicate, ast.TimeRange(lo, hi, lo_inc, hi_inc)))
+ ast.filter.Any(predicate,
+ ast.filter.Between(lo, hi, not lo_inc, not hi_inc)))
else: # date specification
# Check consistency
@@ -368,7 +349,8 @@ class SearchParser():
raise errors.ParserError('Lower bound must not exceed upper bound', (lo, hi))
tokens.append(
- ast.Token(predicate, ast.Datetime(lo, hi, lo_inc, hi_inc)))
+ ast.filter.Any(predicate,
+ ast.filter.Between(lo, hi, not lo_inc, not hi_inc)))
else:
# number predicate
@@ -379,27 +361,14 @@ class SearchParser():
if not (lo < hi or (lo == hi and lo_inc and hi_inc)):
raise errors.ParserError('Lower bound must not exceed upper bound', (lo, hi))
+ # FIXME: mb/port: Three times the same code... optimize
tokens.append(
- ast.Token(predicate, ast.Continuous(lo, hi, lo_inc, hi_inc)))
+ ast.filter.Any(predicate,
+ ast.filter.Between(lo, hi, not lo_inc, not hi_inc)))
else: # prevented by grammar
raise errors.ParserError('Invalid expression', exp)
- return ast.AND(tokens)
-
-
-
-"""Default SearchParser instance.
-
-To produce an ast, call
-
->>> ast_from_string(search)
-
-Convenience shortcut for
-
->>> SearchParser().parse(search)
-
-"""
-ast_from_string = SearchParser(predicates)
+ return ast.filter.And(tokens)
## EOF ##
diff --git a/tagit/parsing/sort.py b/tagit/parsing/sort.py
index 8950613..75fa36c 100644
--- a/tagit/parsing/sort.py
+++ b/tagit/parsing/sort.py
@@ -12,13 +12,13 @@ from tagit.utils import errors, Struct
# exports
__all__ = (
- 'sort_from_string',
+ 'Sort',
)
## code ##
-class SortParser():
+class Sort():
"""Sort parser.
A sort string can be as simple as a predicate, but also allows
@@ -176,17 +176,4 @@ class SortParser():
else:
return ast.Order(*tokens)
-"""Default SortParser instance.
-
-To produce an ast, call
-
->>> sort_from_string(sort)
-
-Convenience shortcut for
-
->>> SortParser().parse(sort)
-
-"""
-sort_from_string = SortParser(sortkeys)
-
## EOF ##
diff --git a/tagit/utils/__init__.py b/tagit/utils/__init__.py
index 3f09078..16dcd4d 100644
--- a/tagit/utils/__init__.py
+++ b/tagit/utils/__init__.py
@@ -9,6 +9,7 @@ import typing
# inner-module imports
from . import bsfs
+from . import namespaces as ns
from . import time as ttime
from .frame import Frame
from .shared import * # FIXME: port properly
diff --git a/tagit/utils/bsfs.py b/tagit/utils/bsfs.py
index 0ab90a9..d80efe0 100644
--- a/tagit/utils/bsfs.py
+++ b/tagit/utils/bsfs.py
@@ -8,8 +8,16 @@ Author: Matthias Baumgartner, 2022
import typing
# bsfs imports
+from bsfs import schema, Open
+from bsfs.query import ast
+from bsfs.namespace import Namespace
# exports
-__all__: typing.Sequence[str] = []
+__all__: typing.Sequence[str] = (
+ 'Namespace',
+ 'Open',
+ 'ast',
+ 'schema',
+ )
## EOF ##
diff --git a/tagit/utils/namespaces.py b/tagit/utils/namespaces.py
new file mode 100644
index 0000000..dd26eef
--- /dev/null
+++ b/tagit/utils/namespaces.py
@@ -0,0 +1,30 @@
+"""Default namespaces used throughout tagit.
+
+Part of the tagit module.
+A copy of the license is provided with the project.
+Author: Matthias Baumgartner, 2022
+"""
+# standard imports
+import typing
+
+# inner-module imports
+from . import bsfs as _bsfs
+
+# constants
+bse = _bsfs.Namespace('http://bsfs.ai/schema/Entity')
+bsfs = _bsfs.Namespace('http://bsfs.ai/schema', fsep='/')
+bsm = _bsfs.Namespace('http://bsfs.ai/schema/Meta')
+bst = _bsfs.Namespace('http://bsfs.ai/schema/Tag')
+xsd = _bsfs.Namespace('http://www.w3.org/2001/XMLSchema')
+
+# export
+__all__: typing.Sequence[str] = (
+ 'bse',
+ 'bsfs',
+ 'bsm',
+ 'xsd',
+ )
+
+## EOF ##
+
+
diff --git a/tagit/utils/shared.py b/tagit/utils/shared.py
index 0d496ed..b5ab421 100644
--- a/tagit/utils/shared.py
+++ b/tagit/utils/shared.py
@@ -28,6 +28,7 @@ __all__: typing.Sequence[str] = (
'is_list',
'magnitude_fmt',
'truncate_dir',
+ 'get_root',
)
@@ -140,4 +141,15 @@ def fileopen(pth):
except KeyError:
warnings.warn('Unknown platform {}'.format(platform.system()))
+
+def get_root(obj):
+ """Traverse the widget tree upwards until the root is found."""
+ while obj.parent is not None and obj.parent != obj.parent.parent:
+ if hasattr(obj, 'root') and obj.root is not None:
+ return obj.root
+
+ obj = obj.parent
+
+ return obj
+
## EOF ##
diff --git a/tagit/widgets/filter.py b/tagit/widgets/filter.py
index 0152737..332ad34 100644
--- a/tagit/widgets/filter.py
+++ b/tagit/widgets/filter.py
@@ -23,7 +23,7 @@ import kivy.properties as kp
# tagit imports
from tagit import config
#from tagit.parsing.search import ast, ast_to_string # FIXME: mb/port
-from tagit.utils import errors
+from tagit.utils import bsfs, errors
# inner-module imports
from .session import ConfigAwareMixin
@@ -108,6 +108,10 @@ class Filter(BoxLayout, ConfigAwareMixin):
## exposed methods
def get_query(self):
+ query = bsfs.ast.filter.And(self.t_head[:]) if len(self.t_head) > 0 else None
+ sort = None
+ return query, sort
+ # FIXME: mb/port.parsing
query = ast.AND(self.t_head[:]) if len(self.t_head) else None
# sort order is always set to False so that changing the sort order
# won't trigger a new query which can be very expensive. The sort
@@ -116,6 +120,8 @@ class Filter(BoxLayout, ConfigAwareMixin):
return query, sort
def abbreviate(self, token):
+ return 'T'
+ # FIXME: mb/port/parsing
if token.predicate() == 'tag':
return ','.join(list(token.condition()))
elif token.predicate() == 'entity':
@@ -162,8 +168,9 @@ class Filter(BoxLayout, ConfigAwareMixin):
if self.changed:
self.redraw()
# issue search
- if self.run_search:
- self.root.trigger('Search')
+ # FIXME: mb/port/parsing
+ #if self.run_search:
+ # self.root.trigger('Search')
def redraw(self):
self.tokens.clear_widgets()
diff --git a/tagit/widgets/session.py b/tagit/widgets/session.py
index a7c7355..ca8c595 100644
--- a/tagit/widgets/session.py
+++ b/tagit/widgets/session.py
@@ -14,6 +14,7 @@ from kivy.uix.widget import Widget
import kivy.properties as kp
# tagit imports
+from tagit import parsing
from tagit.config.loader import load_settings
#from tagit.storage.broker import Broker # FIXME: mb/port
#from tagit.storage.loader import load_broker, load_log # FIXME: mb/port
@@ -38,6 +39,9 @@ class Session(Widget):
self.cfg = cfg
self.storage = storage
self.log = log
+ # derived members
+ self.filter_from_string = parsing.Filter(self.storage.schema)
+ #self.sort_from_string = parsing.Sort(self.storage.schema) # FIXME: mb/port/parsing
def __enter__(self):
return self