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 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
@@ -0,0 +1,4 @@
1
+ from .cli import cli
2
+
3
+ if __name__ == "__main__":
4
+ cli()