Diff
from v0.3.0 to v0.3.1
Diffstat
.gitignore | 1
.pylintrc | 3
.travis.yml | 6
.vulture_whitelist.py | 31
Makefile | 6
Pipfile.lock | 151 +++---
nncli/__init__.py | 2
nncli/__main__.py | 1
nncli/clipboard.py | 28
nncli/config.py | 802 ++++++++++++++++++++++++---------
nncli/gui.py | 913 ++++++++++++++++++++++++++++++++++++++
nncli/log.py | 40 +
nncli/nextcloud_note.py | 87 +-
nncli/nncli.py | 1029 ++++--------------------------------------
nncli/notes_db.py | 511 ++++++++++++--------
nncli/temp.py | 48 +
nncli/user_input.py | 7
nncli/utils.py | 131 ++++
nncli/view_help.py | 172 ++++--
nncli/view_log.py | 30
nncli/view_note.py | 93 ++-
nncli/view_titles.py | 173 ++++--
pyproject.toml | 1
tests/test_config.py | 101 ++-
tests/test_gui.py | 129 +++++
tests/test_nncli.py | 425 ++++++++--------
tox.ini | 7
.gitignore
6
6
.pytest_cache/
7
7
docs/build/
8
8
.tox
9
+htmlcov/
.pylintrc
140
140
xreadlines-attribute,
141
141
deprecated-sys-function,
142
142
exception-escape,
143
- comprehension-escape
143
+ comprehension-escape,
144
+ duplicate-code
144
145
145
146
# Enable the message, report, category or checker with the given id(s). You can
146
147
# either give multiple identifier separated by comma (,) or put this option
.travis.yml
35
35
script: make test
36
36
<<: *xenial-mixin
37
37
python: 3.7
38
- # - stage: lint
39
- # script: make lint
40
- # python: 3.6
38
+ - stage: lint
39
+ script: make lint
40
+ python: 3.6
41
41
- stage: coverage
42
42
script: make coverage
43
43
after_success: coveralls
.vulture_whitelist.py
1
-loop # unused variable (nncli/nncli.py:185)
2
-loop # unused variable (nncli/nncli.py:735)
3
-_.widget # unused attribute (nncli/nncli.py:746)
4
-_.widget # unused attribute (nncli/nncli.py:750)
5
-all_notes_cnt # unused variable (nncli/nncli.py:885)
6
-match_regex # unused variable (nncli/nncli.py:885)
7
-all_notes_cnt # unused variable (nncli/nncli.py:922)
8
-match_regex # unused variable (nncli/nncli.py:922)
9
-all_notes_cnt # unused variable (nncli/nncli.py:976)
10
-match_regex # unused variable (nncli/nncli.py:976)
11
-frame # unused variable (nncli/nncli.py:1057)
12
-signum # unused variable (nncli/nncli.py:1057)
13
-note_index # unused variable (nncli/notes_db.py:410)
14
-note_index # unused variable (nncli/notes_db.py:510)
15
-_.all_notes_cnt # unused attribute (nncli/view_titles.py:13)
16
-_.match_regex # unused attribute (nncli/view_titles.py:13)
17
-_.all_notes_cnt # unused attribute (nncli/view_titles.py:20)
18
-_.match_regex # unused attribute (nncli/view_titles.py:20)
1
+convert # unused function (nncli/cli.py:12)
2
+param # unused variable (nncli/cli.py:12)
3
+loop # unused variable (nncli/gui.py:826)
4
+_.widget # unused attribute (nncli/gui.py:839)
5
+_.widget # unused attribute (nncli/gui.py:844)
6
+arg # unused variable (nncli/gui.py:888)
7
+loop # unused variable (nncli/gui.py:888)
8
+frame # unused variable (nncli/nncli.py:254)
9
+signum # unused variable (nncli/nncli.py:254)
10
+_.all_notes_cnt # unused attribute (nncli/view_titles.py:21)
11
+_.match_regex # unused attribute (nncli/view_titles.py:21)
12
+_.all_notes_cnt # unused attribute (nncli/view_titles.py:33)
13
+_.match_regex # unused attribute (nncli/view_titles.py:33)
Makefile
27
27
BROWSER := python -c "$$BROWSER_PYSCRIPT"
28
28
PIPENV := pipenv
29
29
PIPRUN := $(PIPENV) run
30
-PIPINST := $(PIPENV) --bare install --dev
30
+PIPINST := $(PIPENV) --bare install --dev --skip-lock
31
31
32
32
help:
33
33
@python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST)
. . .
57
57
58
58
lint: ## check style with pylint
59
59
$(PIPRUN) pylint nncli tests --disable=parse-error
60
- $(PIPRUN) vulture nncli .vulture_whitelist
60
+ $(PIPRUN) vulture nncli .vulture_whitelist.py
61
61
62
62
test: ## run tests quickly with the default Python
63
63
$(PIPRUN) python -m pytest
. . .
76
76
$(BROWSER) htmlcov/index.html
77
77
78
78
release: dist ## package and upload a release
79
- $(PIPRUN) flit publish
79
+ twine upload -s dist/*
80
80
81
81
dist: ## builds source and wheel package
82
82
$(PIPRUN) flit build
Pipfile.lock
24
24
},
25
25
"certifi": {
26
26
"hashes": [
27
- "sha256:376690d6f16d32f9d1fe8932551d80b23e9d393a8578c5633a2ed39a64861638",
28
- "sha256:456048c7e371c089d0a77a5212fb37a2c2dce1e24146e3b7e0261736aaeaa22a"
27
+ "sha256:339dc09518b07e2fa7eda5450740925974815557727d6bd35d319c1524a04a4c",
28
+ "sha256:6d58c986d22b038c8c0df30d639f23a3e6d172a05c3583e766f4c0b785c0986a"
29
29
],
30
- "version": "==2018.8.24"
30
+ "version": "==2018.10.15"
31
31
},
32
32
"chardet": {
33
33
"hashes": [
. . .
38
38
},
39
39
"click": {
40
40
"hashes": [
41
- "sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d",
42
- "sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b"
41
+ "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
42
+ "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"
43
43
],
44
44
"index": "pypi",
45
- "version": "==6.7"
45
+ "version": "==7.0"
46
46
},
47
47
"idna": {
48
48
"hashes": [
. . .
53
53
},
54
54
"requests": {
55
55
"hashes": [
56
- "sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1",
57
- "sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a"
56
+ "sha256:99dcfdaaeb17caf6e526f32b6a7b780461512ab3f1d992187801694cba42770c",
57
+ "sha256:a84b8c9ab6239b578f22d1c21d51b696dcfe004032bb80ea832398d6909d7279"
58
58
],
59
59
"index": "pypi",
60
- "version": "==2.19.1"
60
+ "version": "==2.20.0"
61
61
},
62
62
"urllib3": {
63
63
"hashes": [
64
- "sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf",
65
- "sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5"
64
+ "sha256:41c3db2fc01e5b907288010dec72f9d0a74e37d6994e6eb56849f59fea2265ae",
65
+ "sha256:8819bba37a02d143296a4d032373c4dd4aca11f6d4c9973335ca75f9c8475f59"
66
66
],
67
- "markers": "python_version != '3.1.*' and python_version != '3.3.*' and python_version >= '2.6' and python_version != '3.0.*' and python_version != '3.2.*' and python_version < '4'",
68
- "version": "==1.23"
67
+ "version": "==1.24"
69
68
},
70
69
"urwid": {
71
70
"hashes": [
. . .
78
77
"develop": {
79
78
"alabaster": {
80
79
"hashes": [
81
- "sha256:674bb3bab080f598371f4443c5008cbfeb1a5e622dd312395d2d82af2c54c456",
82
- "sha256:b63b1f4dc77c074d386752ec4a8a7517600f6c0db8cd42980cae17ab7b3275d7"
80
+ "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359",
81
+ "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"
83
82
],
84
- "version": "==0.7.11"
83
+ "version": "==0.7.12"
85
84
},
86
85
"astroid": {
87
86
"hashes": [
. . .
95
94
"sha256:0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0",
96
95
"sha256:ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee"
97
96
],
98
- "markers": "python_version != '3.0.*' and python_version != '3.3.*' and python_version != '3.2.*' and python_version >= '2.7' and python_version != '3.1.*'",
99
97
"version": "==1.2.1"
100
98
},
101
99
"attrs": {
. . .
114
112
},
115
113
"certifi": {
116
114
"hashes": [
117
- "sha256:376690d6f16d32f9d1fe8932551d80b23e9d393a8578c5633a2ed39a64861638",
118
- "sha256:456048c7e371c089d0a77a5212fb37a2c2dce1e24146e3b7e0261736aaeaa22a"
115
+ "sha256:339dc09518b07e2fa7eda5450740925974815557727d6bd35d319c1524a04a4c",
116
+ "sha256:6d58c986d22b038c8c0df30d639f23a3e6d172a05c3583e766f4c0b785c0986a"
119
117
],
120
- "version": "==2018.8.24"
118
+ "version": "==2018.10.15"
121
119
},
122
120
"chardet": {
123
121
"hashes": [
. . .
130
128
"hashes": [
131
129
"sha256:03481e81d558d30d230bc12999e3edffe392d244349a90f4ef9b88425fac74ba",
132
130
"sha256:0b136648de27201056c1869a6c0d4e23f464750fd9a9ba9750b8336a244429ed",
131
+ "sha256:0bf8cbbd71adfff0ef1f3a1531e6402d13b7b01ac50a79c97ca15f030dba6306",
133
132
"sha256:10a46017fef60e16694a30627319f38a2b9b52e90182dddb6e37dcdab0f4bf95",
134
133
"sha256:198626739a79b09fa0a2f06e083ffd12eb55449b5f8bfdbeed1df4910b2ca640",
135
134
"sha256:23d341cdd4a0371820eb2b0bd6b88f5003a7438bbedb33688cd33b8eae59affd",
. . .
158
157
"sha256:c1bb572fab8208c400adaf06a8133ac0712179a334c09224fb11393e920abcdd",
159
158
"sha256:de4418dadaa1c01d497e539210cb6baa015965526ff5afc078c57ca69160108d",
160
159
"sha256:e05cb4d9aad6233d67e0541caa7e511fa4047ed7750ec2510d466e806e0255d6",
160
+ "sha256:f05a636b4564104120111800021a92e43397bc12a5c72fed7036be8556e0029e",
161
161
"sha256:f3f501f345f24383c0000395b26b726e46758b71393267aeae0bd36f8b3ade80"
162
162
],
163
- "markers": "python_version < '4' and python_version != '3.2.*' and python_version != '3.0.*' and python_version != '3.1.*' and python_version >= '2.6'",
164
163
"version": "==4.5.1"
165
164
},
166
165
"docutils": {
. . .
170
169
"sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6"
171
170
],
172
171
"version": "==0.14"
172
+ },
173
+ "filelock": {
174
+ "hashes": [
175
+ "sha256:86fe6af56ae08ebc9c66d54ba3398c35b98916d0862d782b276a65816ff39392",
176
+ "sha256:97694f181bdf58f213cca0a7cb556dc7bf90e2f8eb9aa3151260adac56701afb"
177
+ ],
178
+ "version": "==3.0.9"
173
179
},
174
180
"flit": {
175
181
"hashes": [
176
- "sha256:178e6865185b1802aa3b1944f4957d2c83fc56294dc8047d2c4722131f696e61",
177
- "sha256:da823d4acae9bda42dcc0c7ab1d9be475a8a47aae5fd6dde63841d9f430ccb2f"
182
+ "sha256:6aefa6ff89a993af7a7af40d3df3d0387d6663df99797981ec41b1431ec6d1e1",
183
+ "sha256:9969db9708305b64fd8acf20043fcff144f910222397a221fd29871f02ed4a6f"
178
184
],
179
185
"index": "pypi",
180
- "version": "==1.1"
186
+ "version": "==1.2.1"
181
187
},
182
188
"idna": {
183
189
"hashes": [
. . .
191
197
"sha256:3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8",
192
198
"sha256:f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5"
193
199
],
194
- "markers": "python_version != '3.3.*' and python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.2.*' and python_version != '3.1.*'",
195
200
"version": "==1.1.0"
196
201
},
197
202
"isort": {
. . .
200
205
"sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8",
201
206
"sha256:ec9ef8f4a9bc6f71eec99e1806bfa2de401650d996c59330782b89a5555c1497"
202
207
],
203
- "markers": "python_version >= '2.7' and python_version != '3.3.*' and python_version != '3.2.*' and python_version != '3.0.*' and python_version != '3.1.*'",
204
208
"version": "==4.3.4"
205
209
},
206
210
"jinja2": {
. . .
275
279
},
276
280
"packaging": {
277
281
"hashes": [
278
- "sha256:e9215d2d2535d3ae866c3d6efc77d5b24a0192cce0ff20e42896cc0664f889c0",
279
- "sha256:f019b770dd64e585a99714f1fd5e01c7a8f11b45635aa953fd41c689a657375b"
282
+ "sha256:0886227f54515e592aaa2e5a553332c73962917f2831f1b0f9b9f4380a4b9807",
283
+ "sha256:f95a1e147590f204328170981833854229bb2912ac3d5f89e2a8ccd2834800c9"
280
284
],
281
- "version": "==17.1"
285
+ "version": "==18.0"
282
286
},
283
287
"pathlib2": {
284
288
"hashes": [
. . .
291
295
},
292
296
"pbr": {
293
297
"hashes": [
294
- "sha256:1b8be50d938c9bb75d0eaf7eda111eec1bf6dc88a62a6412e33bf077457e0f45",
295
- "sha256:b486975c0cafb6beeb50ca0e17ba047647f229087bd74e37f4a7e2cac17d2caa"
298
+ "sha256:8fc938b1123902f5610b06756a31b1e6febf0d105ae393695b0c9d4244ed2910",
299
+ "sha256:f20ec0abbf132471b68963bb34d9c78e603a5cf9e24473f14358e66551d47475"
296
300
],
297
- "version": "==4.2.0"
301
+ "version": "==5.1.0"
298
302
},
299
303
"pluggy": {
300
304
"hashes": [
301
- "sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1",
302
- "sha256:95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1"
305
+ "sha256:447ba94990e8014ee25ec853339faf7b0fc8050cdc3289d4d71f7f410fb90095",
306
+ "sha256:bde19360a8ec4dfd8a20dcb811780a30998101f078fc7ded6162f0076f50508f"
303
307
],
304
- "markers": "python_version != '3.0.*' and python_version != '3.2.*' and python_version != '3.1.*' and python_version != '3.3.*' and python_version >= '2.7'",
305
- "version": "==0.7.1"
308
+ "version": "==0.8.0"
306
309
},
307
310
"pudb": {
308
311
"hashes": [
. . .
313
316
},
314
317
"py": {
315
318
"hashes": [
316
- "sha256:06a30435d058473046be836d3fc4f27167fd84c45b99704f2fb5509ef61f9af1",
317
- "sha256:50402e9d1c9005d759426988a492e0edaadb7f4e68bcddfea586bc7432d009c6"
319
+ "sha256:bf92637198836372b520efcba9e020c330123be8ce527e535d185ed4b6f45694",
320
+ "sha256:e76826342cefe3c3d5f7e8ee4316b80d1dd8a300781612ddbc765c17ba25a6c6"
318
321
],
319
- "markers": "python_version != '3.0.*' and python_version != '3.2.*' and python_version != '3.3.*' and python_version != '3.1.*' and python_version >= '2.7'",
320
- "version": "==1.6.0"
322
+ "version": "==1.7.0"
321
323
},
322
324
"pygments": {
323
325
"hashes": [
. . .
336
338
},
337
339
"pyparsing": {
338
340
"hashes": [
339
- "sha256:0832bcf47acd283788593e7a0f542407bd9550a55a8a8435214a1960e04bcb04",
340
- "sha256:fee43f17a9c4087e7ed1605bd6df994c6173c1e977d7ade7b651292fab2bd010"
341
+ "sha256:bc6c7146b91af3f567cf6daeaec360bc07d45ffec4cf5353f4d7a208ce7ca30a",
342
+ "sha256:d29593d8ebe7b57d6967b62494f8c72b03ac0262b1eed63826c6f788b3606401"
341
343
],
342
- "version": "==2.2.0"
344
+ "version": "==2.2.2"
343
345
},
344
346
"pytest": {
345
347
"hashes": [
346
- "sha256:2d7c49e931316cc7d1638a3e5f54f5d7b4e5225972b3c9838f3584788d27f349",
347
- "sha256:ad0c7db7b5d4081631e0155f5c61b80ad76ce148551aaafe3a718d65a7508b18"
348
+ "sha256:a9e5e8d7ab9d5b0747f37740276eb362e6a76275d76cebbb52c6049d93b475db",
349
+ "sha256:bf47e8ed20d03764f963f0070ff1c8fda6e2671fc5dd562a4d3b7148ad60f5ca"
348
350
],
349
351
"index": "pypi",
350
- "version": "==3.7.4"
352
+ "version": "==3.9.3"
351
353
},
352
354
"pytest-cov": {
353
355
"hashes": [
. . .
367
369
},
368
370
"pytoml": {
369
371
"hashes": [
370
- "sha256:dae3c4e31d09eb06a6076d671f2281ee5d2c43cbeae16599c3af20881bb818ac"
372
+ "sha256:ca2d0cb127c938b8b76a9a0d0f855cf930c1d50cc3a0af6d3595b566519a1013"
371
373
],
372
- "version": "==0.1.18"
374
+ "version": "==0.1.20"
373
375
},
374
376
"pytz": {
375
377
"hashes": [
376
- "sha256:a061aa0a9e06881eb8b3b2b43f05b9439d6583c206d0a6c340ff72a7b6669053",
377
- "sha256:ffb9ef1de172603304d9d2819af6f5ece76f2e85ec10692a524dd876e72bf277"
378
+ "sha256:31cb35c89bd7d333cd32c5f278fca91b523b0834369e757f4c5641ea252236ca",
379
+ "sha256:8e0f8568c118d3077b46be7d654cc8167fa916092e28320cde048e54bfc9f1e6"
378
380
],
379
- "version": "==2018.5"
381
+ "version": "==2018.7"
380
382
},
381
383
"requests": {
382
384
"hashes": [
383
- "sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1",
384
- "sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a"
385
+ "sha256:99dcfdaaeb17caf6e526f32b6a7b780461512ab3f1d992187801694cba42770c",
386
+ "sha256:a84b8c9ab6239b578f22d1c21d51b696dcfe004032bb80ea832398d6909d7279"
385
387
],
386
388
"index": "pypi",
387
- "version": "==2.19.1"
388
- },
389
- "requests-download": {
390
- "hashes": [
391
- "sha256:92d895a6ca51ea51aa42bab864bddaee31b5601c7e7e1ade4c27b0eb6695d846",
392
- "sha256:994d9d332befae6616f562769bab163f08d6404dc7e28fb7bfed4a0a43a754ad"
393
- ],
394
- "version": "==0.1.2"
389
+ "version": "==2.20.0"
395
390
},
396
391
"scandir": {
397
392
"hashes": [
. . .
427
422
},
428
423
"sphinx": {
429
424
"hashes": [
430
- "sha256:217a7705adcb573da5bbe1e0f5cab4fa0bd89fd9342c9159121746f593c2d5a4",
431
- "sha256:a602513f385f1d5785ff1ca420d9c7eb1a1b63381733b2f0ea8188a391314a86"
425
+ "sha256:652eb8c566f18823a022bb4b6dbc868d366df332a11a0226b5bc3a798a479f17",
426
+ "sha256:d222626d8356de702431e813a05c68a35967e3d66c6cd1c2c89539bb179a7464"
432
427
],
433
428
"index": "pypi",
434
- "version": "==1.7.9"
429
+ "version": "==1.8.1"
435
430
},
436
431
"sphinxcontrib-websupport": {
437
432
"hashes": [
438
433
"sha256:68ca7ff70785cbe1e7bccc71a48b5b6d965d79ca50629606c7861a21b206d9dd",
439
434
"sha256:9de47f375baf1ea07cdb3436ff39d7a9c76042c10a769c52353ec46e4e8fc3b9"
440
435
],
441
- "markers": "python_version != '3.0.*' and python_version != '3.1.*' and python_version >= '2.7' and python_version != '3.3.*' and python_version != '3.2.*'",
442
436
"version": "==1.1.0"
443
437
},
438
+ "toml": {
439
+ "hashes": [
440
+ "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c",
441
+ "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"
442
+ ],
443
+ "version": "==0.10.0"
444
+ },
444
445
"tox": {
445
446
"hashes": [
446
- "sha256:37cf240781b662fb790710c6998527e65ca6851eace84d1595ee71f7af4e85f7",
447
- "sha256:eb61aa5bcce65325538686f09848f04ef679b5cd9b83cc491272099b28739600"
447
+ "sha256:513e32fdf2f9e2d583c2f248f47ba9886428c949f068ac54a0469cac55df5862",
448
+ "sha256:75fa30e8329b41b664585f5fb837e23ce1d7e6fa1f7811f2be571c990f9d911b"
448
449
],
449
450
"index": "pypi",
450
- "version": "==3.2.1"
451
+ "version": "==3.5.3"
451
452
},
452
453
"urllib3": {
453
454
"hashes": [
454
- "sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf",
455
- "sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5"
455
+ "sha256:41c3db2fc01e5b907288010dec72f9d0a74e37d6994e6eb56849f59fea2265ae",
456
+ "sha256:8819bba37a02d143296a4d032373c4dd4aca11f6d4c9973335ca75f9c8475f59"
456
457
],
457
- "markers": "python_version != '3.1.*' and python_version != '3.3.*' and python_version >= '2.6' and python_version != '3.0.*' and python_version != '3.2.*' and python_version < '4'",
458
- "version": "==1.23"
458
+ "version": "==1.24"
459
459
},
460
460
"urwid": {
461
461
"hashes": [
. . .
469
469
"sha256:2ce32cd126117ce2c539f0134eb89de91a8413a29baac49cbab3eb50e2026669",
470
470
"sha256:ca07b4c0b54e14a91af9f34d0919790b016923d157afda5efdde55c96718f752"
471
471
],
472
- "markers": "python_version != '3.2.*' and python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.1.*'",
473
472
"version": "==16.0.0"
474
473
},
475
474
"vulture": {
476
475
"hashes": [
477
- "sha256:79c89ef5e3f2365467bcf491f425f777ae8fd157584dcc550d4591920b00fe3f",
478
- "sha256:e794345a19c76f93f48f4519653038df90ad468ddea7912e14b07a07f6412e32"
476
+ "sha256:4b5a8980c338e9c068d43e7164555a1e4c9c7d84961ce2bc6f3ed975f6e5bc9d",
477
+ "sha256:524b6b9642d0bbe74ea21478bf260937d1ba9b3b86676ca0b17cd10b4b51ba01"
479
478
],
480
479
"index": "pypi",
481
- "version": "==0.29"
480
+ "version": "==1.0"
482
481
},
483
482
"wrapt": {
484
483
"hashes": [
nncli/__init__.py
1
1
# -*- coding: utf-8 -*-
2
2
"""NextCloud Notes Command Line Interface"""
3
3
4
-__version__ = '0.3.0'
4
+__version__ = '0.3.1'
nncli/__main__.py
2
2
"""nncli main module"""
3
3
import nncli.cli
4
4
5
+# pylint: disable=no-value-for-parameter
5
6
if __name__ == '__main__':
6
7
nncli.cli.main()
nncli/clipboard.py
1
1
# -*- coding: utf-8 -*-
2
-
2
+"""clipboard module"""
3
3
import os
4
-from distutils import spawn
4
+import subprocess
5
+from subprocess import CalledProcessError
5
6
6
-class Clipboard(object):
7
+class Clipboard:
8
+ """Class implements copying note content to the clipboard"""
7
9
def __init__(self):
8
10
self.copy_command = self.get_copy_command()
9
11
10
- def get_copy_command(self):
11
- if (spawn.find_executable('xsel')):
12
+ @staticmethod
13
+ def get_copy_command():
14
+ """Defines the copy command based on the contents of $PATH"""
15
+
16
+ try:
17
+ subprocess.check_output(['which', 'xsel'])
12
18
return 'echo "%s" | xsel -ib'
13
- if (spawn.find_executable('pbcopy')):
19
+ except CalledProcessError:
20
+ pass
21
+
22
+ try:
23
+ subprocess.check_output(['which', 'pbcopy'])
14
24
return 'echo "%s" | pbcopy'
25
+ except CalledProcessError:
26
+ pass
27
+
15
28
return None
16
29
17
30
def copy(self, text):
18
- if (self.copy_command):
31
+ """Copies text to the system clipboard"""
32
+ if self.copy_command:
19
33
os.system(self.copy_command % text)
nncli/config.py
1
1
# -*- coding: utf-8 -*-
2
-
3
-import os, sys, urwid, collections, configparser, subprocess
2
+"""config module"""
3
+import collections
4
+import configparser
5
+import os
6
+import subprocess
7
+import sys
4
8
5
9
from appdirs import user_cache_dir, user_config_dir
6
10
11
+# pylint: disable=too-few-public-methods
7
12
class Config:
13
+ """A class to contain all configuration data for nncli"""
14
+ class State:
15
+ """A container class for state information"""
16
+ def __init__(self, **kwargs):
17
+ self.__dict__.update(kwargs)
8
18
9
19
def __init__(self, custom_file=None):
20
+ self.state = Config.State(do_server_sync=True,
21
+ verbose=False,
22
+ do_gui=False,
23
+ search_direction=None)
10
24
self.config_home = user_config_dir('nncli', 'djmoch')
11
25
self.cache_home = user_cache_dir('nncli', 'djmoch')
12
26
13
27
defaults = \
14
28
{
15
- 'cfg_nn_username' : '',
16
- 'cfg_nn_password' : '',
17
- 'cfg_nn_password_eval' : '',
18
- 'cfg_db_path' : self.cache_home,
19
- 'cfg_search_categories' : 'yes', # with regex searches
20
- 'cfg_sort_mode' : 'date', # 'alpha' or 'date'
21
- 'cfg_favorite_ontop' : 'yes',
22
- 'cfg_tabstop' : '4',
23
- 'cfg_format_strftime' : '%Y/%m/%d',
24
- 'cfg_format_note_title' : '[%D] %F %-N %T',
25
- 'cfg_status_bar' : 'yes',
26
- 'cfg_editor' : os.environ['EDITOR'] if 'EDITOR' in os.environ else 'vim {fname} +{line}',
27
- 'cfg_pager' : os.environ['PAGER'] if 'PAGER' in os.environ else 'less -c',
28
- 'cfg_max_logs' : '5',
29
- 'cfg_log_timeout' : '5',
30
- 'cfg_log_reversed' : 'yes',
31
- 'cfg_nn_host' : '',
32
- 'cfg_tempdir' : '',
29
+ 'cfg_nn_username' : '',
30
+ 'cfg_nn_password' : '',
31
+ 'cfg_nn_password_eval' : '',
32
+ 'cfg_db_path' : self.cache_home,
33
+ 'cfg_search_categories' : 'yes', # with regex searches
34
+ 'cfg_sort_mode' : 'date', # 'alpha' or 'date'
35
+ 'cfg_favorite_ontop' : 'yes',
36
+ 'cfg_tabstop' : '4',
37
+ 'cfg_format_strftime' : '%Y/%m/%d',
38
+ 'cfg_format_note_title' : '[%D] %F %-N %T',
39
+ 'cfg_status_bar' : 'yes',
40
+ 'cfg_editor' : os.environ['EDITOR'] \
41
+ if 'EDITOR' in os.environ else 'vim {fname} +{line}',
42
+ 'cfg_pager' : os.environ['PAGER'] \
43
+ if 'PAGER' in os.environ else 'less -c',
44
+ 'cfg_max_logs' : '5',
45
+ 'cfg_log_timeout' : '5',
46
+ 'cfg_log_reversed' : 'yes',
47
+ 'cfg_nn_host' : '',
48
+ 'cfg_tempdir' : '',
33
49
34
- 'kb_help' : 'h',
35
- 'kb_quit' : 'q',
36
- 'kb_sync' : 'S',
37
- 'kb_down' : 'j',
38
- 'kb_up' : 'k',
39
- 'kb_page_down' : 'space',
40
- 'kb_page_up' : 'b',
41
- 'kb_half_page_down' : 'ctrl d',
42
- 'kb_half_page_up' : 'ctrl u',
43
- 'kb_bottom' : 'G',
44
- 'kb_top' : 'g',
45
- 'kb_status' : 's',
46
- 'kb_create_note' : 'C',
47
- 'kb_edit_note' : 'e',
48
- 'kb_view_note' : 'enter',
49
- 'kb_view_note_ext' : 'meta enter',
50
- 'kb_view_note_json' : 'O',
51
- 'kb_pipe_note' : '|',
52
- 'kb_view_next_note' : 'J',
53
- 'kb_view_prev_note' : 'K',
54
- 'kb_view_log' : 'l',
55
- 'kb_tabstop2' : '2',
56
- 'kb_tabstop4' : '4',
57
- 'kb_tabstop8' : '8',
58
- 'kb_search_gstyle' : '/',
59
- 'kb_search_regex' : 'meta /',
60
- 'kb_search_prev_gstyle' : '?',
61
- 'kb_search_prev_regex' : 'meta ?',
62
- 'kb_search_next' : 'n',
63
- 'kb_search_prev' : 'N',
64
- 'kb_clear_search' : 'A',
65
- 'kb_sort_date' : 'd',
66
- 'kb_sort_alpha' : 'a',
67
- 'kb_sort_categories' : 'ctrl t',
68
- 'kb_note_delete' : 'D',
69
- 'kb_note_favorite' : 'p',
70
- 'kb_note_category' : 't',
71
- 'kb_copy_note_text' : 'y',
50
+ 'kb_help' : 'h',
51
+ 'kb_quit' : 'q',
52
+ 'kb_sync' : 'S',
53
+ 'kb_down' : 'j',
54
+ 'kb_up' : 'k',
55
+ 'kb_page_down' : 'space',
56
+ 'kb_page_up' : 'b',
57
+ 'kb_half_page_down' : 'ctrl d',
58
+ 'kb_half_page_up' : 'ctrl u',
59
+ 'kb_bottom' : 'G',
60
+ 'kb_top' : 'g',
61
+ 'kb_status' : 's',
62
+ 'kb_create_note' : 'C',
63
+ 'kb_edit_note' : 'e',
64
+ 'kb_view_note' : 'enter',
65
+ 'kb_view_note_ext' : 'meta enter',
66
+ 'kb_view_note_json' : 'O',
67
+ 'kb_pipe_note' : '|',
68
+ 'kb_view_next_note' : 'J',
69
+ 'kb_view_prev_note' : 'K',
70
+ 'kb_view_log' : 'l',
71
+ 'kb_tabstop2' : '2',
72
+ 'kb_tabstop4' : '4',
73
+ 'kb_tabstop8' : '8',
74
+ 'kb_search_gstyle' : '/',
75
+ 'kb_search_regex' : 'meta /',
76
+ 'kb_search_prev_gstyle' : '?',
77
+ 'kb_search_prev_regex' : 'meta ?',
78
+ 'kb_search_next' : 'n',
79
+ 'kb_search_prev' : 'N',
80
+ 'kb_clear_search' : 'A',
81
+ 'kb_sort_date' : 'd',
82
+ 'kb_sort_alpha' : 'a',
83
+ 'kb_sort_categories' : 'ctrl t',
84
+ 'kb_note_delete' : 'D',
85
+ 'kb_note_favorite' : 'p',
86
+ 'kb_note_category' : 't',
87
+ 'kb_copy_note_text' : 'y',
72
88
73
- 'clr_default_fg' : 'default',
74
- 'clr_default_bg' : 'default',
75
- 'clr_status_bar_fg' : 'dark gray',
76
- 'clr_status_bar_bg' : 'light gray',
77
- 'clr_log_fg' : 'dark gray',
78
- 'clr_log_bg' : 'light gray',
79
- 'clr_user_input_bar_fg' : 'white',
80
- 'clr_user_input_bar_bg' : 'light red',
81
- 'clr_note_focus_fg' : 'white',
82
- 'clr_note_focus_bg' : 'light red',
83
- 'clr_note_title_day_fg' : 'light red',
84
- 'clr_note_title_day_bg' : 'default',
85
- 'clr_note_title_week_fg' : 'light green',
86
- 'clr_note_title_week_bg' : 'default',
87
- 'clr_note_title_month_fg' : 'brown',
88
- 'clr_note_title_month_bg' : 'default',
89
- 'clr_note_title_year_fg' : 'light blue',
90
- 'clr_note_title_year_bg' : 'default',
91
- 'clr_note_title_ancient_fg' : 'light blue',
92
- 'clr_note_title_ancient_bg' : 'default',
93
- 'clr_note_date_fg' : 'dark blue',
94
- 'clr_note_date_bg' : 'default',
95
- 'clr_note_flags_fg' : 'dark magenta',
96
- 'clr_note_flags_bg' : 'default',
97
- 'clr_note_category_fg' : 'dark red',
98
- 'clr_note_category_bg' : 'default',
99
- 'clr_note_content_fg' : 'default',
100
- 'clr_note_content_bg' : 'default',
101
- 'clr_note_content_focus_fg' : 'white',
102
- 'clr_note_content_focus_bg' : 'light red',
103
- 'clr_note_content_old_fg' : 'yellow',
104
- 'clr_note_content_old_bg' : 'dark gray',
105
- 'clr_note_content_old_focus_fg' : 'white',
106
- 'clr_note_content_old_focus_bg' : 'light red',
107
- 'clr_help_focus_fg' : 'white',
108
- 'clr_help_focus_bg' : 'light red',
109
- 'clr_help_header_fg' : 'dark blue',
110
- 'clr_help_header_bg' : 'default',
111
- 'clr_help_config_fg' : 'dark green',
112
- 'clr_help_config_bg' : 'default',
113
- 'clr_help_value_fg' : 'dark red',
114
- 'clr_help_value_bg' : 'default',
115
- 'clr_help_descr_fg' : 'default',
116
- 'clr_help_descr_bg' : 'default'
89
+ 'clr_default_fg' : 'default',
90
+ 'clr_default_bg' : 'default',
91
+ 'clr_status_bar_fg' : 'dark gray',
92
+ 'clr_status_bar_bg' : 'light gray',
93
+ 'clr_log_fg' : 'dark gray',
94
+ 'clr_log_bg' : 'light gray',
95
+ 'clr_user_input_bar_fg' : 'white',
96
+ 'clr_user_input_bar_bg' : 'light red',
97
+ 'clr_note_focus_fg' : 'white',
98
+ 'clr_note_focus_bg' : 'light red',
99
+ 'clr_note_title_day_fg' : 'light red',
100
+ 'clr_note_title_day_bg' : 'default',
101
+ 'clr_note_title_week_fg' : 'light green',
102
+ 'clr_note_title_week_bg' : 'default',
103
+ 'clr_note_title_month_fg' : 'brown',
104
+ 'clr_note_title_month_bg' : 'default',
105
+ 'clr_note_title_year_fg' : 'light blue',
106
+ 'clr_note_title_year_bg' : 'default',
107
+ 'clr_note_title_ancient_fg' : 'light blue',
108
+ 'clr_note_title_ancient_bg' : 'default',
109
+ 'clr_note_date_fg' : 'dark blue',
110
+ 'clr_note_date_bg' : 'default',
111
+ 'clr_note_flags_fg' : 'dark magenta',
112
+ 'clr_note_flags_bg' : 'default',
113
+ 'clr_note_category_fg' : 'dark red',
114
+ 'clr_note_category_bg' : 'default',
115
+ 'clr_note_content_fg' : 'default',
116
+ 'clr_note_content_bg' : 'default',
117
+ 'clr_note_content_focus_fg' : 'white',
118
+ 'clr_note_content_focus_bg' : 'light red',
119
+ 'clr_note_content_old_fg' : 'yellow',
120
+ 'clr_note_content_old_bg' : 'dark gray',
121
+ 'clr_note_content_old_focus_fg' : 'white',
122
+ 'clr_note_content_old_focus_bg' : 'light red',
123
+ 'clr_help_focus_fg' : 'white',
124
+ 'clr_help_focus_bg' : 'light red',
125
+ 'clr_help_header_fg' : 'dark blue',
126
+ 'clr_help_header_bg' : 'default',
127
+ 'clr_help_config_fg' : 'dark green',
128
+ 'clr_help_config_bg' : 'default',
129
+ 'clr_help_value_fg' : 'dark red',
130
+ 'clr_help_value_bg' : 'default',
131
+ 'clr_help_descr_fg' : 'default',
132
+ 'clr_help_descr_bg' : 'default'
117
133
}
118
134
119
- cp = configparser.SafeConfigParser(defaults)
135
+ parser = configparser.SafeConfigParser(defaults)
120
136
if custom_file is not None:
121
- cp.read([custom_file])
137
+ parser.read([custom_file])
122
138
else:
123
- cp.read([os.path.join(self.config_home, 'config')])
139
+ parser.read([os.path.join(self.config_home, 'config')])
124
140
125
141
cfg_sec = 'nncli'
126
142
127
- if not cp.has_section(cfg_sec):
128
- cp.add_section(cfg_sec)
143
+ if not parser.has_section(cfg_sec):
144
+ parser.add_section(cfg_sec)
129
145
146
+ # ordered dicts used to ease help
147
+ self._create_configs_dict(parser, cfg_sec)
148
+ self._create_keybinds_dict(parser, cfg_sec)
149
+ self._create_colors_dict(parser, cfg_sec)
130
150
131
- # special handling for password so we can retrieve it by running a command
132
- nn_password = cp.get(cfg_sec, 'cfg_nn_password', raw=True)
151
+ def _create_keybinds_dict(self, parser, cfg_sec):
152
+ """Create an OrderedDict object with the keybinds"""
153
+ self.keybinds = collections.OrderedDict()
154
+ self.keybinds['help'] = \
155
+ [parser.get(cfg_sec, 'kb_help'), ['common'], 'Help']
156
+ self.keybinds['quit'] = \
157
+ [parser.get(cfg_sec, 'kb_quit'), ['common'], 'Quit']
158
+ self.keybinds['sync'] = \
159
+ [parser.get(cfg_sec, 'kb_sync'), ['common'], 'Full sync']
160
+ self.keybinds['down'] = \
161
+ [
162
+ parser.get(cfg_sec, 'kb_down'),
163
+ ['common'],
164
+ 'Scroll down one line'
165
+ ]
166
+ self.keybinds['up'] = \
167
+ [
168
+ parser.get(cfg_sec, 'kb_up'),
169
+ ['common'],
170
+ 'Scroll up one line'
171
+ ]
172
+ self.keybinds['page_down'] = \
173
+ [
174
+ parser.get(cfg_sec, 'kb_page_down'),
175
+ ['common'],
176
+ 'Page down'
177
+ ]
178
+ self.keybinds['page_up'] = \
179
+ [parser.get(cfg_sec, 'kb_page_up'), ['common'], 'Page up']
180
+ self.keybinds['half_page_down'] = \
181
+ [
182
+ parser.get(cfg_sec, 'kb_half_page_down'),
183
+ ['common'],
184
+ 'Half page down'
185
+ ]
186
+ self.keybinds['half_page_up'] = \
187
+ [
188
+ parser.get(cfg_sec, 'kb_half_page_up'),
189
+ ['common'],
190
+ 'Half page up'
191
+ ]
192
+ self.keybinds['bottom'] = \
193
+ [
194
+ parser.get(cfg_sec, 'kb_bottom'),
195
+ ['common'],
196
+ 'Goto bottom'
197
+ ]
198
+ self.keybinds['top'] = \
199
+ [parser.get(cfg_sec, 'kb_top'), ['common'], 'Goto top']
200
+ self.keybinds['status'] = \
201
+ [
202
+ parser.get(cfg_sec, 'kb_status'),
203
+ ['common'],
204
+ 'Toggle status bar'
205
+ ]
206
+ self.keybinds['view_log'] = \
207
+ [
208
+ parser.get(cfg_sec, 'kb_view_log'),
209
+ ['common'],
210
+ 'View log'
211
+ ]
212
+ self.keybinds['create_note'] = \
213
+ [
214
+ parser.get(cfg_sec, 'kb_create_note'),
215
+ ['titles'],
216
+ 'Create a new note'
217
+ ]
218
+ self.keybinds['edit_note'] = \
219
+ [
220
+ parser.get(cfg_sec, 'kb_edit_note'),
221
+ ['titles', 'notes'],
222
+ 'Edit note'
223
+ ]
224
+ self.keybinds['view_note'] = \
225
+ [
226
+ parser.get(cfg_sec, 'kb_view_note'),
227
+ ['titles'],
228
+ 'View note'
229
+ ]
230
+ self.keybinds['view_note_ext'] = \
231
+ [
232
+ parser.get(cfg_sec, 'kb_view_note_ext'),
233
+ ['titles', 'notes'],
234
+ 'View note with pager'
235
+ ]
236
+ self.keybinds['view_note_json'] = \
237
+ [
238
+ parser.get(cfg_sec, 'kb_view_note_json'),
239
+ ['titles', 'notes'],
240
+ 'View note raw json'
241
+ ]
242
+ self.keybinds['pipe_note'] = \
243
+ [
244
+ parser.get(cfg_sec, 'kb_pipe_note'),
245
+ ['titles', 'notes'],
246
+ 'Pipe note contents'
247
+ ]
248
+ self.keybinds['view_next_note'] = \
249
+ [
250
+ parser.get(cfg_sec, 'kb_view_next_note'),
251
+ ['notes'],
252
+ 'View next note'
253
+ ]
254
+ self.keybinds['view_prev_note'] = \
255
+ [
256
+ parser.get(cfg_sec, 'kb_view_prev_note'),
257
+ ['notes'],
258
+ 'View previous note'
259
+ ]
260
+ self.keybinds['tabstop2'] = \
261
+ [
262
+ parser.get(cfg_sec, 'kb_tabstop2'),
263
+ ['notes'],
264
+ 'View with tabstop=2'
265
+ ]
266
+ self.keybinds['tabstop4'] = \
267
+ [
268
+ parser.get(cfg_sec, 'kb_tabstop4'),
269
+ ['notes'],
270
+ 'View with tabstop=4'
271
+ ]
272
+ self.keybinds['tabstop8'] = \
273
+ [
274
+ parser.get(cfg_sec, 'kb_tabstop8'),
275
+ ['notes'],
276
+ 'View with tabstop=8'
277
+ ]
278
+ self.keybinds['search_gstyle'] = \
279
+ [
280
+ parser.get(cfg_sec, 'kb_search_gstyle'),
281
+ ['titles', 'notes'],
282
+ 'Search using gstyle'
283
+ ]
284
+ self.keybinds['search_prev_gstyle'] = \
285
+ [
286
+ parser.get(cfg_sec, 'kb_search_prev_gstyle'),
287
+ ['notes'],
288
+ 'Search backwards using gstyle'
289
+ ]
290
+ self.keybinds['search_regex'] = \
291
+ [
292
+ parser.get(cfg_sec, 'kb_search_regex'),
293
+ ['titles', 'notes'],
294
+ 'Search using regex'
295
+ ]
296
+ self.keybinds['search_prev_regex'] = \
297
+ [
298
+ parser.get(cfg_sec, 'kb_search_prev_regex'),
299
+ ['notes'],
300
+ 'Search backwards using regex'
301
+ ]
302
+ self.keybinds['search_next'] = \
303
+ [
304
+ parser.get(cfg_sec, 'kb_search_next'),
305
+ ['notes'],
306
+ 'Go to next search result'
307
+ ]
308
+ self.keybinds['search_prev'] = \
309
+ [
310
+ parser.get(cfg_sec, 'kb_search_prev'),
311
+ ['notes'],
312
+ 'Go to previous search result'
313
+ ]
314
+ self.keybinds['clear_search'] = \
315
+ [
316
+ parser.get(cfg_sec, 'kb_clear_search'),
317
+ ['titles'],
318
+ 'Show all notes'
319
+ ]
320
+ self.keybinds['sort_date'] = \
321
+ [
322
+ parser.get(cfg_sec, 'kb_sort_date'),
323
+ ['titles'],
324
+ 'Sort notes by date'
325
+ ]
326
+ self.keybinds['sort_alpha'] = \
327
+ [
328
+ parser.get(cfg_sec, 'kb_sort_alpha'),
329
+ ['titles'],
330
+ 'Sort notes by alpha'
331
+ ]
332
+ self.keybinds['sort_categories'] = \
333
+ [
334
+ parser.get(cfg_sec, 'kb_sort_categories'),
335
+ ['titles'],
336
+ 'Sort notes by categories'
337
+ ]
338
+ self.keybinds['note_delete'] = \
339
+ [
340
+ parser.get(cfg_sec, 'kb_note_delete'),
341
+ ['titles', 'notes'],
342
+ 'Delete a note'
343
+ ]
344
+ self.keybinds['note_favorite'] = \
345
+ [
346
+ parser.get(cfg_sec, 'kb_note_favorite'),
347
+ ['titles', 'notes'],
348
+ 'Favorite note'
349
+ ]
350
+ self.keybinds['note_category'] = \
351
+ [
352
+ parser.get(cfg_sec, 'kb_note_category'),
353
+ ['titles', 'notes'],
354
+ 'Edit note category'
355
+ ]
356
+ self.keybinds['copy_note_text'] = \
357
+ [
358
+ parser.get(cfg_sec, 'kb_copy_note_text'),
359
+ ['notes'],
360
+ 'Copy line (xsel/pbcopy)'
361
+ ]
362
+
363
+ def _create_colors_dict(self, parser, cfg_sec):
364
+ """Create an OrderedDict object with the colors"""
365
+ self.colors = collections.OrderedDict()
366
+ self.colors['default_fg'] = \
367
+ [parser.get(cfg_sec, 'clr_default_fg'), 'Default fg']
368
+ self.colors['default_bg'] = \
369
+ [parser.get(cfg_sec, 'clr_default_bg'), 'Default bg']
370
+ self.colors['status_bar_fg'] = \
371
+ [parser.get(cfg_sec, 'clr_status_bar_fg'), 'Status bar fg']
372
+ self.colors['status_bar_bg'] = \
373
+ [parser.get(cfg_sec, 'clr_status_bar_bg'), 'Status bar bg']
374
+ self.colors['log_fg'] = \
375
+ [parser.get(cfg_sec, 'clr_log_fg'), 'Log message fg']
376
+ self.colors['log_bg'] = \
377
+ [parser.get(cfg_sec, 'clr_log_bg'), 'Log message bg']
378
+ self.colors['user_input_bar_fg'] = \
379
+ [
380
+ parser.get(cfg_sec, 'clr_user_input_bar_fg'),
381
+ 'User input bar fg'
382
+ ]
383
+ self.colors['user_input_bar_bg'] = \
384
+ [
385
+ parser.get(cfg_sec, 'clr_user_input_bar_bg'),
386
+ 'User input bar bg'
387
+ ]
388
+ self.colors['note_focus_fg'] = \
389
+ [
390
+ parser.get(cfg_sec, 'clr_note_focus_fg'),
391
+ 'Note title focus fg'
392
+ ]
393
+ self.colors['note_focus_bg'] = \
394
+ [
395
+ parser.get(cfg_sec, 'clr_note_focus_bg'),
396
+ 'Note title focus bg'
397
+ ]
398
+ self.colors['note_title_day_fg'] = \
399
+ [
400
+ parser.get(cfg_sec, 'clr_note_title_day_fg'),
401
+ 'Day old note title fg'
402
+ ]
403
+ self.colors['note_title_day_bg'] = \
404
+ [
405
+ parser.get(cfg_sec, 'clr_note_title_day_bg'),
406
+ 'Day old note title bg'
407
+ ]
408
+ self.colors['note_title_week_fg'] = \
409
+ [
410
+ parser.get(cfg_sec, 'clr_note_title_week_fg'),
411
+ 'Week old note title fg'
412
+ ]
413
+ self.colors['note_title_week_bg'] = \
414
+ [
415
+ parser.get(cfg_sec, 'clr_note_title_week_bg'),
416
+ 'Week old note title bg'
417
+ ]
418
+ self.colors['note_title_month_fg'] = \
419
+ [
420
+ parser.get(cfg_sec, 'clr_note_title_month_fg'),
421
+ 'Month old note title fg'
422
+ ]
423
+ self.colors['note_title_month_bg'] = \
424
+ [
425
+ parser.get(cfg_sec, 'clr_note_title_month_bg'),
426
+ 'Month old note title bg'
427
+ ]
428
+ self.colors['note_title_year_fg'] = \
429
+ [
430
+ parser.get(cfg_sec, 'clr_note_title_year_fg'),
431
+ 'Year old note title fg'
432
+ ]
433
+ self.colors['note_title_year_bg'] = \
434
+ [
435
+ parser.get(cfg_sec, 'clr_note_title_year_bg'),
436
+ 'Year old note title bg'
437
+ ]
438
+ self.colors['note_title_ancient_fg'] = \
439
+ [
440
+ parser.get(cfg_sec, 'clr_note_title_ancient_fg'),
441
+ 'Ancient note title fg'
442
+ ]
443
+ self.colors['note_title_ancient_bg'] = \
444
+ [
445
+ parser.get(cfg_sec, 'clr_note_title_ancient_bg'),
446
+ 'Ancient note title bg'
447
+ ]
448
+ self.colors['note_date_fg'] = \
449
+ [parser.get(cfg_sec, 'clr_note_date_fg'), 'Note date fg']
450
+ self.colors['note_date_bg'] = \
451
+ [parser.get(cfg_sec, 'clr_note_date_bg'), 'Note date bg']
452
+ self.colors['note_flags_fg'] = \
453
+ [parser.get(cfg_sec, 'clr_note_flags_fg'), 'Note flags fg']
454
+ self.colors['note_flags_bg'] = \
455
+ [parser.get(cfg_sec, 'clr_note_flags_bg'), 'Note flags bg']
456
+ self.colors['note_category_fg'] = \
457
+ [
458
+ parser.get(cfg_sec, 'clr_note_category_fg'),
459
+ 'Note category fg'
460
+ ]
461
+ self.colors['note_category_bg'] = \
462
+ [
463
+ parser.get(cfg_sec, 'clr_note_category_bg'),
464
+ 'Note category bg'
465
+ ]
466
+ self.colors['note_content_fg'] = \
467
+ [parser.get(cfg_sec, 'clr_note_content_fg'), 'Note content fg']
468
+ self.colors['note_content_bg'] = \
469
+ [parser.get(cfg_sec, 'clr_note_content_bg'), 'Note content bg']
470
+ self.colors['note_content_focus_fg'] = \
471
+ [
472
+ parser.get(cfg_sec, 'clr_note_content_focus_fg'),
473
+ 'Note content focus fg'
474
+ ]
475
+ self.colors['note_content_focus_bg'] = \
476
+ [
477
+ parser.get(cfg_sec, 'clr_note_content_focus_bg'),
478
+ 'Note content focus bg'
479
+ ]
480
+ self.colors['note_content_old_fg'] = \
481
+ [
482
+ parser.get(cfg_sec, 'clr_note_content_old_fg'),
483
+ 'Old note content fg'
484
+ ]
485
+ self.colors['note_content_old_bg'] = \
486
+ [
487
+ parser.get(cfg_sec, 'clr_note_content_old_bg'),
488
+ 'Old note content bg'
489
+ ]
490
+ self.colors['note_content_old_focus_fg'] = \
491
+ [
492
+ parser.get(cfg_sec, 'clr_note_content_old_focus_fg'),
493
+ 'Old note content focus fg'
494
+ ]
495
+ self.colors['note_content_old_focus_bg'] = \
496
+ [
497
+ parser.get(cfg_sec, 'clr_note_content_old_focus_bg'),
498
+ 'Old note content focus bg'
499
+ ]
500
+ self.colors['help_focus_fg'] = \
501
+ [parser.get(cfg_sec, 'clr_help_focus_fg'), 'Help focus fg']
502
+ self.colors['help_focus_bg'] = \
503
+ [parser.get(cfg_sec, 'clr_help_focus_bg'), 'Help focus bg']
504
+ self.colors['help_header_fg'] = \
505
+ [parser.get(cfg_sec, 'clr_help_header_fg'), 'Help header fg']
506
+ self.colors['help_header_bg'] = \
507
+ [parser.get(cfg_sec, 'clr_help_header_bg'), 'Help header bg']
508
+ self.colors['help_config_fg'] = \
509
+ [parser.get(cfg_sec, 'clr_help_config_fg'), 'Help config fg']
510
+ self.colors['help_config_bg'] = \
511
+ [parser.get(cfg_sec, 'clr_help_config_bg'), 'Help config bg']
512
+ self.colors['help_value_fg'] = \
513
+ [parser.get(cfg_sec, 'clr_help_value_fg'), 'Help value fg']
514
+ self.colors['help_value_bg'] = \
515
+ [parser.get(cfg_sec, 'clr_help_value_bg'), 'Help value bg']
516
+ self.colors['help_descr_fg'] = \
517
+ [
518
+ parser.get(cfg_sec, 'clr_help_descr_fg'),
519
+ 'Help description fg'
520
+ ]
521
+ self.colors['help_descr_bg'] = \
522
+ [
523
+ parser.get(cfg_sec, 'clr_help_descr_bg'),
524
+ 'Help description bg'
525
+ ]
526
+
527
+ def _create_configs_dict(self, parser, cfg_sec):
528
+ """Create an OrderedDict object with the configs"""
529
+
530
+ # special handling for password so we can retrieve it by
531
+ # running a command
532
+ nn_password = parser.get(cfg_sec, 'cfg_nn_password', raw=True)
133
533
if not nn_password:
134
- command = cp.get(cfg_sec, 'cfg_nn_password_eval', raw=True)
534
+ command = parser.get(cfg_sec, 'cfg_nn_password_eval', raw=True)
135
535
if command:
136
536
try:
137
- nn_password = subprocess.check_output(command, shell=True, universal_newlines=True)
138
- # remove trailing newlines to avoid requiring butchering shell commands (they can't usually be in passwords anyway)
537
+ nn_password = subprocess.check_output(
538
+ command,
539
+ shell=True,
540
+ universal_newlines=True
541
+ )
542
+ # remove trailing newlines to avoid requiring
543
+ # butchering shell commands (they can't usually be
544
+ # in passwords anyway)
139
545
nn_password = nn_password.rstrip('\n')
140
- except subprocess.CalledProcessError as e:
141
- print('Error evaluating command for password.')
142
- print(e)
546
+ except subprocess.CalledProcessError as ex:
547
+ print('Error evaluating command for password: %s' % ex)
143
548
sys.exit(1)
144
-
145
- # ordered dicts used to ease help
146
549
147
550
self.configs = collections.OrderedDict()
148
- self.configs['nn_username'] = [ cp.get(cfg_sec, 'cfg_nn_username', raw=True), 'NextCloud Username' ]
149
- self.configs['nn_password'] = [ nn_password, 'NextCloud Password' ]
150
- self.configs['nn_host'] = [ cp.get(cfg_sec, 'cfg_nn_host', raw=True), 'NextCloud server hostname' ]
151
- self.configs['db_path'] = [ cp.get(cfg_sec, 'cfg_db_path'), 'Note storage path' ]
152
- self.configs['search_categories'] = [ cp.get(cfg_sec, 'cfg_search_categories'), 'Search categories as well' ]
153
- self.configs['sort_mode'] = [ cp.get(cfg_sec, 'cfg_sort_mode'), 'Sort mode' ]
154
- self.configs['favorite_ontop'] = [ cp.get(cfg_sec, 'cfg_favorite_ontop'), 'Favorite at top of list' ]
155
- self.configs['tabstop'] = [ cp.get(cfg_sec, 'cfg_tabstop'), 'Tabstop spaces' ]
156
- self.configs['format_strftime'] = [ cp.get(cfg_sec, 'cfg_format_strftime', raw=True), 'Date strftime format' ]
157
- self.configs['format_note_title'] = [ cp.get(cfg_sec, 'cfg_format_note_title', raw=True), 'Note title format' ]
158
- self.configs['status_bar'] = [ cp.get(cfg_sec, 'cfg_status_bar'), 'Show the status bar' ]
159
- self.configs['editor'] = [ cp.get(cfg_sec, 'cfg_editor'), 'Editor command' ]
160
- self.configs['pager'] = [ cp.get(cfg_sec, 'cfg_pager'), 'External pager command' ]
161
- self.configs['max_logs'] = [ cp.get(cfg_sec, 'cfg_max_logs'), 'Max logs in footer' ]
162
- self.configs['log_timeout'] = [ cp.get(cfg_sec, 'cfg_log_timeout'), 'Log timeout' ]
163
- self.configs['log_reversed'] = [ cp.get(cfg_sec, 'cfg_log_reversed'), 'Log file reversed' ]
164
- self.configs['tempdir'] = [ cp.get(cfg_sec, 'cfg_tempdir'), 'Temporary directory for note storage' ]
165
-
166
- self.keybinds = collections.OrderedDict()
167
- self.keybinds['help'] = [ cp.get(cfg_sec, 'kb_help'), [ 'common' ], 'Help' ]
168
- self.keybinds['quit'] = [ cp.get(cfg_sec, 'kb_quit'), [ 'common' ], 'Quit' ]
169
- self.keybinds['sync'] = [ cp.get(cfg_sec, 'kb_sync'), [ 'common' ], 'Full sync' ]
170
- self.keybinds['down'] = [ cp.get(cfg_sec, 'kb_down'), [ 'common' ], 'Scroll down one line' ]
171
- self.keybinds['up'] = [ cp.get(cfg_sec, 'kb_up'), [ 'common' ], 'Scroll up one line' ]
172
- self.keybinds['page_down'] = [ cp.get(cfg_sec, 'kb_page_down'), [ 'common' ], 'Page down' ]
173
- self.keybinds['page_up'] = [ cp.get(cfg_sec, 'kb_page_up'), [ 'common' ], 'Page up' ]
174
- self.keybinds['half_page_down'] = [ cp.get(cfg_sec, 'kb_half_page_down'), [ 'common' ], 'Half page down' ]
175
- self.keybinds['half_page_up'] = [ cp.get(cfg_sec, 'kb_half_page_up'), [ 'common' ], 'Half page up' ]
176
- self.keybinds['bottom'] = [ cp.get(cfg_sec, 'kb_bottom'), [ 'common' ], 'Goto bottom' ]
177
- self.keybinds['top'] = [ cp.get(cfg_sec, 'kb_top'), [ 'common' ], 'Goto top' ]
178
- self.keybinds['status'] = [ cp.get(cfg_sec, 'kb_status'), [ 'common' ], 'Toggle status bar' ]
179
- self.keybinds['view_log'] = [ cp.get(cfg_sec, 'kb_view_log'), [ 'common' ], 'View log' ]
180
- self.keybinds['create_note'] = [ cp.get(cfg_sec, 'kb_create_note'), [ 'titles' ], 'Create a new note' ]
181
- self.keybinds['edit_note'] = [ cp.get(cfg_sec, 'kb_edit_note'), [ 'titles', 'notes' ], 'Edit note' ]
182
- self.keybinds['view_note'] = [ cp.get(cfg_sec, 'kb_view_note'), [ 'titles' ], 'View note' ]
183
- self.keybinds['view_note_ext'] = [ cp.get(cfg_sec, 'kb_view_note_ext'), [ 'titles', 'notes' ], 'View note with pager' ]
184
- self.keybinds['view_note_json'] = [ cp.get(cfg_sec, 'kb_view_note_json'), [ 'titles', 'notes' ], 'View note raw json' ]
185
- self.keybinds['pipe_note'] = [ cp.get(cfg_sec, 'kb_pipe_note'), [ 'titles', 'notes' ], 'Pipe note contents' ]
186
- self.keybinds['view_next_note'] = [ cp.get(cfg_sec, 'kb_view_next_note'), [ 'notes' ], 'View next note' ]
187
- self.keybinds['view_prev_note'] = [ cp.get(cfg_sec, 'kb_view_prev_note'), [ 'notes' ], 'View previous note' ]
188
- self.keybinds['tabstop2'] = [ cp.get(cfg_sec, 'kb_tabstop2'), [ 'notes' ], 'View with tabstop=2' ]
189
- self.keybinds['tabstop4'] = [ cp.get(cfg_sec, 'kb_tabstop4'), [ 'notes' ], 'View with tabstop=4' ]
190
- self.keybinds['tabstop8'] = [ cp.get(cfg_sec, 'kb_tabstop8'), [ 'notes' ], 'View with tabstop=8' ]
191
- self.keybinds['search_gstyle'] = [ cp.get(cfg_sec, 'kb_search_gstyle'), [ 'titles', 'notes' ], 'Search using gstyle' ]
192
- self.keybinds['search_prev_gstyle'] = [ cp.get(cfg_sec, 'kb_search_prev_gstyle'), [ 'notes' ], 'Search backwards using gstyle' ]
193
- self.keybinds['search_regex'] = [ cp.get(cfg_sec, 'kb_search_regex'), [ 'titles', 'notes' ], 'Search using regex' ]
194
- self.keybinds['search_prev_regex'] = [ cp.get(cfg_sec, 'kb_search_prev_regex'), [ 'notes' ], 'Search backwards using regex' ]
195
- self.keybinds['search_next'] = [ cp.get(cfg_sec, 'kb_search_next'), [ 'notes' ], 'Go to next search result' ]
196
- self.keybinds['search_prev'] = [ cp.get(cfg_sec, 'kb_search_prev'), [ 'notes' ], 'Go to previous search result' ]
197
- self.keybinds['clear_search'] = [ cp.get(cfg_sec, 'kb_clear_search'), [ 'titles' ], 'Show all notes' ]
198
- self.keybinds['sort_date'] = [ cp.get(cfg_sec, 'kb_sort_date'), [ 'titles' ], 'Sort notes by date' ]
199
- self.keybinds['sort_alpha'] = [ cp.get(cfg_sec, 'kb_sort_alpha'), [ 'titles' ], 'Sort notes by alpha' ]
200
- self.keybinds['sort_categories'] = [ cp.get(cfg_sec, 'kb_sort_categories'), [ 'titles' ], 'Sort notes by categories' ]
201
- self.keybinds['note_delete'] = [ cp.get(cfg_sec,'kb_note_delete'), [ 'titles', 'notes' ], 'Delete a note' ]
202
- self.keybinds['note_favorite'] = [ cp.get(cfg_sec, 'kb_note_favorite'), [ 'titles', 'notes' ], 'Favorite note' ]
203
- self.keybinds['note_category'] = [ cp.get(cfg_sec, 'kb_note_category'), [ 'titles', 'notes' ], 'Edit note category' ]
204
- self.keybinds['copy_note_text'] = [ cp.get(cfg_sec, 'kb_copy_note_text'), [ 'notes' ], 'Copy line (xsel/pbcopy)' ]
205
-
206
- self.colors = collections.OrderedDict()
207
- self.colors['default_fg'] = [ cp.get(cfg_sec, 'clr_default_fg'), 'Default fg' ]
208
- self.colors['default_bg'] = [ cp.get(cfg_sec, 'clr_default_bg'), 'Default bg' ]
209
- self.colors['status_bar_fg'] = [ cp.get(cfg_sec, 'clr_status_bar_fg'), 'Status bar fg' ]
210
- self.colors['status_bar_bg'] = [ cp.get(cfg_sec, 'clr_status_bar_bg'), 'Status bar bg' ]
211
- self.colors['log_fg'] = [ cp.get(cfg_sec, 'clr_log_fg'), 'Log message fg' ]
212
- self.colors['log_bg'] = [ cp.get(cfg_sec, 'clr_log_bg'), 'Log message bg' ]
213
- self.colors['user_input_bar_fg'] = [ cp.get(cfg_sec, 'clr_user_input_bar_fg'), 'User input bar fg' ]
214
- self.colors['user_input_bar_bg'] = [ cp.get(cfg_sec, 'clr_user_input_bar_bg'), 'User input bar bg' ]
215
- self.colors['note_focus_fg'] = [ cp.get(cfg_sec, 'clr_note_focus_fg'), 'Note title focus fg' ]
216
- self.colors['note_focus_bg'] = [ cp.get(cfg_sec, 'clr_note_focus_bg'), 'Note title focus bg' ]
217
- self.colors['note_title_day_fg'] = [ cp.get(cfg_sec, 'clr_note_title_day_fg'), 'Day old note title fg' ]
218
- self.colors['note_title_day_bg'] = [ cp.get(cfg_sec, 'clr_note_title_day_bg'), 'Day old note title bg' ]
219
- self.colors['note_title_week_fg'] = [ cp.get(cfg_sec, 'clr_note_title_week_fg'), 'Week old note title fg' ]
220
- self.colors['note_title_week_bg'] = [ cp.get(cfg_sec, 'clr_note_title_week_bg'), 'Week old note title bg' ]
221
- self.colors['note_title_month_fg'] = [ cp.get(cfg_sec, 'clr_note_title_month_fg'), 'Month old note title fg' ]
222
- self.colors['note_title_month_bg'] = [ cp.get(cfg_sec, 'clr_note_title_month_bg'), 'Month old note title bg' ]
223
- self.colors['note_title_year_fg'] = [ cp.get(cfg_sec, 'clr_note_title_year_fg'), 'Year old note title fg' ]
224
- self.colors['note_title_year_bg'] = [ cp.get(cfg_sec, 'clr_note_title_year_bg'), 'Year old note title bg' ]
225
- self.colors['note_title_ancient_fg'] = [ cp.get(cfg_sec, 'clr_note_title_ancient_fg'), 'Ancient note title fg' ]
226
- self.colors['note_title_ancient_bg'] = [ cp.get(cfg_sec, 'clr_note_title_ancient_bg'), 'Ancient note title bg' ]
227
- self.colors['note_date_fg'] = [ cp.get(cfg_sec, 'clr_note_date_fg'), 'Note date fg' ]
228
- self.colors['note_date_bg'] = [ cp.get(cfg_sec, 'clr_note_date_bg'), 'Note date bg' ]
229
- self.colors['note_flags_fg'] = [ cp.get(cfg_sec, 'clr_note_flags_fg'), 'Note flags fg' ]
230
- self.colors['note_flags_bg'] = [ cp.get(cfg_sec, 'clr_note_flags_bg'), 'Note flags bg' ]
231
- self.colors['note_category_fg'] = [ cp.get(cfg_sec, 'clr_note_category_fg'), 'Note category fg' ]
232
- self.colors['note_category_bg'] = [ cp.get(cfg_sec, 'clr_note_category_bg'), 'Note category bg' ]
233
- self.colors['note_content_fg'] = [ cp.get(cfg_sec, 'clr_note_content_fg'), 'Note content fg' ]
234
- self.colors['note_content_bg'] = [ cp.get(cfg_sec, 'clr_note_content_bg'), 'Note content bg' ]
235
- self.colors['note_content_focus_fg'] = [ cp.get(cfg_sec, 'clr_note_content_focus_fg'), 'Note content focus fg' ]
236
- self.colors['note_content_focus_bg'] = [ cp.get(cfg_sec, 'clr_note_content_focus_bg'), 'Note content focus bg' ]
237
- self.colors['note_content_old_fg'] = [ cp.get(cfg_sec, 'clr_note_content_old_fg'), 'Old note content fg' ]
238
- self.colors['note_content_old_bg'] = [ cp.get(cfg_sec, 'clr_note_content_old_bg'), 'Old note content bg' ]
239
- self.colors['note_content_old_focus_fg'] = [ cp.get(cfg_sec, 'clr_note_content_old_focus_fg'), 'Old note content focus fg' ]
240
- self.colors['note_content_old_focus_bg'] = [ cp.get(cfg_sec, 'clr_note_content_old_focus_bg'), 'Old note content focus bg' ]
241
- self.colors['help_focus_fg'] = [ cp.get(cfg_sec, 'clr_help_focus_fg'), 'Help focus fg' ]
242
- self.colors['help_focus_bg'] = [ cp.get(cfg_sec, 'clr_help_focus_bg'), 'Help focus bg' ]
243
- self.colors['help_header_fg'] = [ cp.get(cfg_sec, 'clr_help_header_fg'), 'Help header fg' ]
244
- self.colors['help_header_bg'] = [ cp.get(cfg_sec, 'clr_help_header_bg'), 'Help header bg' ]
245
- self.colors['help_config_fg'] = [ cp.get(cfg_sec, 'clr_help_config_fg'), 'Help config fg' ]
246
- self.colors['help_config_bg'] = [ cp.get(cfg_sec, 'clr_help_config_bg'), 'Help config bg' ]
247
- self.colors['help_value_fg'] = [ cp.get(cfg_sec, 'clr_help_value_fg'), 'Help value fg' ]
248
- self.colors['help_value_bg'] = [ cp.get(cfg_sec, 'clr_help_value_bg'), 'Help value bg' ]
249
- self.colors['help_descr_fg'] = [ cp.get(cfg_sec, 'clr_help_descr_fg'), 'Help description fg' ]
250
- self.colors['help_descr_bg'] = [ cp.get(cfg_sec, 'clr_help_descr_bg'), 'Help description bg' ]
551
+ self.configs['nn_username'] = \
552
+ [
553
+ parser.get(cfg_sec, 'cfg_nn_username', raw=True),
554
+ 'NextCloud Username'
555
+ ]
556
+ self.configs['nn_password'] = [nn_password, 'NextCloud Password']
557
+ self.configs['nn_host'] = \
558
+ [
559
+ parser.get(cfg_sec, 'cfg_nn_host', raw=True),
560
+ 'NextCloud server hostname'
561
+ ]
562
+ self.configs['db_path'] = \
563
+ [parser.get(cfg_sec, 'cfg_db_path'), 'Note storage path']
564
+ self.configs['search_categories'] = \
565
+ [
566
+ parser.get(cfg_sec, 'cfg_search_categories'),
567
+ 'Search categories as well'
568
+ ]
569
+ self.configs['sort_mode'] = \
570
+ [parser.get(cfg_sec, 'cfg_sort_mode'), 'Sort mode']
571
+ self.configs['favorite_ontop'] = \
572
+ [
573
+ parser.get(cfg_sec, 'cfg_favorite_ontop'),
574
+ 'Favorite at top of list'
575
+ ]
576
+ self.configs['tabstop'] = \
577
+ [parser.get(cfg_sec, 'cfg_tabstop'), 'Tabstop spaces']
578
+ self.configs['format_strftime'] = \
579
+ [
580
+ parser.get(cfg_sec, 'cfg_format_strftime', raw=True),
581
+ 'Date strftime format'
582
+ ]
583
+ self.configs['format_note_title'] = \
584
+ [
585
+ parser.get(cfg_sec, 'cfg_format_note_title', raw=True),
586
+ 'Note title format'
587
+ ]
588
+ self.configs['status_bar'] = \
589
+ [parser.get(cfg_sec, 'cfg_status_bar'), 'Show the status bar']
590
+ self.configs['editor'] = \
591
+ [parser.get(cfg_sec, 'cfg_editor'), 'Editor command']
592
+ self.configs['pager'] = \
593
+ [parser.get(cfg_sec, 'cfg_pager'), 'External pager command']
594
+ self.configs['max_logs'] = \
595
+ [parser.get(cfg_sec, 'cfg_max_logs'), 'Max logs in footer']
596
+ self.configs['log_timeout'] = \
597
+ [parser.get(cfg_sec, 'cfg_log_timeout'), 'Log timeout']
598
+ self.configs['log_reversed'] = \
599
+ [parser.get(cfg_sec, 'cfg_log_reversed'), 'Log file reversed']
600
+ self.configs['tempdir'] = \
601
+ [
602
+ None if parser.get(cfg_sec, 'cfg_tempdir') == '' \
603
+ else parser.get(cfg_sec, 'cfg_tempdir'),
604
+ 'Temporary directory for note storage'
605
+ ]
251
606
252
607
def get_config(self, name):
608
+ """Get a config value"""
253
609
return self.configs[name][0]
254
610
255
611
def get_config_descr(self, name):
612
+ """Get a config description"""
256
613
return self.configs[name][1]
257
614
258
615
def get_keybind(self, name):
616
+ """Get a keybinding value"""
259
617
return self.keybinds[name][0]
260
618
261
619
def get_keybind_use(self, name):
620
+ """Get the context(s) where a keybinding is valid"""
262
621
return self.keybinds[name][1]
263
622
264
623
def get_keybind_descr(self, name):
624
+ """Get a keybinding description"""
265
625
return self.keybinds[name][2]
266
626
267
627
def get_color(self, name):
628
+ """Get a color value"""
268
629
return self.colors[name][0]
269
630
270
631
def get_color_descr(self, name):
632
+ """Get a color description"""
271
633
return self.colors[name][1]
nncli/gui.py (created)
1
+# -*- coding: utf-8 -*-
2
+"""nncli_gui module"""
3
+import hashlib
4
+import subprocess
5
+import threading
6
+
7
+import urwid
8
+from . import view_titles, view_note, view_help, view_log, user_input
9
+from .utils import exec_cmd_on_note, get_pager
10
+
11
+# pylint: disable=too-many-instance-attributes, unused-argument
12
+class NncliGui:
13
+ """NncliGui class. Responsible for the console GUI view logic."""
14
+ def __init__(self, config, logger, ndb, key=None):
15
+ self.ndb = ndb
16
+ self.logger = logger
17
+ self.config = config
18
+ self.last_view = []
19
+ self.status_bar = self.config.get_config('status_bar')
20
+ self.config.state.current_sort_mode = \
21
+ self.config.get_config('sort_mode')
22
+
23
+
24
+ self.log_lock = threading.Lock()
25
+ self.log_alarms = 0
26
+ self.logs = []
27
+
28
+ self.thread_sync = threading.Thread(
29
+ target=self.ndb.sync_worker,
30
+ args=[self.config.state.do_server_sync]
31
+ )
32
+ self.thread_sync.setDaemon(True)
33
+
34
+ self.view_titles = \
35
+ view_titles.ViewTitles(
36
+ self.config,
37
+ {
38
+ 'ndb' : self.ndb,
39
+ 'search_string' : None,
40
+ 'log' : self.log
41
+ }
42
+ )
43
+ self.view_note = \
44
+ view_note.ViewNote(
45
+ self.config,
46
+ {
47
+ 'ndb' : self.ndb,
48
+ 'id' : key, # initial key to view or None
49
+ 'log' : self.log
50
+ }
51
+ )
52
+
53
+ self.view_log = view_log.ViewLog(self.config)
54
+ self.view_help = view_help.ViewHelp(self.config)
55
+
56
+ palette = \
57
+ [
58
+ (
59
+ 'default',
60
+ self.config.get_color('default_fg'),
61
+ self.config.get_color('default_bg')
62
+ ),
63
+ (
64
+ 'status_bar',
65
+ self.config.get_color('status_bar_fg'),
66
+ self.config.get_color('status_bar_bg')
67
+ ),
68
+ (
69
+ 'log',
70
+ self.config.get_color('log_fg'),
71
+ self.config.get_color('log_bg')
72
+ ),
73
+ (
74
+ 'user_input_bar',
75
+ self.config.get_color('user_input_bar_fg'),
76
+ self.config.get_color('user_input_bar_bg')
77
+ ),
78
+ (
79
+ 'note_focus',
80
+ self.config.get_color('note_focus_fg'),
81
+ self.config.get_color('note_focus_bg')
82
+ ),
83
+ (
84
+ 'note_title_day',
85
+ self.config.get_color('note_title_day_fg'),
86
+ self.config.get_color('note_title_day_bg')
87
+ ),
88
+ (
89
+ 'note_title_week',
90
+ self.config.get_color('note_title_week_fg'),
91
+ self.config.get_color('note_title_week_bg')
92
+ ),
93
+ (
94
+ 'note_title_month',
95
+ self.config.get_color('note_title_month_fg'),
96
+ self.config.get_color('note_title_month_bg')
97
+ ),
98
+ (
99
+ 'note_title_year',
100
+ self.config.get_color('note_title_year_fg'),
101
+ self.config.get_color('note_title_year_bg')
102
+ ),
103
+ (
104
+ 'note_title_ancient',
105
+ self.config.get_color('note_title_ancient_fg'),
106
+ self.config.get_color('note_title_ancient_bg')
107
+ ),
108
+ (
109
+ 'note_date',
110
+ self.config.get_color('note_date_fg'),
111
+ self.config.get_color('note_date_bg')
112
+ ),
113
+ (
114
+ 'note_flags',
115
+ self.config.get_color('note_flags_fg'),
116
+ self.config.get_color('note_flags_bg')
117
+ ),
118
+ (
119
+ 'note_category',
120
+ self.config.get_color('note_category_fg'),
121
+ self.config.get_color('note_category_bg')
122
+ ),
123
+ (
124
+ 'note_content',
125
+ self.config.get_color('note_content_fg'),
126
+ self.config.get_color('note_content_bg')
127
+ ),
128
+ (
129
+ 'note_content_focus',
130
+ self.config.get_color('note_content_focus_fg'),
131
+ self.config.get_color('note_content_focus_bg')
132
+ ),
133
+ (
134
+ 'note_content_old',
135
+ self.config.get_color('note_content_old_fg'),
136
+ self.config.get_color('note_content_old_bg')
137
+ ),
138
+ (
139
+ 'note_content_old_focus',
140
+ self.config.get_color(
141
+ 'note_content_old_focus_fg'
142
+ ),
143
+ self.config.get_color(
144
+ 'note_content_old_focus_bg'
145
+ )
146
+ ),
147
+ (
148
+ 'help_focus',
149
+ self.config.get_color('help_focus_fg'),
150
+ self.config.get_color('help_focus_bg')
151
+ ),
152
+ (
153
+ 'help_header',
154
+ self.config.get_color('help_header_fg'),
155
+ self.config.get_color('help_header_bg')
156
+ ),
157
+ (
158
+ 'help_config',
159
+ self.config.get_color('help_config_fg'),
160
+ self.config.get_color('help_config_bg')
161
+ ),
162
+ (
163
+ 'help_value',
164
+ self.config.get_color('help_value_fg'),
165
+ self.config.get_color('help_value_bg')
166
+ ),
167
+ (
168
+ 'help_descr',
169
+ self.config.get_color('help_descr_fg'),
170
+ self.config.get_color('help_descr_bg')
171
+ )
172
+ ]
173
+
174
+ self.master_frame = urwid.Frame(
175
+ body=urwid.Filler(urwid.Text('')),
176
+ header=None,
177
+ footer=urwid.Pile([urwid.Pile([]), urwid.Pile([])]),
178
+ focus_part='body')
179
+
180
+ self.nncli_loop = urwid.MainLoop(self.master_frame,
181
+ palette,
182
+ handle_mouse=False)
183
+
184
+ self.nncli_loop.set_alarm_in(0, self._gui_init_view, \
185
+ True if key else False)
186
+
187
+ def run(self):
188
+ """Run the GUI"""
189
+ self.nncli_loop.run()
190
+
191
+ def _gui_header_clear(self):
192
+ """Clear the console GUI header row"""
193
+ self.master_frame.contents['header'] = (None, None)
194
+ self.nncli_loop.draw_screen()
195
+
196
+ def _gui_header_set(self, widget):
197
+ """Set the content of the console GUI header row"""
198
+ self.master_frame.contents['header'] = (widget, None)
199
+ self.nncli_loop.draw_screen()
200
+
201
+ def _gui_footer_log_clear(self):
202
+ """Clear the log at the bottom of the GUI"""
203
+ gui = self._gui_footer_input_get()
204
+ self.master_frame.contents['footer'] = \
205
+ (urwid.Pile([urwid.Pile([]), urwid.Pile([gui])]), None)
206
+ self.nncli_loop.draw_screen()
207
+
208
+ def _gui_footer_log_set(self, pile):
209
+ """Set the log at the bottom of the GUI"""
210
+ gui = self._gui_footer_input_get()
211
+ self.master_frame.contents['footer'] = \
212
+ (urwid.Pile([urwid.Pile(pile), urwid.Pile([gui])]), None)
213
+ self.nncli_loop.draw_screen()
214
+
215
+ def _gui_footer_log_get(self):
216
+ """Get the log at the bottom of the GUI"""
217
+ return self.master_frame.contents['footer'][0].contents[0][0]
218
+
219
+ def _gui_footer_input_clear(self):
220
+ """Clear the input at the bottom of the GUI"""
221
+ pile = self._gui_footer_log_get()
222
+ self.master_frame.contents['footer'] = \
223
+ (urwid.Pile([urwid.Pile([pile]), urwid.Pile([])]), None)
224
+ self.nncli_loop.draw_screen()
225
+
226
+ def _gui_footer_input_set(self, gui):
227
+ """Set the input at the bottom of the GUI"""
228
+ pile = self._gui_footer_log_get()
229
+ self.master_frame.contents['footer'] = \
230
+ (urwid.Pile([urwid.Pile([pile]), urwid.Pile([gui])]), None)
231
+ self.nncli_loop.draw_screen()
232
+
233
+ def _gui_footer_input_get(self):
234
+ """Get the input at the bottom of the GUI"""
235
+ return self.master_frame.contents['footer'][0].contents[1][0]
236
+
237
+ def _gui_footer_focus_input(self):
238
+ """Set the GUI focus to the input at the bottom of the GUI"""
239
+ self.master_frame.focus_position = 'footer'
240
+ self.master_frame.contents['footer'][0].focus_position = 1
241
+
242
+ def _gui_body_set(self, widget):
243
+ """Set the GUI body"""
244
+ self.master_frame.contents['body'] = (widget, None)
245
+ self._gui_update_status_bar()
246
+ self.nncli_loop.draw_screen()
247
+
248
+ def gui_body_get(self):
249
+ """Get the GUI body"""
250
+ return self.master_frame.contents['body'][0]
251
+
252
+ def _gui_body_focus(self):
253
+ """Set the GUI focus to the body"""
254
+ self.master_frame.focus_position = 'body'
255
+
256
+ def gui_update_view(self):
257
+ """Update the GUI"""
258
+ if not self.config.state.do_gui:
259
+ return
260
+
261
+ try:
262
+ cur_key = self.view_titles.note_list \
263
+ [self.view_titles.focus_position].note['localkey']
264
+ except IndexError:
265
+ cur_key = None
266
+
267
+ self.view_titles.update_note_list(
268
+ self.view_titles.search_string,
269
+ sort_mode=self.config.state.current_sort_mode
270
+ )
271
+ self.view_titles.focus_note(cur_key)
272
+
273
+ if self.gui_body_get().__class__ == view_note.ViewNote:
274
+ self.view_note.update_note_view()
275
+
276
+ self._gui_update_status_bar()
277
+
278
+ def _gui_update_status_bar(self):
279
+ """Update the GUI status bar"""
280
+ if self.status_bar != 'yes':
281
+ self._gui_header_clear()
282
+ else:
283
+ self._gui_header_set(self.gui_body_get().get_status_bar())
284
+
285
+ def _gui_switch_frame_body(self, new_view, save_current_view=True):
286
+ """
287
+ Switch the body frame of the GUI. Used to switch to a new
288
+ view
289
+ """
290
+ if new_view is None:
291
+ if not self.last_view:
292
+ self._gui_stop()
293
+ else:
294
+ self._gui_body_set(self.last_view.pop())
295
+ else:
296
+ if self.gui_body_get().__class__ != new_view.__class__:
297
+ if save_current_view:
298
+ self.last_view.append(self.gui_body_get())
299
+ self._gui_body_set(new_view)
300
+
301
+ def _delete_note_callback(self, key, delete):
302
+ """Update the GUI after deleting a note"""
303
+ if not delete:
304
+ return
305
+ self.ndb.set_note_deleted(key, True)
306
+
307
+ if self.gui_body_get().__class__ == view_titles.ViewTitles:
308
+ self.view_titles.update_note_title()
309
+
310
+ self._gui_update_status_bar()
311
+ self.ndb.sync_worker_go()
312
+
313
+ def _gui_yes_no_input(self, args, yes_no):
314
+ """Create a yes/no input dialog at the GUI footer"""
315
+ self._gui_footer_input_clear()
316
+ self._gui_body_focus()
317
+ self.master_frame.keypress = self._gui_frame_keypress
318
+ args[0](args[1],
319
+ True if yes_no in ['YES', 'Yes', 'yes', 'Y', 'y'] \
320
+ else False
321
+ )
322
+
323
+ def _gui_search_input(self, args, search_string):
324
+ """Create a search input dialog at the GUI footer"""
325
+ self._gui_footer_input_clear()
326
+ self._gui_body_focus()
327
+ self.master_frame.keypress = self._gui_frame_keypress
328
+ if search_string:
329
+ if self.gui_body_get() == self.view_note:
330
+ self.config.state.search_direction = args[1]
331
+ self.view_note.search_note_view_next(
332
+ search_string=search_string,
333
+ search_mode=args[0]
334
+ )
335
+ else:
336
+ self.view_titles.update_note_list(
337
+ search_string,
338
+ args[0],
339
+ sort_mode=self.config.state.current_sort_mode
340
+ )
341
+ self._gui_body_set(self.view_titles)
342
+
343
+ def _gui_category_input(self, args, category):
344
+ """Create a category input at the GUI footer"""
345
+ self._gui_footer_input_clear()
346
+ self._gui_body_focus()
347
+ self.master_frame.keypress = self._gui_frame_keypress
348
+ if category is not None:
349
+ if self.gui_body_get().__class__ == view_titles.ViewTitles:
350
+ note = self.view_titles.note_list \
351
+ [self.view_titles.focus_position].note
352
+ else: # self.gui_body_get().__class__ == view_note.ViewNote:
353
+ note = self.view_note.note
354
+
355
+ self.ndb.set_note_category(note['localkey'], category)
356
+
357
+ if self.gui_body_get().__class__ == view_titles.ViewTitles:
358
+ self.view_titles.update_note_title()
359
+ else: # self.gui_body_get().__class__ == view_note.ViewNote:
360
+ self.view_note.update_note_view()
361
+
362
+ self._gui_update_status_bar()
363
+ self.ndb.sync_worker_go()
364
+
365
+ def _gui_pipe_input(self, args, cmd):
366
+ """Create a pipe input dialog at the GUI footoer"""
367
+ self._gui_footer_input_clear()
368
+ self._gui_body_focus()
369
+ self.master_frame.keypress = self._gui_frame_keypress
370
+ if cmd is not None:
371
+ if self.gui_body_get().__class__ == view_titles.ViewTitles:
372
+ note = self.view_titles.note_list \
373
+ [self.view_titles.focus_position].note
374
+ else: # self.gui_body_get().__class__ == view_note.ViewNote:
375
+ note = self.view_note.old_note \
376
+ if self.view_note.old_note \
377
+ else self.view_note.note
378
+ try:
379
+ self._gui_clear()
380
+ pipe = subprocess.Popen(cmd, stdin=subprocess.PIPE, shell=True)
381
+ pipe.communicate(note['content'].encode('utf-8'))
382
+ pipe.stdin.close()
383
+ pipe.wait()
384
+ except OSError as ex:
385
+ self.log('Pipe error: %s' % ex)
386
+ finally:
387
+ self._gui_reset()
388
+
389
+ # pylint: disable=too-many-return-statements, too-many-branches
390
+ # pylint: disable=too-many-statements
391
+ def _gui_frame_keypress(self, size, key):
392
+ """Keypress handler for the GUI"""
393
+ # convert space character into name
394
+ if key == ' ':
395
+ key = 'space'
396
+
397
+ contents = self.gui_body_get()
398
+
399
+ if key == self.config.get_keybind('quit'):
400
+ self._gui_switch_frame_body(None)
401
+
402
+ elif key == self.config.get_keybind('help'):
403
+ self._gui_switch_frame_body(self.view_help)
404
+
405
+ elif key == self.config.get_keybind('sync'):
406
+ self.ndb.last_sync = 0
407
+ self.ndb.sync_worker_go()
408
+
409
+ elif key == self.config.get_keybind('view_log'):
410
+ self.view_log.update_log()
411
+ self._gui_switch_frame_body(self.view_log)
412
+
413
+ elif key == self.config.get_keybind('down'):
414
+ if not contents.body.positions():
415
+ return None
416
+ last = len(contents.body.positions())
417
+ if contents.focus_position == (last - 1):
418
+ return None
419
+ contents.focus_position += 1
420
+ contents.render(size)
421
+
422
+ elif key == self.config.get_keybind('up'):
423
+ if not contents.body.positions():
424
+ return None
425
+ if contents.focus_position == 0:
426
+ return None
427
+ contents.focus_position -= 1
428
+ contents.render(size)
429
+
430
+ elif key == self.config.get_keybind('page_down'):
431
+ if not contents.body.positions():
432
+ return None
433
+ last = len(contents.body.positions())
434
+ next_focus = contents.focus_position + size[1]
435
+ if next_focus >= last:
436
+ next_focus = last - 1
437
+ contents.change_focus(size, next_focus,
438
+ offset_inset=0,
439
+ coming_from='above')
440
+
441
+ elif key == self.config.get_keybind('page_up'):
442
+ if not contents.body.positions():
443
+ return None
444
+ if 'bottom' in contents.ends_visible(size):
445
+ last = len(contents.body.positions())
446
+ next_focus = last - size[1] - size[1]
447
+ else:
448
+ next_focus = contents.focus_position - size[1]
449
+ if next_focus < 0:
450
+ next_focus = 0
451
+ contents.change_focus(size, next_focus,
452
+ offset_inset=0,
453
+ coming_from='below')
454
+
455
+ elif key == self.config.get_keybind('half_page_down'):
456
+ if not contents.body.positions():
457
+ return None
458
+ last = len(contents.body.positions())
459
+ next_focus = contents.focus_position + (size[1] // 2)
460
+ if next_focus >= last:
461
+ next_focus = last - 1
462
+ contents.change_focus(size, next_focus,
463
+ offset_inset=0,
464
+ coming_from='above')
465
+
466
+ elif key == self.config.get_keybind('half_page_up'):
467
+ if not contents.body.positions():
468
+ return None
469
+ if 'bottom' in contents.ends_visible(size):
470
+ last = len(contents.body.positions())
471
+ next_focus = last - size[1] - (size[1] // 2)
472
+ else:
473
+ next_focus = contents.focus_position - (size[1] // 2)
474
+ if next_focus < 0:
475
+ next_focus = 0
476
+ contents.change_focus(size, next_focus,
477
+ offset_inset=0,
478
+ coming_from='below')
479
+
480
+ elif key == self.config.get_keybind('bottom'):
481
+ if not contents.body.positions():
482
+ return None
483
+ contents.change_focus(size, (len(contents.body.positions()) - 1),
484
+ offset_inset=0,
485
+ coming_from='above')
486
+
487
+ elif key == self.config.get_keybind('top'):
488
+ if not contents.body.positions():
489
+ return None
490
+ contents.change_focus(size, 0,
491
+ offset_inset=0,
492
+ coming_from='below')
493
+
494
+ elif key == self.config.get_keybind('view_next_note'):
495
+ if self.gui_body_get().__class__ != view_note.ViewNote:
496
+ return key
497
+
498
+ if not self.view_titles.body.positions():
499
+ return None
500
+ last = len(self.view_titles.body.positions())
501
+ if self.view_titles.focus_position == (last - 1):
502
+ return None
503
+ self.view_titles.focus_position += 1
504
+ contents.update_note_view(
505
+ self.view_titles. \
506
+ note_list[self.view_titles. \
507
+ focus_position].note['localkey']
508
+ )
509
+ self._gui_switch_frame_body(self.view_note)
510
+
511
+ elif key == self.config.get_keybind('view_prev_note'):
512
+ if self.gui_body_get().__class__ != view_note.ViewNote:
513
+ return key
514
+
515
+ if not self.view_titles.body.positions():
516
+ return None
517
+ if self.view_titles.focus_position == 0:
518
+ return None
519
+ self.view_titles.focus_position -= 1
520
+ contents.update_note_view(
521
+ self.view_titles. \
522
+ note_list[self.view_titles. \
523
+ focus_position].note['localkey']
524
+ )
525
+ self._gui_switch_frame_body(self.view_note)
526
+
527
+ elif key == self.config.get_keybind('status'):
528
+ if self.status_bar == 'yes':
529
+ self.status_bar = 'no'
530
+ else:
531
+ self.status_bar = self.config.get_config('status_bar')
532
+
533
+ elif key == self.config.get_keybind('create_note'):
534
+ if self.gui_body_get().__class__ != view_titles.ViewTitles:
535
+ return key
536
+
537
+ self._gui_clear()
538
+ content = exec_cmd_on_note(None, self.config, self, self.logger)
539
+ self._gui_reset()
540
+
541
+ if content:
542
+ self.log('New note created')
543
+ self.ndb.create_note(content)
544
+ self.gui_update_view()
545
+ self.ndb.sync_worker_go()
546
+
547
+ elif key == self.config.get_keybind('edit_note') or \
548
+ key == self.config.get_keybind('view_note_ext') or \
549
+ key == self.config.get_keybind('view_note_json'):
550
+ if self.gui_body_get().__class__ != view_titles.ViewTitles and \
551
+ self.gui_body_get().__class__ != view_note.ViewNote:
552
+ return key
553
+
554
+ if self.gui_body_get().__class__ == view_titles.ViewTitles:
555
+ if not contents.body.positions():
556
+ return None
557
+ note = contents.note_list[contents.focus_position].note
558
+ else: # self.gui_body_get().__class__ == view_note.ViewNote:
559
+ if key == self.config.get_keybind('edit_note'):
560
+ note = contents.note
561
+ else:
562
+ note = contents.old_note if contents.old_note \
563
+ else contents.note
564
+
565
+ self._gui_clear()
566
+ if key == self.config.get_keybind('edit_note'):
567
+ content = exec_cmd_on_note(note, self.config, self,
568
+ self.logger)
569
+ elif key == self.config.get_keybind('view_note_ext'):
570
+ content = exec_cmd_on_note(
571
+ note,
572
+ self.config,
573
+ self,
574
+ self.logger,
575
+ cmd=get_pager(self.config, self.logger))
576
+ else: # key == self.config.get_keybind('view_note_json')
577
+ content = exec_cmd_on_note(
578
+ note,
579
+ self.config,
580
+ self,
581
+ self.logger,
582
+ cmd=get_pager(self.config, self.logger),
583
+ raw=True
584
+ )
585
+
586
+ self._gui_reset()
587
+
588
+ if not content:
589
+ return None
590
+
591
+ md5_old = hashlib.md5(note['content'].encode('utf-8')).digest()
592
+ md5_new = hashlib.md5(content.encode('utf-8')).digest()
593
+
594
+ if md5_old != md5_new:
595
+ self.log('Note updated')
596
+ self.ndb.set_note_content(note['localkey'], content)
597
+ if self.gui_body_get().__class__ == view_titles.ViewTitles:
598
+ contents.update_note_title()
599
+ else: # self.gui_body_get().__class__ == view_note.ViewNote:
600
+ contents.update_note_view()
601
+ self.ndb.sync_worker_go()
602
+ else:
603
+ self.log('Note unchanged')
604
+
605
+ elif key == self.config.get_keybind('view_note'):
606
+ if self.gui_body_get().__class__ != view_titles.ViewTitles:
607
+ return key
608
+
609
+ if not contents.body.positions():
610
+ return None
611
+ self.view_note.update_note_view(
612
+ contents.note_list[contents.focus_position]. \
613
+ note['localkey'])
614
+ self._gui_switch_frame_body(self.view_note)
615
+
616
+ elif key == self.config.get_keybind('pipe_note'):
617
+ if self.gui_body_get().__class__ != view_titles.ViewTitles and \
618
+ self.gui_body_get().__class__ != view_note.ViewNote:
619
+ return key
620
+
621
+ if self.gui_body_get().__class__ == view_titles.ViewTitles:
622
+ if not contents.body.positions():
623
+ return None
624
+ note = contents.note_list[contents.focus_position].note
625
+ else: # self.gui_body_get().__class__ == view_note.ViewNote:
626
+ note = contents.old_note if contents.old_note else contents.note
627
+
628
+ self._gui_footer_input_set(
629
+ urwid.AttrMap(
630
+ user_input.UserInput(
631
+ self.config,
632
+ key,
633
+ '',
634
+ self._gui_pipe_input,
635
+ None
636
+ ),
637
+ 'user_input_bar'
638
+ )
639
+ )
640
+ self._gui_footer_focus_input()
641
+ self.master_frame.keypress = \
642
+ self._gui_footer_input_get().keypress
643
+
644
+ elif key == self.config.get_keybind('note_delete'):
645
+ if self.gui_body_get().__class__ != view_titles.ViewTitles and \
646
+ self.gui_body_get().__class__ != view_note.ViewNote:
647
+ return key
648
+
649
+ if self.gui_body_get().__class__ == view_titles.ViewTitles:
650
+ if not contents.body.positions():
651
+ return None
652
+ note = contents.note_list[contents.focus_position].note
653
+ else: # self.gui_body_get().__class__ == view_note.ViewNote:
654
+ note = contents.note
655
+
656
+ self._gui_footer_input_set(
657
+ urwid.AttrMap(
658
+ user_input.UserInput(
659
+ self.config,
660
+ 'Delete (y/n): ',
661
+ '',
662
+ self._gui_yes_no_input,
663
+ [
664
+ self._delete_note_callback,
665
+ note['localkey']
666
+ ]
667
+ ),
668
+ 'user_input_bar'
669
+ )
670
+ )
671
+ self._gui_footer_focus_input()
672
+ self.master_frame.keypress = \
673
+ self._gui_footer_input_get().keypress
674
+
675
+ elif key == self.config.get_keybind('note_favorite'):
676
+ if self.gui_body_get().__class__ != view_titles.ViewTitles and \
677
+ self.gui_body_get().__class__ != view_note.ViewNote:
678
+ return key
679
+
680
+ if self.gui_body_get().__class__ == view_titles.ViewTitles:
681
+ if not contents.body.positions():
682
+ return None
683
+ note = contents.note_list[contents.focus_position].note
684
+ else: # self.gui_body_get().__class__ == view_note.ViewNote:
685
+ note = contents.note
686
+
687
+ favorite = not note['favorite']
688
+
689
+ self.ndb.set_note_favorite(note['localkey'], favorite)
690
+
691
+ if self.gui_body_get().__class__ == view_titles.ViewTitles:
692
+ contents.update_note_title()
693
+
694
+ self.ndb.sync_worker_go()
695
+
696
+ elif key == self.config.get_keybind('note_category'):
697
+ if self.gui_body_get().__class__ != view_titles.ViewTitles and \
698
+ self.gui_body_get().__class__ != view_note.ViewNote:
699
+ return key
700
+
701
+ if self.gui_body_get().__class__ == view_titles.ViewTitles:
702
+ if not contents.body.positions():
703
+ return None
704
+ note = contents.note_list[contents.focus_position].note
705
+ else: # self.gui_body_get().__class__ == view_note.ViewNote:
706
+ note = contents.note
707
+
708
+ self._gui_footer_input_set(
709
+ urwid.AttrMap(
710
+ user_input.UserInput(
711
+ self.config,
712
+ 'Category: ',
713
+ note['category'],
714
+ self._gui_category_input,
715
+ None
716
+ ),
717
+ 'user_input_bar'
718
+ )
719
+ )
720
+ self._gui_footer_focus_input()
721
+ self.master_frame.keypress = \
722
+ self._gui_footer_input_get().keypress
723
+
724
+ elif key == self.config.get_keybind('search_gstyle') or \
725
+ key == self.config.get_keybind('search_regex') or \
726
+ key == self.config.get_keybind('search_prev_gstyle') or \
727
+ key == self.config.get_keybind('search_prev_regex'):
728
+ if self.gui_body_get().__class__ != view_titles.ViewTitles and \
729
+ self.gui_body_get().__class__ != view_note.ViewNote:
730
+ return key
731
+
732
+ if self.gui_body_get().__class__ == view_note.ViewNote:
733
+ if key == self.config.get_keybind('search_prev_gstyle') or \
734
+ key == self.config.get_keybind('search_prev_regex'):
735
+ self.view_note.search_direction = 'backward'
736
+ else:
737
+ self.view_note.search_direction = 'forward'
738
+
739
+ options = [
740
+ 'gstyle' if key == self.config.get_keybind('search_gstyle')
741
+ or key == self.config.get_keybind('search_prev_gstyle')
742
+ else 'regex',
743
+ 'backward' if key ==
744
+ self.config.get_keybind('search_prev_gstyle')
745
+ or key == self.config.get_keybind('search_prev_regex')
746
+ else 'forward'
747
+ ]
748
+
749
+ caption = '{}{}'.format('(regex) '
750
+ if options[0] == 'regex'
751
+ else '',
752
+ '/' if options[1] == 'forward'
753
+ else '?')
754
+
755
+ self._gui_footer_input_set(
756
+ urwid.AttrMap(
757
+ user_input.UserInput(
758
+ self.config,
759
+ caption,
760
+ '',
761
+ self._gui_search_input,
762
+ options
763
+ ),
764
+ 'user_input_bar'
765
+ )
766
+ )
767
+ self._gui_footer_focus_input()
768
+ self.master_frame.keypress = \
769
+ self._gui_footer_input_get().keypress
770
+
771
+ elif key == self.config.get_keybind('search_next'):
772
+ if self.gui_body_get().__class__ != view_note.ViewNote:
773
+ return key
774
+
775
+ self.view_note.search_note_view_next()
776
+
777
+ elif key == self.config.get_keybind('search_prev'):
778
+ if self.gui_body_get().__class__ != view_note.ViewNote:
779
+ return key
780
+
781
+ self.view_note.search_note_view_prev()
782
+
783
+ elif key == self.config.get_keybind('clear_search'):
784
+ if self.gui_body_get().__class__ != view_titles.ViewTitles:
785
+ return key
786
+
787
+ self.view_titles.update_note_list(
788
+ None,
789
+ sort_mode=self.config.state.current_sort_mode
790
+ )
791
+ self._gui_body_set(self.view_titles)
792
+
793
+ elif key == self.config.get_keybind('sort_date'):
794
+ if self.gui_body_get().__class__ != view_titles.ViewTitles:
795
+ return key
796
+
797
+ self.config.state.current_sort_mode = 'date'
798
+ self.view_titles.sort_note_list('date')
799
+
800
+ elif key == self.config.get_keybind('sort_alpha'):
801
+ if self.gui_body_get().__class__ != view_titles.ViewTitles:
802
+ return key
803
+
804
+ self.config.state.current_sort_mode = 'alpha'
805
+ self.view_titles.sort_note_list('alpha')
806
+
807
+ elif key == self.config.get_keybind('sort_categories'):
808
+ if self.gui_body_get().__class__ != view_titles.ViewTitles:
809
+ return key
810
+
811
+ self.config.state.current_sort_mode = 'categories'
812
+ self.view_titles.sort_note_list('categories')
813
+
814
+ elif key == self.config.get_keybind('copy_note_text'):
815
+ if self.gui_body_get().__class__ != view_note.ViewNote:
816
+ return key
817
+
818
+ self.view_note.copy_note_text()
819
+
820
+ else:
821
+ return contents.keypress(size, key)
822
+
823
+ self._gui_update_status_bar()
824
+ return None
825
+
826
+ def _gui_init_view(self, loop, show_note):
827
+ """Initialize the GUI"""
828
+ self.master_frame.keypress = self._gui_frame_keypress
829
+ self._gui_body_set(self.view_titles)
830
+
831
+ if show_note:
832
+ # note that title view set first to prime the view stack
833
+ self._gui_switch_frame_body(self.view_note)
834
+
835
+ self.thread_sync.start()
836
+
837
+ def _gui_clear(self):
838
+ """Clear the GUI"""
839
+ self.nncli_loop.widget = urwid.Filler(urwid.Text(''))
840
+ self.nncli_loop.draw_screen()
841
+
842
+ def _gui_reset(self):
843
+ """Reset the GUI"""
844
+ self.nncli_loop.widget = self.master_frame
845
+ self.nncli_loop.draw_screen()
846
+
847
+ def _gui_stop(self):
848
+ """Stop the GUI"""
849
+ # don't exit if there are any notes not yet saved to the disk
850
+
851
+ # NOTE: this was originally causing hangs on exit with urllib2
852
+ # should not be a problem now since using the requests library
853
+ # ref https://github.com/insanum/sncli/issues/18#issuecomment-105517773
854
+ if self.ndb.verify_all_saved():
855
+ # clear the screen and exit the urwid run loop
856
+ self._gui_clear()
857
+ raise urwid.ExitMainLoop()
858
+ else:
859
+ self.log('WARNING: Not all notes saved'
860
+ 'to disk (wait for sync worker)')
861
+
862
+ def log(self, msg):
863
+ """Log as message, displaying to the user as appropriate"""
864
+ self.logger.log(msg)
865
+
866
+ self.log_lock.acquire()
867
+
868
+ self.log_alarms += 1
869
+ self.logs.append(msg)
870
+
871
+ if len(self.logs) > int(self.config.get_config('max_logs')):
872
+ self.log_alarms -= 1
873
+ self.logs.pop(0)
874
+
875
+ log_pile = []
876
+ for log in self.logs:
877
+ log_pile.append(urwid.AttrMap(urwid.Text(log), 'log'))
878
+
879
+ if self.config.state.verbose:
880
+ self._gui_footer_log_set(log_pile)
881
+
882
+ self.nncli_loop.set_alarm_in(
883
+ int(self.config.get_config('log_timeout')),
884
+ self._log_timeout, None)
885
+
886
+ self.log_lock.release()
887
+
888
+ def _log_timeout(self, loop, arg):
889
+ """
890
+ Run periodically to check for new log entries to append to
891
+ the GUI footer
892
+ """
893
+ self.log_lock.acquire()
894
+
895
+ self.log_alarms -= 1
896
+
897
+ if self.log_alarms == 0:
898
+ self._gui_footer_log_clear()
899
+ self.logs = []
900
+ else:
901
+ # for some reason having problems with this being empty?
902
+ if not self.logs:
903
+ self.logs.pop(0)
904
+
905
+ log_pile = []
906
+
907
+ for log in self.logs:
908
+ log_pile.append(urwid.AttrMap(urwid.Text(log), 'log'))
909
+
910
+ if self.config.state.verbose:
911
+ self._gui_footer_log_set(log_pile)
912
+
913
+ self.log_lock.release()
nncli/log.py (created)
1
+# -*- coding: utf-8 -*-
2
+"""log module"""
3
+import logging
4
+from logging.handlers import RotatingFileHandler
5
+
6
+import os
7
+
8
+# pylint: disable=unused-argument, too-few-public-methods
9
+class Logger:
10
+ """Handles logging for the application"""
11
+ def __init__(self, config):
12
+ self.config = config
13
+ self.logfile = os.path.join(
14
+ config.get_config('db_path'),
15
+ 'nncli.log'
16
+ )
17
+ self.loghandler = RotatingFileHandler(
18
+ self.logfile,
19
+ maxBytes=100000,
20
+ backupCount=1
21
+ )
22
+ self.loghandler.setLevel(logging.DEBUG)
23
+ self.loghandler.setFormatter(
24
+ logging.Formatter(
25
+ fmt='%(asctime)s [%(levelname)s] %(message)s'
26
+ )
27
+ )
28
+ self.logger = logging.getLogger()
29
+ self.logger.setLevel(logging.DEBUG)
30
+ self.logger.addHandler(self.loghandler)
31
+
32
+ logging.debug('nncli logging initialized')
33
+
34
+ def log(self, msg):
35
+ """Log as message, displaying to the user as appropriate"""
36
+ logging.debug(msg)
37
+
38
+ if not self.config.state.do_gui:
39
+ if self.config.state.verbose:
40
+ print(msg)
nncli/nextcloud_note.py
1
1
# -*- coding: utf-8 -*-
2
-
3
-from requests.exceptions import RequestException, ConnectionError
4
-import time
5
-import datetime
2
+"""nextcloud_note module"""
6
3
import logging
7
-import requests
4
+import time
8
5
import traceback
9
6
10
-try:
11
- import json
12
-except ImportError:
13
- try:
14
- import simplejson as json
15
- except ImportError:
16
- # For Google AppEngine
17
- from django.utils import simplejson as json
7
+import requests
8
+from requests.exceptions import RequestException
18
9
19
-class NextcloudNote(object):
10
+class NextcloudNote:
20
11
""" Class for interacting with the NextCloud Notes web service """
21
12
22
13
def __init__(self, username, password, host):
. . .
49
40
res.raise_for_status()
50
41
note = res.json()
51
42
self.status = 'online'
52
- except ConnectionError as e:
43
+ except ConnectionError as ex:
53
44
self.status = 'offline, connection error'
54
- return e, -1
55
- except RequestException as e:
45
+ return ex, -1
46
+ except RequestException as ex:
56
47
# logging.debug('RESPONSE ERROR: ' + str(e))
57
- return e, -1
58
- except ValueError as e:
59
- return e, -1
48
+ return ex, -1
49
+ except ValueError as ex:
50
+ return ex, -1
60
51
61
52
# # use UTF-8 encoding
62
53
# note["content"] = note["content"].encode('utf-8')
. . .
96
87
97
88
#logging.debug('REQUEST: ' + url + ' - ' + str(note))
98
89
try:
99
- logging.debug('NOTE: ' + str(note))
90
+ logging.debug('NOTE: %s', note)
100
91
if url != self.url:
101
- res = requests.put(url, auth=(self.username,
102
- self.password), json=note)
92
+ res = requests.put(
93
+ url,
94
+ auth=(self.username, self.password),
95
+ json=note
96
+ )
103
97
else:
104
- res = requests.post(url, auth=(self.username,
105
- self.password), json=note)
98
+ res = requests.post(
99
+ url, auth=(self.username, self.password),
100
+ json=note
101
+ )
106
102
note = res.json()
107
103
res.raise_for_status()
108
- logging.debug('NOTE (from response): ' + str(res.json()))
104
+ logging.debug('NOTE (from response): %s', res.json())
109
105
self.status = 'online'
110
- except ConnectionError as e:
106
+ except ConnectionError as ex:
111
107
self.status = 'offline, connection error'
112
- return e, -1
113
- except RequestException as e:
114
- logging.debug('RESPONSE ERROR: ' + str(e))
108
+ raise ex
109
+ except RequestException as ex:
110
+ logging.debug('RESPONSE ERROR: %s', ex)
115
111
logging.debug(traceback.print_exc())
116
112
self.status = 'error updating note, check log'
117
- return e, -1
118
- except ValueError as e:
119
- return e, -1
113
+ raise ex
114
+ except ValueError as ex:
115
+ raise ex
120
116
#logging.debug('RESPONSE OK: ' + str(note))
121
117
return note, 0
122
118
. . .
149
145
150
146
# perform initial HTTP request
151
147
try:
152
- logging.debug('REQUEST: ' + self.url + \
153
- '?exclude=content')
154
- res = requests.get(self.url, auth=(self.username, self.password), params=params)
148
+ logging.debug('REQUEST: %s', self.url + '?exclude=content')
149
+ res = requests.get(
150
+ self.url,
151
+ auth=(self.username, self.password),
152
+ params=params
153
+ )
155
154
res.raise_for_status()
156
155
#logging.debug('RESPONSE OK: ' + str(res))
157
156
note_list = res.json()
158
157
self.status = 'online'
159
- except ConnectionError as e:
158
+ except ConnectionError:
160
159
logging.exception('connection error')
161
160
self.status = 'offline, connection error'
162
161
status = -1
163
- except RequestException as e:
162
+ except RequestException:
164
163
# if problem with network request/response
165
164
logging.exception('request error')
166
165
status = -1
167
- except ValueError as e:
166
+ except ValueError:
168
167
# if invalid json data
169
168
status = -1
170
169
logging.exception('request returned bad JSON data')
. . .
194
193
url = '{}/{}'.format(self.url, str(note['id']))
195
194
196
195
try:
197
- logging.debug('REQUEST DELETE: ' + url)
196
+ logging.debug('REQUEST DELETE: %s', url)
198
197
res = requests.delete(url, auth=(self.username, self.password))
199
198
res.raise_for_status()
200
199
self.status = 'online'
201
- except ConnectionError as e:
200
+ except ConnectionError as ex:
202
201
self.status = 'offline, connection error'
203
- return e, -1
204
- except RequestException as e:
205
- return e, -1
202
+ raise ex
203
+ except RequestException as ex:
204
+ raise ex
206
205
return {}, 0
nncli/nncli.py
1
1
# -*- coding: utf-8 -*-
2
+"""nncli module"""
3
+import hashlib
4
+import json
5
+import os
6
+import signal
7
+import sys
8
+import time
2
9
3
-import os, sys, getopt, re, signal, time, datetime, shlex, hashlib
4
-import subprocess, threading, logging
5
-import copy, json, urwid, datetime
6
-from . import view_titles, view_note, view_help, view_log, user_input
7
-from . import utils, temp, __version__
10
+from . import utils, __version__
8
11
from .config import Config
9
-from .nextcloud_note import NextcloudNote
12
+from .gui import NncliGui
13
+from .log import Logger
10
14
from .notes_db import NotesDB, ReadError, WriteError
11
-from logging.handlers import RotatingFileHandler
15
+from .utils import exec_cmd_on_note
12
16
17
+# pylint: disable=unused-argument
13
18
class Nncli:
14
-
19
+ """Nncli class. Responsible for most of the application logic"""
15
20
def __init__(self, do_server_sync, verbose=False, config_file=None):
16
- self.config = Config(config_file)
17
- self.do_server_sync = do_server_sync
18
- self.verbose = verbose
19
- self.do_gui = False
20
- force_full_sync = False
21
- self.current_sort_mode = self.config.get_config('sort_mode')
22
-
23
- self.tempdir = self.config.get_config('tempdir')
24
- if self.tempdir == '':
25
- self.tempdir = None
21
+ self.config = Config(config_file)
22
+ self.config.state.do_server_sync = do_server_sync
23
+ self.config.state.verbose = verbose
24
+ force_full_sync = False
26
25
27
26
if not os.path.exists(self.config.get_config('db_path')):
28
27
os.mkdir(self.config.get_config('db_path'))
29
28
force_full_sync = True
30
29
31
- # configure the logging module
32
- self.logfile = os.path.join(self.config.get_config('db_path'), 'nncli.log')
33
- self.loghandler = RotatingFileHandler(self.logfile, maxBytes=100000, backupCount=1)
34
- self.loghandler.setLevel(logging.DEBUG)
35
- self.loghandler.setFormatter(logging.Formatter(fmt='%(asctime)s [%(levelname)s] %(message)s'))
36
- self.logger = logging.getLogger()
37
- self.logger.setLevel(logging.DEBUG)
38
- self.logger.addHandler(self.loghandler)
39
- self.config.logfile = self.logfile
40
-
41
- logging.debug('nncli logging initialized')
42
-
43
- self.logs = []
30
+ self.logger = Logger(self.config)
44
31
45
32
try:
46
- self.ndb = NotesDB(self.config, self.log, self.gui_update_view)
47
- except Exception as e:
48
- self.log(str(e))
33
+ self.ndb = NotesDB(
34
+ self.config,
35
+ self.logger.log
36
+ )
37
+ except (ReadError, WriteError) as ex:
38
+ self.logger.log(str(ex))
49
39
sys.exit(1)
40
+
41
+ self.nncli_gui = NncliGui(self.config, self.logger, self.ndb)
42
+ self.ndb.set_update_view(self.nncli_gui.gui_update_view)
50
43
51
44
if force_full_sync:
52
45
# The note database doesn't exist so force a full sync. It is
53
46
# important to do this outside of the gui because an account
54
47
# with hundreds of notes will cause a recursion panic under
55
48
# urwid. This simple workaround gets the job done. :-)
56
- self.verbose = True
57
- self.log('nncli database doesn\'t exist, forcing full sync...')
58
- self.sync_notes()
59
- self.verbose = verbose
60
-
61
- def sync_notes(self):
62
- self.ndb.sync_now(self.do_server_sync)
63
-
64
- def get_editor(self):
65
- editor = self.config.get_config('editor')
66
- if not editor:
67
- self.log('No editor configured!')
68
- return None
69
- return editor
70
-
71
- def get_pager(self):
72
- pager = self.config.get_config('pager')
73
- if not pager:
74
- self.log('No pager configured!')
75
- return None
76
- return pager
77
-
78
- def exec_cmd_on_note(self, note, cmd=None, raw=False):
79
-
80
- if not cmd:
81
- cmd = self.get_editor()
82
- if not cmd:
83
- return None
84
-
85
- tf = temp.tempfile_create(note if note else None, raw=raw, tempdir=self.tempdir)
86
- fname = temp.tempfile_name(tf)
87
-
88
- focus_position = 0
89
- try:
90
- focus_position = self.gui_body_get().focus_position
91
- except IndexError:
92
- # focus position will fail if no notes available (listbox empty)
93
- # TODO: find a neater way to check than try/except
94
- pass
95
- except AttributeError:
96
- # we're running in CLI mode
97
- pass
98
-
99
- subs = {
100
- 'fname': fname,
101
- 'line': focus_position + 1,
102
- }
103
- cmd_list = [c.format(**subs) for c in shlex.split(cmd)]
104
-
105
- # if the filename wasn't able to be subbed, append it
106
- # this makes it fully backwards compatible with previous configs
107
- if '{fname}' not in cmd:
108
- cmd_list.append(fname)
109
-
110
- self.log("EXECUTING: {}".format(cmd_list))
111
-
112
- try:
113
- subprocess.check_call(cmd_list)
114
- except Exception as e:
115
- self.log('Command error: ' + str(e))
116
- temp.tempfile_delete(tf)
117
- return None
118
-
119
- content = None
120
- if not raw:
121
- content = temp.tempfile_content(tf)
122
- if not content or content == '\n':
123
- content = None
124
-
125
- temp.tempfile_delete(tf)
126
-
127
- if self.do_gui:
128
- self.nncli_loop.screen.clear()
129
- self.nncli_loop.draw_screen()
130
-
131
- return content
132
-
133
- def gui_header_clear(self):
134
- self.master_frame.contents['header'] = ( None, None )
135
- self.nncli_loop.draw_screen()
136
-
137
- def gui_header_set(self, w):
138
- self.master_frame.contents['header'] = ( w, None )
139
- self.nncli_loop.draw_screen()
140
-
141
- def gui_footer_log_clear(self):
142
- ui = self.gui_footer_input_get()
143
- self.master_frame.contents['footer'] = \
144
- (urwid.Pile([ urwid.Pile([]), urwid.Pile([ui]) ]), None)
145
- self.nncli_loop.draw_screen()
146
-
147
- def gui_footer_log_set(self, pl):
148
- ui = self.gui_footer_input_get()
149
- self.master_frame.contents['footer'] = \
150
- (urwid.Pile([ urwid.Pile(pl), urwid.Pile([ui]) ]), None)
151
- self.nncli_loop.draw_screen()
152
-
153
- def gui_footer_log_get(self):
154
- return self.master_frame.contents['footer'][0].contents[0][0]
155
-
156
- def gui_footer_input_clear(self):
157
- pl = self.gui_footer_log_get()
158
- self.master_frame.contents['footer'] = \
159
- (urwid.Pile([ urwid.Pile([pl]), urwid.Pile([]) ]), None)
160
- self.nncli_loop.draw_screen()
161
-
162
- def gui_footer_input_set(self, ui):
163
- pl = self.gui_footer_log_get()
164
- self.master_frame.contents['footer'] = \
165
- (urwid.Pile([ urwid.Pile([pl]), urwid.Pile([ui]) ]), None)
166
- self.nncli_loop.draw_screen()
167
-
168
- def gui_footer_input_get(self):
169
- return self.master_frame.contents['footer'][0].contents[1][0]
170
-
171
- def gui_footer_focus_input(self):
172
- self.master_frame.focus_position = 'footer'
173
- self.master_frame.contents['footer'][0].focus_position = 1
174
-
175
- def gui_body_set(self, w):
176
- self.master_frame.contents['body'] = ( w, None )
177
- self.gui_update_status_bar()
178
- self.nncli_loop.draw_screen()
179
-
180
- def gui_body_get(self):
181
- return self.master_frame.contents['body'][0]
182
-
183
- def gui_body_focus(self):
184
- self.master_frame.focus_position = 'body'
185
-
186
- def log_timeout(self, loop, arg):
187
- self.log_lock.acquire()
188
-
189
- self.log_alarms -= 1
190
-
191
- if self.log_alarms == 0:
192
- self.gui_footer_log_clear()
193
- self.logs = []
194
- else:
195
- # for some reason having problems with this being empty?
196
- if len(self.logs) > 0:
197
- self.logs.pop(0)
198
-
199
- log_pile = []
200
-
201
- for l in self.logs:
202
- log_pile.append(urwid.AttrMap(urwid.Text(l), 'log'))
203
-
204
- if self.verbose:
205
- self.gui_footer_log_set(log_pile)
206
-
207
- self.log_lock.release()
208
-
209
- def log(self, msg):
210
- logging.debug(msg)
211
-
212
- if not self.do_gui:
213
- if self.verbose:
214
- print(msg)
215
- return
216
-
217
- self.log_lock.acquire()
218
-
219
- self.log_alarms += 1
220
- self.logs.append(msg)
221
-
222
- if len(self.logs) > int(self.config.get_config('max_logs')):
223
- self.log_alarms -= 1
224
- self.logs.pop(0)
225
-
226
- log_pile = []
227
- for l in self.logs:
228
- log_pile.append(urwid.AttrMap(urwid.Text(l), 'log'))
229
-
230
- if self.verbose:
231
- self.gui_footer_log_set(log_pile)
232
-
233
- self.nncli_loop.set_alarm_in(
234
- int(self.config.get_config('log_timeout')),
235
- self.log_timeout, None)
236
-
237
- self.log_lock.release()
238
-
239
- def gui_update_view(self):
240
- if not self.do_gui:
241
- return
242
-
243
- try:
244
- cur_key = self.view_titles.note_list[self.view_titles.focus_position].note['localkey']
245
- except IndexError as e:
246
- cur_key = None
247
- pass
248
-
249
- self.view_titles.update_note_list(self.view_titles.search_string, sort_mode=self.current_sort_mode)
250
- self.view_titles.focus_note(cur_key)
251
-
252
- if self.gui_body_get().__class__ == view_note.ViewNote:
253
- self.view_note.update_note_view()
254
-
255
- self.gui_update_status_bar()
256
-
257
- def gui_update_status_bar(self):
258
- if self.status_bar != 'yes':
259
- self.gui_header_clear()
260
- else:
261
- self.gui_header_set(self.gui_body_get().get_status_bar())
262
-
263
- def gui_switch_frame_body(self, new_view, save_current_view=True):
264
- if new_view == None:
265
- if len(self.last_view) == 0:
266
- # XXX verify all notes saved...
267
- self.gui_stop()
268
- else:
269
- self.gui_body_set(self.last_view.pop())
270
- else:
271
- if self.gui_body_get().__class__ != new_view.__class__:
272
- if save_current_view:
273
- self.last_view.append(self.gui_body_get())
274
- self.gui_body_set(new_view)
275
-
276
- def delete_note_callback(self, key, delete):
277
- if not delete:
278
- return
279
- note = self.ndb.get_note(key)
280
- self.ndb.set_note_deleted(key, True)
281
-
282
- if self.gui_body_get().__class__ == view_titles.ViewTitles:
283
- self.view_titles.update_note_title()
284
-
285
- self.gui_update_status_bar()
286
- self.ndb.sync_worker_go()
287
-
288
- def gui_yes_no_input(self, args, yes_no):
289
- self.gui_footer_input_clear()
290
- self.gui_body_focus()
291
- self.master_frame.keypress = self.gui_frame_keypress
292
- args[0](args[1],
293
- True if yes_no in [ 'YES', 'Yes', 'yes', 'Y', 'y' ]
294
- else False)
295
-
296
- def gui_search_input(self, args, search_string):
297
- self.gui_footer_input_clear()
298
- self.gui_body_focus()
299
- self.master_frame.keypress = self.gui_frame_keypress
300
- if search_string:
301
- if (self.gui_body_get() == self.view_note):
302
- self.search_direction = args[1]
303
- self.view_note.search_note_view_next(search_string=search_string, search_mode=args[0])
304
- else:
305
- self.view_titles.update_note_list(search_string, args[0], sort_mode=self.current_sort_mode)
306
- self.gui_body_set(self.view_titles)
307
-
308
- def gui_category_input(self, args, category):
309
- self.gui_footer_input_clear()
310
- self.gui_body_focus()
311
- self.master_frame.keypress = self.gui_frame_keypress
312
- if category != None:
313
- if self.gui_body_get().__class__ == view_titles.ViewTitles:
314
- note = self.view_titles.note_list[self.view_titles.focus_position].note
315
- else: # self.gui_body_get().__class__ == view_note.ViewNote:
316
- note = self.view_note.note
317
-
318
- self.ndb.set_note_category(note['localkey'], category)
319
-
320
- if self.gui_body_get().__class__ == view_titles.ViewTitles:
321
- self.view_titles.update_note_title()
322
- else: # self.gui_body_get().__class__ == view_note.ViewNote:
323
- self.view_note.update_note_view()
324
-
325
- self.gui_update_status_bar()
326
- self.ndb.sync_worker_go()
327
-
328
- def gui_pipe_input(self, args, cmd):
329
- self.gui_footer_input_clear()
330
- self.gui_body_focus()
331
- self.master_frame.keypress = self.gui_frame_keypress
332
- if cmd != None:
333
- if self.gui_body_get().__class__ == view_titles.ViewTitles:
334
- note = self.view_titles.note_list[self.view_titles.focus_position].note
335
- else: # self.gui_body_get().__class__ == view_note.ViewNote:
336
- note = self.view_note.old_note if self.view_note.old_note \
337
- else self.view_note.note
338
- args = shlex.split(cmd)
339
- try:
340
- self.gui_clear()
341
- pipe = subprocess.Popen(cmd, stdin=subprocess.PIPE, shell=True)
342
- pipe.communicate(note['content'].encode('utf-8'))
343
- pipe.stdin.close()
344
- pipe.wait()
345
- except OSError as e:
346
- self.log('Pipe error: ' + str(e))
347
- finally:
348
- self.gui_reset()
349
-
350
- def gui_frame_keypress(self, size, key):
351
- # convert space character into name
352
- if key == ' ':
353
- key = 'space'
354
-
355
- lb = self.gui_body_get()
356
-
357
- if key == self.config.get_keybind('quit'):
358
- self.gui_switch_frame_body(None)
359
-
360
- elif key == self.config.get_keybind('help'):
361
- self.gui_switch_frame_body(self.view_help)
362
-
363
- elif key == self.config.get_keybind('sync'):
364
- self.ndb.last_sync = 0
365
- self.ndb.sync_worker_go()
366
-
367
- elif key == self.config.get_keybind('view_log'):
368
- self.view_log.update_log()
369
- self.gui_switch_frame_body(self.view_log)
370
-
371
- elif key == self.config.get_keybind('down'):
372
- if len(lb.body.positions()) <= 0:
373
- return None
374
- last = len(lb.body.positions())
375
- if lb.focus_position == (last - 1):
376
- return None
377
- lb.focus_position += 1
378
- lb.render(size)
379
-
380
- elif key == self.config.get_keybind('up'):
381
- if len(lb.body.positions()) <= 0:
382
- return None
383
- if lb.focus_position == 0:
384
- return None
385
- lb.focus_position -= 1
386
- lb.render(size)
387
-
388
- elif key == self.config.get_keybind('page_down'):
389
- if len(lb.body.positions()) <= 0:
390
- return None
391
- last = len(lb.body.positions())
392
- next_focus = lb.focus_position + size[1]
393
- if next_focus >= last:
394
- next_focus = last - 1
395
- lb.change_focus(size, next_focus,
396
- offset_inset=0,
397
- coming_from='above')
398
-
399
- elif key == self.config.get_keybind('page_up'):
400
- if len(lb.body.positions()) <= 0:
401
- return None
402
- if 'bottom' in lb.ends_visible(size):
403
- last = len(lb.body.positions())
404
- next_focus = last - size[1] - size[1]
405
- else:
406
- next_focus = lb.focus_position - size[1]
407
- if next_focus < 0:
408
- next_focus = 0
409
- lb.change_focus(size, next_focus,
410
- offset_inset=0,
411
- coming_from='below')
412
-
413
- elif key == self.config.get_keybind('half_page_down'):
414
- if len(lb.body.positions()) <= 0:
415
- return None
416
- last = len(lb.body.positions())
417
- next_focus = lb.focus_position + (size[1] // 2)
418
- if next_focus >= last:
419
- next_focus = last - 1
420
- lb.change_focus(size, next_focus,
421
- offset_inset=0,
422
- coming_from='above')
423
-
424
- elif key == self.config.get_keybind('half_page_up'):
425
- if len(lb.body.positions()) <= 0:
426
- return None
427
- if 'bottom' in lb.ends_visible(size):
428
- last = len(lb.body.positions())
429
- next_focus = last - size[1] - (size[1] // 2)
430
- else:
431
- next_focus = lb.focus_position - (size[1] // 2)
432
- if next_focus < 0:
433
- next_focus = 0
434
- lb.change_focus(size, next_focus,
435
- offset_inset=0,
436
- coming_from='below')
437
-
438
- elif key == self.config.get_keybind('bottom'):
439
- if len(lb.body.positions()) <= 0:
440
- return None
441
- lb.change_focus(size, (len(lb.body.positions()) - 1),
442
- offset_inset=0,
443
- coming_from='above')
444
-
445
- elif key == self.config.get_keybind('top'):
446
- if len(lb.body.positions()) <= 0:
447
- return None
448
- lb.change_focus(size, 0,
449
- offset_inset=0,
450
- coming_from='below')
451
-
452
- elif key == self.config.get_keybind('view_next_note'):
453
- if self.gui_body_get().__class__ != view_note.ViewNote:
454
- return key
455
-
456
- if len(self.view_titles.body.positions()) <= 0:
457
- return None
458
- last = len(self.view_titles.body.positions())
459
- if self.view_titles.focus_position == (last - 1):
460
- return None
461
- self.view_titles.focus_position += 1
462
- lb.update_note_view(
463
- self.view_titles.note_list[self.view_titles.focus_position].note['localkey'])
464
- self.gui_switch_frame_body(self.view_note)
465
-
466
- elif key == self.config.get_keybind('view_prev_note'):
467
- if self.gui_body_get().__class__ != view_note.ViewNote:
468
- return key
469
-
470
- if len(self.view_titles.body.positions()) <= 0:
471
- return None
472
- if self.view_titles.focus_position == 0:
473
- return None
474
- self.view_titles.focus_position -= 1
475
- lb.update_note_view(
476
- self.view_titles.note_list[self.view_titles.focus_position].note['localkey'])
477
- self.gui_switch_frame_body(self.view_note)
478
-
479
- elif key == self.config.get_keybind('status'):
480
- if self.status_bar == 'yes':
481
- self.status_bar = 'no'
482
- else:
483
- self.status_bar = self.config.get_config('status_bar')
484
-
485
- elif key == self.config.get_keybind('create_note'):
486
- if self.gui_body_get().__class__ != view_titles.ViewTitles:
487
- return key
488
-
489
- self.gui_clear()
490
- content = self.exec_cmd_on_note(None)
491
- self.gui_reset()
492
-
493
- if content:
494
- self.log('New note created')
495
- self.ndb.create_note(content)
496
- self.gui_update_view()
497
- self.ndb.sync_worker_go()
498
-
499
- elif key == self.config.get_keybind('edit_note') or \
500
- key == self.config.get_keybind('view_note_ext') or \
501
- key == self.config.get_keybind('view_note_json'):
502
- if self.gui_body_get().__class__ != view_titles.ViewTitles and \
503
- self.gui_body_get().__class__ != view_note.ViewNote:
504
- return key
505
-
506
- if self.gui_body_get().__class__ == view_titles.ViewTitles:
507
- if len(lb.body.positions()) <= 0:
508
- return None
509
- note = lb.note_list[lb.focus_position].note
510
- else: # self.gui_body_get().__class__ == view_note.ViewNote:
511
- if key == self.config.get_keybind('edit_note'):
512
- note = lb.note
513
- else:
514
- note = lb.old_note if lb.old_note else lb.note
515
-
516
- self.gui_clear()
517
- if key == self.config.get_keybind('edit_note'):
518
- content = self.exec_cmd_on_note(note)
519
- elif key == self.config.get_keybind('view_note_ext'):
520
- content = self.exec_cmd_on_note(note, cmd=self.get_pager())
521
- else: # key == self.config.get_keybind('view_note_json')
522
- content = self.exec_cmd_on_note(note, cmd=self.get_pager(), raw=True)
523
-
524
- self.gui_reset()
525
-
526
- if not content:
527
- return None
528
-
529
- md5_old = hashlib.md5(note['content'].encode('utf-8')).digest()
530
- md5_new = hashlib.md5(content.encode('utf-8')).digest()
531
-
532
- if md5_old != md5_new:
533
- self.log('Note updated')
534
- self.ndb.set_note_content(note['localkey'], content)
535
- if self.gui_body_get().__class__ == view_titles.ViewTitles:
536
- lb.update_note_title()
537
- else: # self.gui_body_get().__class__ == view_note.ViewNote:
538
- lb.update_note_view()
539
- self.ndb.sync_worker_go()
540
- else:
541
- self.log('Note unchanged')
542
-
543
- elif key == self.config.get_keybind('view_note'):
544
- if self.gui_body_get().__class__ != view_titles.ViewTitles:
545
- return key
546
-
547
- if len(lb.body.positions()) <= 0:
548
- return None
549
- self.view_note.update_note_view(
550
- lb.note_list[lb.focus_position].note['localkey'])
551
- self.gui_switch_frame_body(self.view_note)
552
-
553
- elif key == self.config.get_keybind('pipe_note'):
554
- if self.gui_body_get().__class__ != view_titles.ViewTitles and \
555
- self.gui_body_get().__class__ != view_note.ViewNote:
556
- return key
557
-
558
- if self.gui_body_get().__class__ == view_titles.ViewTitles:
559
- if len(lb.body.positions()) <= 0:
560
- return None
561
- note = lb.note_list[lb.focus_position].note
562
- else: # self.gui_body_get().__class__ == view_note.ViewNote:
563
- note = lb.old_note if lb.old_note else lb.note
564
-
565
- self.gui_footer_input_set(
566
- urwid.AttrMap(
567
- user_input.UserInput(
568
- self.config,
569
- key,
570
- '',
571
- self.gui_pipe_input,
572
- None),
573
- 'user_input_bar'))
574
- self.gui_footer_focus_input()
575
- self.master_frame.keypress = self.gui_footer_input_get().keypress
576
-
577
- elif key == self.config.get_keybind('note_delete'):
578
- if self.gui_body_get().__class__ != view_titles.ViewTitles and \
579
- self.gui_body_get().__class__ != view_note.ViewNote:
580
- return key
581
-
582
- if self.gui_body_get().__class__ == view_titles.ViewTitles:
583
- if len(lb.body.positions()) <= 0:
584
- return None
585
- note = lb.note_list[lb.focus_position].note
586
- else: # self.gui_body_get().__class__ == view_note.ViewNote:
587
- note = lb.note
588
-
589
- self.gui_footer_input_set(
590
- urwid.AttrMap(
591
- user_input.UserInput(
592
- self.config,
593
- 'Delete (y/n): ',
594
- '',
595
- self.gui_yes_no_input,
596
- [ self.delete_note_callback, note['localkey'] ]),
597
- 'user_input_bar'))
598
- self.gui_footer_focus_input()
599
- self.master_frame.keypress = self.gui_footer_input_get().keypress
600
-
601
- elif key == self.config.get_keybind('note_favorite'):
602
- if self.gui_body_get().__class__ != view_titles.ViewTitles and \
603
- self.gui_body_get().__class__ != view_note.ViewNote:
604
- return key
605
-
606
- if self.gui_body_get().__class__ == view_titles.ViewTitles:
607
- if len(lb.body.positions()) <= 0:
608
- return None
609
- note = lb.note_list[lb.focus_position].note
610
- else: # self.gui_body_get().__class__ == view_note.ViewNote:
611
- note = lb.note
612
-
613
- favorite = not note['favorite']
614
-
615
- self.ndb.set_note_favorite(note['localkey'], favorite)
616
-
617
- if self.gui_body_get().__class__ == view_titles.ViewTitles:
618
- lb.update_note_title()
619
-
620
- self.ndb.sync_worker_go()
621
-
622
- elif key == self.config.get_keybind('note_category'):
623
- if self.gui_body_get().__class__ != view_titles.ViewTitles and \
624
- self.gui_body_get().__class__ != view_note.ViewNote:
625
- return key
626
-
627
- if self.gui_body_get().__class__ == view_titles.ViewTitles:
628
- if len(lb.body.positions()) <= 0:
629
- return None
630
- note = lb.note_list[lb.focus_position].note
631
- else: # self.gui_body_get().__class__ == view_note.ViewNote:
632
- note = lb.note
633
-
634
- self.gui_footer_input_set(
635
- urwid.AttrMap(
636
- user_input.UserInput(
637
- self.config,
638
- 'Category: ',
639
- note['category'],
640
- self.gui_category_input,
641
- None),
642
- 'user_input_bar'))
643
- self.gui_footer_focus_input()
644
- self.master_frame.keypress = self.gui_footer_input_get().keypress
645
-
646
- elif key == self.config.get_keybind('search_gstyle') or \
647
- key == self.config.get_keybind('search_regex') or \
648
- key == self.config.get_keybind('search_prev_gstyle') or \
649
- key == self.config.get_keybind('search_prev_regex'):
650
- if self.gui_body_get().__class__ != view_titles.ViewTitles and \
651
- self.gui_body_get().__class__ != view_note.ViewNote:
652
- return key
653
-
654
- if self.gui_body_get().__class__ == view_note.ViewNote:
655
- if key == self.config.get_keybind('search_prev_gstyle') or \
656
- key == self.config.get_keybind('search_prev_regex'):
657
- self.view_note.search_direction = 'backward'
658
- else:
659
- self.view_note.search_direction = 'forward'
660
-
661
- options = [
662
- 'gstyle' if key == self.config.get_keybind('search_gstyle')
663
- or key == self.config.get_keybind('search_prev_gstyle')
664
- else 'regex',
665
- 'backward' if key == self.config.get_keybind('search_prev_gstyle')
666
- or key == self.config.get_keybind('search_prev_regex')
667
- else 'forward'
668
- ]
669
-
670
- caption = '{}{}'.format('(regex) ' if options[0] == 'regex' else '', '/' if options[1] == 'forward' else '?')
671
-
672
- self.gui_footer_input_set(
673
- urwid.AttrMap(
674
- user_input.UserInput(
675
- self.config,
676
- caption,
677
- '',
678
- self.gui_search_input,
679
- options),
680
- 'user_input_bar'))
681
- self.gui_footer_focus_input()
682
- self.master_frame.keypress = self.gui_footer_input_get().keypress
683
-
684
- elif key == self.config.get_keybind('search_next'):
685
- if self.gui_body_get().__class__ != view_note.ViewNote:
686
- return key
687
-
688
- self.view_note.search_note_view_next()
689
-
690
- elif key == self.config.get_keybind('search_prev'):
691
- if self.gui_body_get().__class__ != view_note.ViewNote:
692
- return key
693
-
694
- self.view_note.search_note_view_prev()
695
-
696
- elif key == self.config.get_keybind('clear_search'):
697
- if self.gui_body_get().__class__ != view_titles.ViewTitles:
698
- return key
699
-
700
- self.view_titles.update_note_list(None, sort_mode=self.current_sort_mode)
701
- self.gui_body_set(self.view_titles)
702
-
703
- elif key == self.config.get_keybind('sort_date'):
704
- if self.gui_body_get().__class__ != view_titles.ViewTitles:
705
- return key
706
-
707
- self.current_sort_mode = 'date'
708
- self.view_titles.sort_note_list('date')
709
-
710
- elif key == self.config.get_keybind('sort_alpha'):
711
- if self.gui_body_get().__class__ != view_titles.ViewTitles:
712
- return key
713
-
714
- self.current_sort_mode = 'alpha'
715
- self.view_titles.sort_note_list('alpha')
716
-
717
- elif key == self.config.get_keybind('sort_categories'):
718
- if self.gui_body_get().__class__ != view_titles.ViewTitles:
719
- return key
720
-
721
- self.current_sort_mode = 'categories'
722
- self.view_titles.sort_note_list('categories')
723
-
724
- elif key == self.config.get_keybind('copy_note_text'):
725
- if self.gui_body_get().__class__ != view_note.ViewNote:
726
- return key
727
-
728
- self.view_note.copy_note_text()
729
-
730
- else:
731
- return lb.keypress(size, key)
732
-
733
- self.gui_update_status_bar()
734
- return None
735
-
736
- def gui_init_view(self, loop, view_note):
737
- self.master_frame.keypress = self.gui_frame_keypress
738
- self.gui_body_set(self.view_titles)
739
-
740
- if view_note:
741
- # note that title view set first to prime the view stack
742
- self.gui_switch_frame_body(self.view_note)
743
-
744
- self.thread_sync.start()
745
-
746
- def gui_clear(self):
747
- self.nncli_loop.widget = urwid.Filler(urwid.Text(''))
748
- self.nncli_loop.draw_screen()
749
-