pynotefile 0.11.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- notefile/__init__.py +248 -0
- notefile/__main__.py +4 -0
- notefile/cli.py +1298 -0
- notefile/find.py +183 -0
- notefile/nfyaml.py +73 -0
- notefile/notefile.py +1510 -0
- notefile/safe_eval.py +431 -0
- notefile/utils.py +236 -0
- pynotefile-0.11.3.dist-info/METADATA +250 -0
- pynotefile-0.11.3.dist-info/RECORD +13 -0
- pynotefile-0.11.3.dist-info/WHEEL +5 -0
- pynotefile-0.11.3.dist-info/entry_points.txt +2 -0
- pynotefile-0.11.3.dist-info/top_level.txt +1 -0
notefile/__init__.py
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
__version__ = "0.11.3"
|
|
2
|
+
__author__ = "Justin Winokur"
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
if sys.version_info < (3, 8):
|
|
8
|
+
# Limited by argarse's extend action
|
|
9
|
+
sys.stderr.write("ERROR: Must use Python >= 3.8\n")
|
|
10
|
+
sys.exit(1)
|
|
11
|
+
|
|
12
|
+
# Env Variables
|
|
13
|
+
HIDDEN = os.environ.get("NOTEFILE_HIDDEN", "false").strip().lower() == "true"
|
|
14
|
+
SUBDIR = os.environ.get("NOTEFILE_SUBDIR", "false").strip().lower() == "true"
|
|
15
|
+
|
|
16
|
+
DEBUG = os.environ.get("NOTEFILE_DEBUG", "false").strip().lower() == "true"
|
|
17
|
+
NOTEFIELD = os.environ.get("NOTEFILE_NOTEFIELD", "notes").strip()
|
|
18
|
+
FORMAT = os.environ.get("NOTEFILE_FORMAT", "yaml").strip().lower()
|
|
19
|
+
|
|
20
|
+
DISABLE_QUERY = os.environ.get("NOTEFILE_DISABLE_QUERY", "false").lower() == "true"
|
|
21
|
+
SAFE_QUERY = os.environ.get("NOTEFILE_SAFE_QUERY", "true").strip().lower() == "true"
|
|
22
|
+
|
|
23
|
+
# Constants
|
|
24
|
+
NOTESEXT = ".notes.yaml"
|
|
25
|
+
NOHASH = "** not computed **"
|
|
26
|
+
DT = 1 # mtime change
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def debug(*args, **kwargs):
|
|
30
|
+
"""Print a debug message when `NOTEFILE_DEBUG` is enabled."""
|
|
31
|
+
if DEBUG:
|
|
32
|
+
kwargs["file"] = sys.stderr
|
|
33
|
+
print("DEBUG:", *args, **kwargs)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def warn(*args, **kwargs):
|
|
37
|
+
"""Print a warning message to standard error."""
|
|
38
|
+
kwargs["file"] = sys.stderr
|
|
39
|
+
print("WARNING:", *args, **kwargs)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
from .find import find
|
|
43
|
+
from .notefile import Notefile, get_filenames
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def query_help(print_help=True, safe=None):
|
|
47
|
+
"""Return or print the query-language help text.
|
|
48
|
+
|
|
49
|
+
Parameters
|
|
50
|
+
----------
|
|
51
|
+
print_help:
|
|
52
|
+
When true, print the generated help text to stdout.
|
|
53
|
+
safe:
|
|
54
|
+
Override whether the safe-query or unsafe-query help variant is used.
|
|
55
|
+
|
|
56
|
+
Returns
|
|
57
|
+
-------
|
|
58
|
+
str
|
|
59
|
+
The rendered help text.
|
|
60
|
+
"""
|
|
61
|
+
if safe is None:
|
|
62
|
+
safe = SAFE_QUERY
|
|
63
|
+
|
|
64
|
+
if safe:
|
|
65
|
+
help = """\
|
|
66
|
+
Queries:
|
|
67
|
+
--------
|
|
68
|
+
Queries are single statements where the last line must evaluate to True or False.
|
|
69
|
+
They are evaluated by a restricted parser (no eval/exec).
|
|
70
|
+
|
|
71
|
+
The following variables are defined:
|
|
72
|
+
|
|
73
|
+
data Dictionary of the note itself.
|
|
74
|
+
notes == data['notes'] or data[<note_field>] if set. The note text.
|
|
75
|
+
tags == data['tags']. Set of tags (note, all lower case).
|
|
76
|
+
text Raw contents (YAML/JSON) of the note.
|
|
77
|
+
filename Path to the file being noted.
|
|
78
|
+
notefile_path Path to the notefile sidecar.
|
|
79
|
+
isdir True if the note target is a directory.
|
|
80
|
+
isfile True if the note target is a file.
|
|
81
|
+
|
|
82
|
+
And it includes the following functions:
|
|
83
|
+
|
|
84
|
+
grep performs a match against 'notes'. Respects the flags:
|
|
85
|
+
'--match-expr-case','--fixed-strings','--full-word' automatically but
|
|
86
|
+
can also be overridden with the respective keyword arguments.
|
|
87
|
+
|
|
88
|
+
g Aliased to grep
|
|
89
|
+
|
|
90
|
+
gall Essentially grep with match_any = False
|
|
91
|
+
|
|
92
|
+
gany Essentially grep with match_any = True
|
|
93
|
+
|
|
94
|
+
tany Returns True if that tag is in tags: e.g.
|
|
95
|
+
tany('tag1','tag2') <==> any(t in tags for t in ['tag1','tag2'])
|
|
96
|
+
|
|
97
|
+
tall Returns true if all args are in tags: e.g.
|
|
98
|
+
tall('tag1','tag2') <==> all(t in tags for t in ['tag1','tag2'])
|
|
99
|
+
|
|
100
|
+
t aliased to tany
|
|
101
|
+
norm_tags Normalize tags (splits commas, lowercases, strips whitespace)
|
|
102
|
+
|
|
103
|
+
It also includes the `re` module and `ss = shlex.split`. Imports are not supported.
|
|
104
|
+
Attribute access is limited to safe string and dict methods.
|
|
105
|
+
|
|
106
|
+
Additional supported features include but are not limited to:
|
|
107
|
+
|
|
108
|
+
- Safe builtins: any, all, len, set, list, tuple, sorted, min, max, sum
|
|
109
|
+
- Container literals beyond lists (tuples, sets, dicts)
|
|
110
|
+
- Comprehensions (list, set, dict, generator)
|
|
111
|
+
- Subscripts and slices (e.g., x[0], x[1:3])
|
|
112
|
+
- If-expressions (e.g., a if cond else b)
|
|
113
|
+
- Dict method access (get, keys, values, items, copy)
|
|
114
|
+
- String method allowlist (e.g., splitlines, removeprefix, removesuffix)
|
|
115
|
+
|
|
116
|
+
Queries can replace --tag and grep but grep is faster if it can be used since it
|
|
117
|
+
is accelerated by not parsing YAML unless needed.
|
|
118
|
+
|
|
119
|
+
For example, the following return the same thing:
|
|
120
|
+
|
|
121
|
+
$ notefile grep word1 word2
|
|
122
|
+
$ notefile query "grep('word1') or grep('word2')"
|
|
123
|
+
$ notefile query "grep('word1','word2')"
|
|
124
|
+
$ notefile query "grep(ss('word1 word2'))" # can use shlex.split (ss)
|
|
125
|
+
|
|
126
|
+
However, queries can be much more complex. For example:
|
|
127
|
+
|
|
128
|
+
$ notefile query "(grep('word1') or grep('word2')) and not grep('word3')"
|
|
129
|
+
|
|
130
|
+
Limited multi-line support exists. Multiple lines can be delineated by ';'.
|
|
131
|
+
However, the last line must evaluate the query. Example:
|
|
132
|
+
|
|
133
|
+
$ notefile query "tt = ['a','b','c']; all(t in tags for t in tt)"
|
|
134
|
+
|
|
135
|
+
Or even using multiple lines in the shell
|
|
136
|
+
|
|
137
|
+
$ notefile query "tt = ['a','b','c']
|
|
138
|
+
> all(t in tags for t in tt)"
|
|
139
|
+
|
|
140
|
+
Can also pass STDIN with the expression `-` to make quoting a bit less onerous
|
|
141
|
+
|
|
142
|
+
$ notefile query - <<EOF
|
|
143
|
+
> a = t('tag1') and not t('tag2')
|
|
144
|
+
> b = g('expr1') or g('expr2') or not g('expr3')
|
|
145
|
+
> a and b
|
|
146
|
+
> EOF
|
|
147
|
+
|
|
148
|
+
tany and/or tall could also be used:
|
|
149
|
+
|
|
150
|
+
$ notefile query "tall('a','b','c')"
|
|
151
|
+
|
|
152
|
+
Reminder: safe queries are restricted but not fully sandboxed; expensive regex or
|
|
153
|
+
large computations can still be costly.
|
|
154
|
+
|
|
155
|
+
Safe queries are now the default. To enable *unsafe* queries on the CLI, set
|
|
156
|
+
NOTEFILE_SAFE_QUERY=false. Or use the Note.unsafe_query(...) APIs.
|
|
157
|
+
"""
|
|
158
|
+
else:
|
|
159
|
+
help = """\
|
|
160
|
+
Queries:
|
|
161
|
+
--------
|
|
162
|
+
Queries are single statements where the last line must evaluate to True or False.
|
|
163
|
+
They are evaluated as Python (with no sandboxing or sanitizing so DO NOT EVALUATE
|
|
164
|
+
UNTRUSTED INPUT). The following variables are defined:
|
|
165
|
+
|
|
166
|
+
note Notefile object including attributes such as 'filename',
|
|
167
|
+
'destnote','hidden', etc. See notefile.Notefile documention.
|
|
168
|
+
data Dictionary of the note itself (optional convenience).
|
|
169
|
+
notes == data['notes'] or data[<note_field>] if set. The note text.
|
|
170
|
+
tags == data['tags']. Set object of tags (note, all lower case).
|
|
171
|
+
text Raw contents (YAML/JSON) of the note.
|
|
172
|
+
filename Path to the file being noted.
|
|
173
|
+
notefile_path Path to the notefile sidecar.
|
|
174
|
+
isdir True if the note target is a directory.
|
|
175
|
+
isfile True if the note target is a file.
|
|
176
|
+
|
|
177
|
+
And it includes the following functions:
|
|
178
|
+
|
|
179
|
+
grep performs a match against 'notes'. Respects the flags:
|
|
180
|
+
'--match-expr-case','--fixed-strings','--full-word' automatically but
|
|
181
|
+
can also be overridden with the respective keyword arguments.
|
|
182
|
+
|
|
183
|
+
g Aliased to grep
|
|
184
|
+
|
|
185
|
+
gall Essentially grep with match_any = False
|
|
186
|
+
|
|
187
|
+
gany Essentially grep with match_any = True
|
|
188
|
+
|
|
189
|
+
tany Returns True if that tag is in tags: e.g.
|
|
190
|
+
tany('tag1','tag2') <==> any(t in tags for t in ['tag1','tag2'])
|
|
191
|
+
|
|
192
|
+
tall Returns true if all args are in tags: e.g.
|
|
193
|
+
tall('tag1','tag2') <==> all(t in tags for t in ['tag1','tag2'])
|
|
194
|
+
|
|
195
|
+
t aliased to tany
|
|
196
|
+
norm_tags Normalize tags (splits commas, lowercases, strips whitespace)
|
|
197
|
+
|
|
198
|
+
It also includes the `re` module and `ss = shlex.split`. More cn be imported with
|
|
199
|
+
multiple lines.
|
|
200
|
+
|
|
201
|
+
WARNING: Unsafe queries are deprecated and will be removed in a future release.
|
|
202
|
+
Set NOTEFILE_SAFE_QUERY=true to use safe queries.
|
|
203
|
+
|
|
204
|
+
Queries can replace --tag and grep but grep is faster if it can be used since it
|
|
205
|
+
is accelerated by not parsing YAML unless needed.
|
|
206
|
+
|
|
207
|
+
For example, the following return the same thing:
|
|
208
|
+
|
|
209
|
+
$ notefile grep word1 word2
|
|
210
|
+
$ notefile query "grep('word1') or grep('word2')"
|
|
211
|
+
$ notefile query "grep('word1','word2')"
|
|
212
|
+
$ notefile query "grep(ss('word1 word2'))" # can use shlex.split (ss)
|
|
213
|
+
|
|
214
|
+
However, queries can be much more complex. For example:
|
|
215
|
+
|
|
216
|
+
$ notefile query "(grep('word1') or grep('word2')) and not grep('word3')"
|
|
217
|
+
|
|
218
|
+
Limited multi-line support exists. Multiple lines can be delineated by ';'.
|
|
219
|
+
However, the last line must evaluate the query. Example:
|
|
220
|
+
|
|
221
|
+
$ notefile query "tt = ['a','b','c']; all(t in tags for t in tt)"
|
|
222
|
+
|
|
223
|
+
Or even using multiple lines in the shell
|
|
224
|
+
|
|
225
|
+
$ notefile query "tt = ['a','b','c']
|
|
226
|
+
> all(t in tags for t in tt)"
|
|
227
|
+
|
|
228
|
+
Can also pass STDIN with the expression `-` to make quoting a bit less onerous
|
|
229
|
+
|
|
230
|
+
$ notefile query - <<EOF
|
|
231
|
+
> a = t('tag1') and not t('tag2')
|
|
232
|
+
> b = g('expr1') or g('expr2') or not g('expr3')
|
|
233
|
+
> a and b
|
|
234
|
+
> EOF
|
|
235
|
+
|
|
236
|
+
tany and/or tall could also be used:
|
|
237
|
+
|
|
238
|
+
$ notefile query "tall('a','b','c')"
|
|
239
|
+
|
|
240
|
+
Queries are pretty flexible and give a good bit of control but some actions
|
|
241
|
+
and queries are still better handled directly in Python.
|
|
242
|
+
|
|
243
|
+
Reminder: `unsafe_query` is unsafe for untrusted input. `safe_query` is restricted
|
|
244
|
+
but not fully sandboxed; expensive regex or large computations can still be costly.
|
|
245
|
+
"""
|
|
246
|
+
if print_help:
|
|
247
|
+
print(help)
|
|
248
|
+
return help
|
notefile/__main__.py
ADDED