Diff
from v0.2.0 to v0.3.0
Diffstat
.vulture_whitelist.py | 18 ++
Makefile | 3
Pipfile | 3
Pipfile.lock | 104 ++++----------
README.rst | 32 +---
docs/source/configuration.rst | 25 ++-
docs/source/index.rst | 23 +++
nncli/__init__.py | 4
nncli/__main__.py | 6
nncli/cli.py | 242 ++++++++++++++++++++++++++++++++++
nncli/clipboard.py | 2
nncli/config.py | 8
nncli/nextcloud_note.py | 32 ----
nncli/nncli.py | 257 +-----------------------------------
nncli/notes_db.py | 26 ---
nncli/temp.py | 2
nncli/user_input.py | 2
nncli/utils.py | 49 ------
nncli/view_help.py | 2
nncli/view_log.py | 2
nncli/view_note.py | 2
nncli/view_titles.py | 2
pyproject.toml | 7
tests/test_config.py | 2
tests/test_nncli.py | 16 +-
.vulture_whitelist.py (created)
|
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) |
Makefile
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 |
61 |
|
61 |
62 |
test: ## run tests quickly with the default Python |
62 |
63 |
$(PIPRUN) python -m pytest |
|
|
. . . |
83 |
84 |
|
84 |
85 |
docs: ## builds the sphinx documentation and opens in the browser |
85 |
86 |
make -C docs html |
|
87 |
+ make -C docs latexpdf |
|
88 |
+ make -C docs man |
86 |
89 |
$(BROWSER) docs/build/html/index.html |
87 |
90 |
|
88 |
91 |
install: ## install the package to the active Python's site-packages |
Pipfile
7 |
7 |
appdirs = "*" |
8 |
8 |
requests = "*" |
9 |
9 |
urwid = "*" |
|
10 |
+click = "*" |
10 |
11 |
|
11 |
12 |
[dev-packages] |
12 |
13 |
pytest = "*" |
13 |
14 |
pytest-cov = "*" |
14 |
15 |
pytest-mock = "*" |
15 |
|
-pytest-runner = "*" |
16 |
16 |
pylint = "*" |
17 |
17 |
pudb = "*" |
18 |
18 |
sphinx = "*" |
|
|
. . . |
22 |
22 |
tox = "*" |
23 |
23 |
pathlib2 = {version = "*", markers = "python_version < '3.5'"} |
24 |
24 |
scandir = {version = "*", markers = "python_version < '3.5'"} |
|
25 |
+vulture = "*" |
Pipfile.lock
1 |
1 |
{ |
2 |
2 |
"_meta": { |
3 |
3 |
"hash": { |
4 |
|
- "sha256": "3dd4d429a27a82ab0f5daa11fb974392ab9f45ac6b20647762d79a1c4ab1bd55" |
|
4 |
+ "sha256": "44a70f798cfaf69e97a5d6d1dfc9d4e7f19feb48ae8f32cbe55b2d34f3083731" |
5 |
5 |
}, |
6 |
6 |
"pipfile-spec": 6, |
7 |
7 |
"requires": {}, |
|
|
. . . |
36 |
36 |
], |
37 |
37 |
"version": "==3.0.4" |
38 |
38 |
}, |
|
39 |
+ "click": { |
|
40 |
+ "hashes": [ |
|
41 |
+ "sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d", |
|
42 |
+ "sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b" |
|
43 |
+ ], |
|
44 |
+ "index": "pypi", |
|
45 |
+ "version": "==6.7" |
|
46 |
+ }, |
39 |
47 |
"idna": { |
40 |
48 |
"hashes": [ |
41 |
49 |
"sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", |
|
|
. . . |
56 |
64 |
"sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf", |
57 |
65 |
"sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5" |
58 |
66 |
], |
59 |
|
- "markers": "python_version != '3.1.*' and python_version != '3.3.*' and python_version < '4' and python_version != '3.0.*' and python_version != '3.2.*' and python_version >= '2.6'", |
|
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'", |
60 |
68 |
"version": "==1.23" |
61 |
69 |
}, |
62 |
70 |
"urwid": { |
|
|
. . . |
87 |
95 |
"sha256:0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0", |
88 |
96 |
"sha256:ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee" |
89 |
97 |
], |
90 |
|
- "markers": "python_version != '3.1.*' and python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.2.*' and python_version != '3.3.*'", |
|
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.*'", |
91 |
99 |
"version": "==1.2.1" |
92 |
100 |
}, |
93 |
101 |
"attrs": { |
|
|
. . . |
152 |
160 |
"sha256:e05cb4d9aad6233d67e0541caa7e511fa4047ed7750ec2510d466e806e0255d6", |
153 |
161 |
"sha256:f3f501f345f24383c0000395b26b726e46758b71393267aeae0bd36f8b3ade80" |
154 |
162 |
], |
155 |
|
- "markers": "python_version >= '2.6' and python_version != '3.1.*' and python_version != '3.0.*' and python_version != '3.2.*' and python_version < '4'", |
|
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'", |
156 |
164 |
"version": "==4.5.1" |
157 |
165 |
}, |
158 |
166 |
"docutils": { |
|
|
. . . |
183 |
191 |
"sha256:3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8", |
184 |
192 |
"sha256:f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5" |
185 |
193 |
], |
186 |
|
- "markers": "python_version != '3.1.*' and python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.2.*' and python_version != '3.3.*'", |
|
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.*'", |
187 |
195 |
"version": "==1.1.0" |
188 |
196 |
}, |
189 |
197 |
"isort": { |
|
|
. . . |
192 |
200 |
"sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8", |
193 |
201 |
"sha256:ec9ef8f4a9bc6f71eec99e1806bfa2de401650d996c59330782b89a5555c1497" |
194 |
202 |
], |
195 |
|
- "markers": "python_version != '3.1.*' and python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.2.*' and python_version != '3.3.*'", |
|
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.*'", |
196 |
204 |
"version": "==4.3.4" |
197 |
205 |
}, |
198 |
206 |
"jinja2": { |
|
|
. . . |
293 |
301 |
"sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1", |
294 |
302 |
"sha256:95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1" |
295 |
303 |
], |
296 |
|
- "markers": "python_version != '3.1.*' and python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.2.*' and python_version != '3.3.*'", |
|
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'", |
297 |
305 |
"version": "==0.7.1" |
298 |
306 |
}, |
299 |
307 |
"pudb": { |
|
|
. . . |
308 |
316 |
"sha256:06a30435d058473046be836d3fc4f27167fd84c45b99704f2fb5509ef61f9af1", |
309 |
317 |
"sha256:50402e9d1c9005d759426988a492e0edaadb7f4e68bcddfea586bc7432d009c6" |
310 |
318 |
], |
311 |
|
- "markers": "python_version != '3.1.*' and python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.2.*' and python_version != '3.3.*'", |
|
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'", |
312 |
320 |
"version": "==1.6.0" |
313 |
321 |
}, |
314 |
322 |
"pygments": { |
|
|
. . . |
343 |
351 |
}, |
344 |
352 |
"pytest-cov": { |
345 |
353 |
"hashes": [ |
346 |
|
- "sha256:03aa752cf11db41d281ea1d807d954c4eda35cfa1b21d6971966cc041bbf6e2d", |
347 |
|
- "sha256:890fe5565400902b0c78b5357004aab1c814115894f4f21370e2433256a3eeec" |
|
354 |
+ "sha256:513c425e931a0344944f84ea47f3956be0e416d95acbd897a44970c8d926d5d7", |
|
355 |
+ "sha256:e360f048b7dae3f2f2a9a4d067b2dd6b6a015d384d1577c994a43f3f7cbad762" |
348 |
356 |
], |
349 |
357 |
"index": "pypi", |
350 |
|
- "version": "==2.5.1" |
|
358 |
+ "version": "==2.6.0" |
351 |
359 |
}, |
352 |
360 |
"pytest-mock": { |
353 |
361 |
"hashes": [ |
|
|
. . . |
356 |
364 |
], |
357 |
365 |
"index": "pypi", |
358 |
366 |
"version": "==1.10.0" |
359 |
|
- }, |
360 |
|
- "pytest-runner": { |
361 |
|
- "hashes": [ |
362 |
|
- "sha256:d23f117be39919f00dd91bffeb4f15e031ec797501b717a245e377aee0f577be", |
363 |
|
- "sha256:d987fec1e31287592ffe1cb823a8c613c533db4c6aaca0ee1191dbc91e2fcc61" |
364 |
|
- ], |
365 |
|
- "index": "pypi", |
366 |
|
- "version": "==4.2" |
367 |
367 |
}, |
368 |
368 |
"pytoml": { |
369 |
369 |
"hashes": [ |
|
|
. . . |
427 |
427 |
}, |
428 |
428 |
"sphinx": { |
429 |
429 |
"hashes": [ |
430 |
|
- "sha256:a07050845cc9a2f4026a6035cc8ed795a5ce7be6528bbc82032385c10807dfe7", |
431 |
|
- "sha256:d719de667218d763e8fd144b7fcfeefd8d434a6201f76bf9f0f0c1fa6f47fcdb" |
|
430 |
+ "sha256:217a7705adcb573da5bbe1e0f5cab4fa0bd89fd9342c9159121746f593c2d5a4", |
|
431 |
+ "sha256:a602513f385f1d5785ff1ca420d9c7eb1a1b63381733b2f0ea8188a391314a86" |
432 |
432 |
], |
433 |
433 |
"index": "pypi", |
434 |
|
- "version": "==1.7.8" |
|
434 |
+ "version": "==1.7.9" |
435 |
435 |
}, |
436 |
436 |
"sphinxcontrib-websupport": { |
437 |
437 |
"hashes": [ |
438 |
438 |
"sha256:68ca7ff70785cbe1e7bccc71a48b5b6d965d79ca50629606c7861a21b206d9dd", |
439 |
439 |
"sha256:9de47f375baf1ea07cdb3436ff39d7a9c76042c10a769c52353ec46e4e8fc3b9" |
440 |
440 |
], |
441 |
|
- "markers": "python_version != '3.1.*' and python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.2.*' and python_version != '3.3.*'", |
|
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 |
442 |
"version": "==1.1.0" |
443 |
443 |
}, |
444 |
444 |
"tox": { |
|
|
. . . |
449 |
449 |
"index": "pypi", |
450 |
450 |
"version": "==3.2.1" |
451 |
451 |
}, |
452 |
|
- "typed-ast": { |
453 |
|
- "hashes": [ |
454 |
|
- "sha256:0948004fa228ae071054f5208840a1e88747a357ec1101c17217bfe99b299d58", |
455 |
|
- "sha256:10703d3cec8dcd9eef5a630a04056bbc898abc19bac5691612acba7d1325b66d", |
456 |
|
- "sha256:1f6c4bd0bdc0f14246fd41262df7dfc018d65bb05f6e16390b7ea26ca454a291", |
457 |
|
- "sha256:25d8feefe27eb0303b73545416b13d108c6067b846b543738a25ff304824ed9a", |
458 |
|
- "sha256:29464a177d56e4e055b5f7b629935af7f49c196be47528cc94e0a7bf83fbc2b9", |
459 |
|
- "sha256:2e214b72168ea0275efd6c884b114ab42e316de3ffa125b267e732ed2abda892", |
460 |
|
- "sha256:3e0d5e48e3a23e9a4d1a9f698e32a542a4a288c871d33ed8df1b092a40f3a0f9", |
461 |
|
- "sha256:519425deca5c2b2bdac49f77b2c5625781abbaf9a809d727d3a5596b30bb4ded", |
462 |
|
- "sha256:57fe287f0cdd9ceaf69e7b71a2e94a24b5d268b35df251a88fef5cc241bf73aa", |
463 |
|
- "sha256:668d0cec391d9aed1c6a388b0d5b97cd22e6073eaa5fbaa6d2946603b4871efe", |
464 |
|
- "sha256:68ba70684990f59497680ff90d18e756a47bf4863c604098f10de9716b2c0bdd", |
465 |
|
- "sha256:6de012d2b166fe7a4cdf505eee3aaa12192f7ba365beeefaca4ec10e31241a85", |
466 |
|
- "sha256:79b91ebe5a28d349b6d0d323023350133e927b4de5b651a8aa2db69c761420c6", |
467 |
|
- "sha256:8550177fa5d4c1f09b5e5f524411c44633c80ec69b24e0e98906dd761941ca46", |
468 |
|
- "sha256:898f818399cafcdb93cbbe15fc83a33d05f18e29fb498ddc09b0214cdfc7cd51", |
469 |
|
- "sha256:94b091dc0f19291adcb279a108f5d38de2430411068b219f41b343c03b28fb1f", |
470 |
|
- "sha256:a26863198902cda15ab4503991e8cf1ca874219e0118cbf07c126bce7c4db129", |
471 |
|
- "sha256:a8034021801bc0440f2e027c354b4eafd95891b573e12ff0418dec385c76785c", |
472 |
|
- "sha256:bc978ac17468fe868ee589c795d06777f75496b1ed576d308002c8a5756fb9ea", |
473 |
|
- "sha256:c05b41bc1deade9f90ddc5d988fe506208019ebba9f2578c622516fd201f5863", |
474 |
|
- "sha256:c9b060bd1e5a26ab6e8267fd46fc9e02b54eb15fffb16d112d4c7b1c12987559", |
475 |
|
- "sha256:edb04bdd45bfd76c8292c4d9654568efaedf76fe78eb246dde69bdb13b2dad87", |
476 |
|
- "sha256:f19f2a4f547505fe9072e15f6f4ae714af51b5a681a97f187971f50c283193b6" |
477 |
|
- ], |
478 |
|
- "markers": "python_version < '3.7' and implementation_name == 'cpython'", |
479 |
|
- "version": "==1.1.0" |
480 |
|
- }, |
481 |
|
- "typing": { |
482 |
|
- "hashes": [ |
483 |
|
- "sha256:4027c5f6127a6267a435201981ba156de91ad0d1d98e9ddc2aa173453453492d", |
484 |
|
- "sha256:57dcf675a99b74d64dacf6fba08fb17cf7e3d5fdff53d4a30ea2a5e7e52543d4", |
485 |
|
- "sha256:a4c8473ce11a65999c8f59cb093e70686b6c84c98df58c1dae9b3b196089858a" |
486 |
|
- ], |
487 |
|
- "markers": "python_version < '3.5'", |
488 |
|
- "version": "==3.6.6" |
489 |
|
- }, |
490 |
452 |
"urllib3": { |
491 |
453 |
"hashes": [ |
492 |
454 |
"sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf", |
493 |
455 |
"sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5" |
494 |
456 |
], |
495 |
|
- "markers": "python_version != '3.1.*' and python_version != '3.3.*' and python_version < '4' and python_version != '3.0.*' and python_version != '3.2.*' and python_version >= '2.6'", |
|
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'", |
496 |
458 |
"version": "==1.23" |
497 |
459 |
}, |
498 |
460 |
"urwid": { |
|
|
. . . |
507 |
469 |
"sha256:2ce32cd126117ce2c539f0134eb89de91a8413a29baac49cbab3eb50e2026669", |
508 |
470 |
"sha256:ca07b4c0b54e14a91af9f34d0919790b016923d157afda5efdde55c96718f752" |
509 |
471 |
], |
510 |
|
- "markers": "python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.2.*' and python_version != '3.1.*'", |
|
472 |
+ "markers": "python_version != '3.2.*' and python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.1.*'", |
511 |
473 |
"version": "==16.0.0" |
512 |
474 |
}, |
513 |
|
- "wrapt": { |
|
475 |
+ "vulture": { |
514 |
476 |
"hashes": [ |
515 |
|
- "sha256:d4d560d479f2c21e1b5443bbd15fe7ec4b37fe7e53d335d3b9b0a7b1226fe3c6" |
|
477 |
+ "sha256:79c89ef5e3f2365467bcf491f425f777ae8fd157584dcc550d4591920b00fe3f", |
|
478 |
+ "sha256:e794345a19c76f93f48f4519653038df90ad468ddea7912e14b07a07f6412e32" |
516 |
479 |
], |
517 |
|
- "version": "==1.10.11" |
|
480 |
+ "index": "pypi", |
|
481 |
+ "version": "==0.29" |
518 |
482 |
}, |
519 |
|
- "zipfile36": { |
|
483 |
+ "wrapt": { |
520 |
484 |
"hashes": [ |
521 |
|
- "sha256:a78a8dddf4fa114f7fe73df76ffcce7538e23433b7a6a96c1c904023f122aead", |
522 |
|
- "sha256:f7e48adf627f75cd74cdb50e7d850623b465f4cf5de913809196e8f3aa57dc4b" |
|
485 |
+ "sha256:d4d560d479f2c21e1b5443bbd15fe7ec4b37fe7e53d335d3b9b0a7b1226fe3c6" |
523 |
486 |
], |
524 |
|
- "markers": "python_version in '3.3 3.4 3.5'", |
525 |
|
- "version": "==0.1.3" |
|
487 |
+ "version": "==1.10.11" |
526 |
488 |
} |
527 |
489 |
} |
528 |
490 |
} |
README.rst
1 |
|
-.. image:: https://img.shields.io/pypi/l/nncli.svg |
2 |
|
- :alt: PyPI - License |
3 |
|
-.. image:: https://img.shields.io/travis/com/djmoch/nncli.svg |
4 |
|
- :alt: Travis (.com) |
5 |
|
- :target: https://travis-ci.com/djmoch/nncli |
6 |
|
-.. image:: https://img.shields.io/pypi/v/nncli.svg |
7 |
|
- :alt: PyPI |
8 |
|
- :target: https://pypi.org/project/nncli |
9 |
|
-.. image:: https://img.shields.io/coveralls/github/djmoch/nncli.svg |
10 |
|
- :alt: Coveralls github |
11 |
|
- :target: https://coveralls.io/github/djmoch/nncli |
12 |
|
-.. image:: https://img.shields.io/readthedocs/nncli.svg |
13 |
|
- :alt: Read the Docs |
14 |
|
- :target: https://nncli.readthedocs.io |
15 |
|
- |
16 |
1 |
nncli is a Python application that gives you access to your NextCloud |
17 |
2 |
Notes account via the command line. It's a "hard" fork of |
18 |
3 |
sncli_. You can access your notes via |
|
|
. . . |
26 |
11 |
More detailed documentation can be found in the docs. |
27 |
12 |
|
28 |
13 |
Requirements |
29 |
|
-~~~~~~~~~~~~ |
|
14 |
+------------ |
30 |
15 |
|
31 |
16 |
- `Python 3`_ |
32 |
17 |
|
|
|
. . . |
37 |
22 |
- A love for the command line! |
38 |
23 |
|
39 |
24 |
Installation |
40 |
|
-~~~~~~~~~~~~ |
|
25 |
+------------ |
41 |
26 |
|
42 |
27 |
- Via pip (latest release): |
43 |
28 |
|
|
|
. . . |
45 |
30 |
|
46 |
31 |
- Manually: |
47 |
32 |
|
|
33 |
+ - If you don't already have it, install Flit_: ``pip3 install flit`` |
|
34 |
+ |
48 |
35 |
- Clone this repository to your hard disk: ``git clone |
49 |
36 |
https://github.com/djmoch/nncli.git`` |
50 |
37 |
|
51 |
|
- - Install nncli: ``python3 setup.py install`` |
|
38 |
+ - Install nncli: ``flit install --deps production`` |
52 |
39 |
|
53 |
40 |
- Development: |
54 |
41 |
|
55 |
42 |
- Clone the repo |
56 |
43 |
|
57 |
|
- - Install Pipenv: ``pip install pipenv`` |
|
44 |
+ - Install Pipenv: ``pip3 install pipenv`` |
58 |
45 |
|
59 |
|
- - Stand up development environment: ``pipenv install --dev`` |
|
46 |
+ - Stand up development virtualenv: ``pipenv install --dev`` |
60 |
47 |
|
61 |
48 |
Features |
62 |
|
-~~~~~~~~ |
|
49 |
+-------- |
63 |
50 |
|
64 |
51 |
- Console GUI |
65 |
52 |
|
|
|
. . . |
118 |
105 |
- view and edit note category |
119 |
106 |
|
120 |
107 |
Acknowledgements |
121 |
|
-~~~~~~~~~~~~~~~~ |
|
108 |
+---------------- |
122 |
109 |
|
123 |
110 |
nncli is a fork of sncli_ by Eric Davis. This application further pulls in |
124 |
111 |
and uses modified versions of the simplenote.py_ module by Daniel Schauenberg and |
|
|
. . . |
130 |
117 |
.. _Requests: https://requests.readthedocs.org/en/master |
131 |
118 |
.. _simplenote.py: https://github.com/mrtazz/simplenote.py |
132 |
119 |
.. _nvpy: https://github.com/cpbotha/nvpy |
|
120 |
+.. _Flit: https://flit.readthedocs.io |
docs/source/configuration.rst
5 |
5 |
|
6 |
6 |
The current NextCloud Notes API does not support oauth authentication so |
7 |
7 |
your NextCloud Notes account password must be stored someplace |
8 |
|
-accessible to nncli. Use of the ``cfg_nn_password_eval`` option is |
9 |
|
-recommended (see :ref:`config-file`). |
|
8 |
+accessible to nncli. |
10 |
9 |
|
11 |
10 |
.. index:: single: configuration file |
12 |
11 |
|
|
|
. . . |
16 |
15 |
------------------ |
17 |
16 |
|
18 |
17 |
nncli pulls in configuration from the ``config`` file located in the |
19 |
|
-standard location for your platform. |
|
18 |
+standard location for your platform: |
20 |
19 |
|
21 |
20 |
- Windows: ``%USERPROFILE%\AppData\Local\djmoch\nncli`` |
22 |
21 |
|
|
|
. . . |
51 |
50 |
Optional. Overrides :confval:`cfg_nn_password_eval` if both are |
52 |
51 |
specified. |
53 |
52 |
|
|
53 |
+ .. note:: |
|
54 |
+ |
|
55 |
+ For security reasons, use of the ``cfg_nn_password_eval`` option |
|
56 |
+ is recommended |
|
57 |
+ |
54 |
58 |
.. confval:: cfg_nn_password_eval |
55 |
59 |
|
56 |
60 |
A command to run to retrieve the password. The command should return |
|
|
. . . |
68 |
72 |
|
69 |
73 |
- macOS: ``~/Library/Caches/nncli`` |
70 |
74 |
|
71 |
|
- - \*nix: ``~/.cache/nncli`` |
|
75 |
+ - \*nix: ``$XDG_CACHE_HOME/nncli`` or ``$HOME/.cache/nncli`` |
72 |
76 |
|
73 |
77 |
.. confval:: cfg_search_categories |
74 |
78 |
|
|
|
. . . |
153 |
157 |
|
154 |
158 |
Optional. Default value: ``$PAGER`` if defined in the user's |
155 |
159 |
environment, else ``less -c``. |
156 |
|
- |
157 |
|
-.. confval:: cfg_diff |
158 |
|
- |
159 |
|
- .. todo:: Remove ``cfg_diff`` |
160 |
160 |
|
161 |
161 |
.. confval:: cfg_max_logs |
162 |
162 |
|
|
|
. . . |
457 |
457 |
nncli utilizes the Python Urwid_ module to implement the console user |
458 |
458 |
interface. |
459 |
459 |
|
460 |
|
-At this time, nncli does not yet support 256-color terminals and is |
461 |
|
-limited to just 16-colors. Color names that can be specified in the |
462 |
|
-``config`` file are listed :ref:`here <urwid:16-standard-foreground>`. |
|
460 |
+.. note:: |
|
461 |
+ |
|
462 |
+ At this time, nncli does not yet support 256-color terminals and is |
|
463 |
+ limited to just 16-colors. Color names that can be specified in the |
|
464 |
+ ``config`` file are listed :ref:`here |
|
465 |
+ <urwid:16-standard-foreground>`. |
463 |
466 |
|
464 |
467 |
The following pairs of configuration values represent the foreground and |
465 |
468 |
background colors for different elements of the console GUI. In each |
docs/source/index.rst
6 |
6 |
NextCloud Notes Command Line Interface |
7 |
7 |
====================================== |
8 |
8 |
|
|
9 |
+.. only:: html |
|
10 |
+ |
|
11 |
+ .. image:: https://img.shields.io/pypi/l/nncli.svg |
|
12 |
+ :alt: PyPI - License |
|
13 |
+ .. image:: https://img.shields.io/travis/com/djmoch/nncli.svg |
|
14 |
+ :alt: Travis (.com) |
|
15 |
+ :target: https://travis-ci.com/djmoch/nncli |
|
16 |
+ .. image:: https://img.shields.io/pypi/v/nncli.svg |
|
17 |
+ :alt: PyPI |
|
18 |
+ :target: https://pypi.org/project/nncli |
|
19 |
+ .. image:: https://img.shields.io/coveralls/github/djmoch/nncli.svg |
|
20 |
+ :alt: Coveralls github |
|
21 |
+ :target: https://coveralls.io/github/djmoch/nncli |
|
22 |
+ .. image:: https://img.shields.io/readthedocs/nncli.svg |
|
23 |
+ :alt: Read the Docs |
|
24 |
+ :target: https://nncli.readthedocs.io |
|
25 |
+ |
9 |
26 |
.. include:: ../../README.rst |
10 |
27 |
|
|
28 |
+Contents |
|
29 |
+-------- |
|
30 |
+ |
11 |
31 |
.. toctree:: |
12 |
32 |
:maxdepth: 2 |
13 |
|
- :caption: Contents: |
14 |
33 |
|
15 |
34 |
configuration |
16 |
35 |
usage |
17 |
36 |
|
18 |
37 |
|
19 |
38 |
Indices and tables |
20 |
|
-================== |
|
39 |
+------------------ |
21 |
40 |
|
22 |
41 |
* :ref:`genindex` |
23 |
42 |
* :ref:`search` |
nncli/__init__.py
1 |
|
-# -*- encoding: utf-8 -*- |
|
1 |
+# -*- coding: utf-8 -*- |
2 |
2 |
"""NextCloud Notes Command Line Interface""" |
3 |
3 |
|
4 |
|
-__version__ = '0.2.0' |
|
4 |
+__version__ = '0.3.0' |
nncli/__main__.py
1 |
|
-# -*- encoding: utf-8 -*- |
|
1 |
+# -*- coding: utf-8 -*- |
2 |
2 |
"""nncli main module""" |
3 |
|
-import nncli.nncli |
|
3 |
+import nncli.cli |
4 |
4 |
|
5 |
5 |
if __name__ == '__main__': |
6 |
|
- nncli.nncli.main() |
|
6 |
+ nncli.cli.main() |
nncli/cli.py (created)
|
1 |
+# -*- coding: utf-8 -*- |
|
2 |
+"""Command line interface module""" |
|
3 |
+import click |
|
4 |
+ |
|
5 |
+from . import __version__ |
|
6 |
+from .nncli import Nncli |
|
7 |
+ |
|
8 |
+class StdinFlag(click.ParamType): |
|
9 |
+ """StdinFlag Click Parameter Type""" |
|
10 |
+ name = "stdin_flag" |
|
11 |
+ |
|
12 |
+ def convert(self, value, param, ctx): |
|
13 |
+ if value == '-': |
|
14 |
+ return True |
|
15 |
+ return self.fail('%s is not a valid stdin_flag') |
|
16 |
+ |
|
17 |
+STDIN_FLAG = StdinFlag() |
|
18 |
+ |
|
19 |
+@click.command() |
|
20 |
+@click.pass_obj |
|
21 |
+def rm_category(ctx_obj): |
|
22 |
+ """Remove note category.""" |
|
23 |
+ nncli = ctx_obj['nncli'] |
|
24 |
+ key = ctx_obj['key'] |
|
25 |
+ nncli.cli_note_category_rm(key) |
|
26 |
+ |
|
27 |
+@click.command() |
|
28 |
+@click.argument('category', required=True) |
|
29 |
+@click.pass_obj |
|
30 |
+def set_category(ctx_obj, category): |
|
31 |
+ """Set the note category.""" |
|
32 |
+ nncli = ctx_obj['nncli'] |
|
33 |
+ key = ctx_obj['key'] |
|
34 |
+ nncli.cli_note_category_set(key, category) |
|
35 |
+ |
|
36 |
+@click.command(short_help="Print the note category.") |
|
37 |
+@click.pass_obj |
|
38 |
+def get_category(ctx_obj): |
|
39 |
+ """Print the category for the given note on stdout.""" |
|
40 |
+ nncli = ctx_obj['nncli'] |
|
41 |
+ key = ctx_obj['key'] |
|
42 |
+ category = nncli.cli_note_category_get(key) |
|
43 |
+ if category: |
|
44 |
+ print(category) |
|
45 |
+ |
|
46 |
+@click.group() |
|
47 |
+@click.option( |
|
48 |
+ '-k', |
|
49 |
+ '--key', |
|
50 |
+ required=True, |
|
51 |
+ type=click.INT, |
|
52 |
+ help="Specify the note key." |
|
53 |
+ ) |
|
54 |
+@click.pass_context |
|
55 |
+def cat(ctx, key): |
|
56 |
+ """Operate on the note category.""" |
|
57 |
+ nncli = ctx.obj |
|
58 |
+ ctx.obj = {} |
|
59 |
+ ctx.obj['nncli'] = nncli |
|
60 |
+ ctx.obj['key'] = key |
|
61 |
+ |
|
62 |
+cat.add_command(get_category, 'get') |
|
63 |
+cat.add_command(set_category, 'set') |
|
64 |
+cat.add_command(rm_category, 'rm') |
|
65 |
+ |
|
66 |
+@click.command() |
|
67 |
+@click.option( |
|
68 |
+ '-k', |
|
69 |
+ '--key', |
|
70 |
+ required=True, |
|
71 |
+ type=click.INT, |
|
72 |
+ help="Specify the note key.") |
|
73 |
+@click.pass_obj |
|
74 |
+def favorite(nncli, key): |
|
75 |
+ """Mark as note as a favorite.""" |
|
76 |
+ nncli.cli_note_favorite(key, 1) |
|
77 |
+ |
|
78 |
+@click.command() |
|
79 |
+@click.option( |
|
80 |
+ '-k', |
|
81 |
+ '--key', |
|
82 |
+ required=True, |
|
83 |
+ type=click.INT, |
|
84 |
+ help="Specify the note key." |
|
85 |
+ ) |
|
86 |
+@click.pass_obj |
|
87 |
+def unfavorite(nncli, key): |
|
88 |
+ """Remove favorite flag from a note.""" |
|
89 |
+ nncli.cli_note_favorite(key, 0) |
|
90 |
+ |
|
91 |
+@click.command(short_help="Print JSON-formatted note to stdout.") |
|
92 |
+@click.option('-k', '--key', type=click.INT, help="Specify the note key.") |
|
93 |
+@click.option( |
|
94 |
+ '-r', |
|
95 |
+ '--regex', |
|
96 |
+ is_flag=True, |
|
97 |
+ help="Treat search term(s) as regular expressions." |
|
98 |
+ ) |
|
99 |
+@click.argument('search_terms', nargs=-1) |
|
100 |
+@click.pass_obj |
|
101 |
+def export(nncli, key, regex, search_terms): |
|
102 |
+ """ |
|
103 |
+ Print JSON-formatted note to stdout. If a key is specified, then regex |
|
104 |
+ and search_terms are ignored. |
|
105 |
+ """ |
|
106 |
+ if key: |
|
107 |
+ nncli.cli_note_export(key) |
|
108 |
+ else: |
|
109 |
+ nncli.cli_export_notes(regex, ' '.join(search_terms)) |
|
110 |
+ |
|
111 |
+@click.command(short_help="Print note contents to stdout.") |
|
112 |
+@click.option('-k', '--key', type=click.INT, help="Specify the note key.") |
|
113 |
+@click.option( |
|
114 |
+ '-r', |
|
115 |
+ '--regex', |
|
116 |
+ is_flag=True, |
|
117 |
+ help="Treat search term(s) as regular expressions." |
|
118 |
+ ) |
|
119 |
+@click.argument('search_terms', nargs=-1) |
|
120 |
+@click.pass_obj |
|
121 |
+def dump(nncli, key, regex, search_terms): |
|
122 |
+ """ |
|
123 |
+ Print note contents to stdout. If a key is specified, then regex |
|
124 |
+ and search_terms are ignored. |
|
125 |
+ """ |
|
126 |
+ if key: |
|
127 |
+ nncli.cli_note_dump(key) |
|
128 |
+ else: |
|
129 |
+ nncli.cli_dump_notes(regex, ' '.join(search_terms)) |
|
130 |
+ |
|
131 |
+@click.command(short_help="List notes.") |
|
132 |
+@click.option( |
|
133 |
+ '-r', |
|
134 |
+ '--regex', |
|
135 |
+ is_flag=True, |
|
136 |
+ help="Treat search term(s) as regular expressions." |
|
137 |
+ ) |
|
138 |
+@click.argument('search_terms', nargs=-1) |
|
139 |
+@click.pass_obj |
|
140 |
+def list_notes(nncli, regex, search_terms): |
|
141 |
+ """ |
|
142 |
+ List notes, optionally providing search terms to narrow the |
|
143 |
+ results. |
|
144 |
+ """ |
|
145 |
+ nncli.cli_list_notes(regex, ' '.join(search_terms)) |
|
146 |
+ |
|
147 |
+@click.command(short_help="Sync notes to server.") |
|
148 |
+def sync(): |
|
149 |
+ """ |
|
150 |
+ Perform a full, bi-directional sync of your notes between the |
|
151 |
+ server and the local cache. |
|
152 |
+ """ |
|
153 |
+ pass |
|
154 |
+ |
|
155 |
+@click.command() |
|
156 |
+@click.option( |
|
157 |
+ '-k', |
|
158 |
+ '--key', |
|
159 |
+ required=True, |
|
160 |
+ type=click.INT, |
|
161 |
+ help="Specify the note key." |
|
162 |
+ ) |
|
163 |
+@click.pass_obj |
|
164 |
+def delete(nncli, key): |
|
165 |
+ """Delete an existing note.""" |
|
166 |
+ nncli.cli_note_delete(key, True) |
|
167 |
+ |
|
168 |
+@click.command() |
|
169 |
+@click.option( |
|
170 |
+ '-k', |
|
171 |
+ '--key', |
|
172 |
+ required=True, |
|
173 |
+ type=click.INT, |
|
174 |
+ help="Specify the note key." |
|
175 |
+ ) |
|
176 |
+@click.pass_obj |
|
177 |
+def edit(nncli, key): |
|
178 |
+ """Edit an existing note.""" |
|
179 |
+ nncli.cli_note_edit(key) |
|
180 |
+ |
|
181 |
+@click.command(short_help="Import a JSON note.") |
|
182 |
+@click.argument('from_stdin', metavar='[-]', type=STDIN_FLAG) |
|
183 |
+@click.pass_obj |
|
184 |
+def json_import(nncli, from_stdin): |
|
185 |
+ """ |
|
186 |
+ Import a JSON-formatted note file into your account. The expected |
|
187 |
+ JSON format is the same format used internally by nncli. If - is |
|
188 |
+ specified, the note is read from stdin, otherwise the editor will |
|
189 |
+ open. |
|
190 |
+ """ |
|
191 |
+ nncli.cli_note_import(from_stdin) |
|
192 |
+ |
|
193 |
+@click.command(short_help="Add a new note.") |
|
194 |
+@click.option('-t', '--title', help="Specify the title of note for create.") |
|
195 |
+@click.argument('from_stdin', metavar='[-]', type=STDIN_FLAG) |
|
196 |
+@click.pass_obj |
|
197 |
+def create(nncli, title, from_stdin): |
|
198 |
+ """ |
|
199 |
+ Create a new note, either opening the editor or, if - is specified, |
|
200 |
+ reading from stdin. |
|
201 |
+ """ |
|
202 |
+ nncli.cli_note_create(from_stdin, title) |
|
203 |
+ |
|
204 |
+@click.group(invoke_without_command=True) |
|
205 |
+@click.option( |
|
206 |
+ '-n', |
|
207 |
+ '--nosync', |
|
208 |
+ is_flag=True, |
|
209 |
+ help="Don't perform a server sync." |
|
210 |
+ ) |
|
211 |
+@click.option('-v', '--verbose', is_flag=True, help="Print verbose output.") |
|
212 |
+@click.option( |
|
213 |
+ '-c', |
|
214 |
+ '--config', |
|
215 |
+ type=click.Path(exists=True), |
|
216 |
+ help="Specify the config file to read from." |
|
217 |
+ ) |
|
218 |
+@click.option('-k', '--key', type=click.INT, help="Specify the note key.") |
|
219 |
+@click.version_option(version=__version__, message='%(prog)s %(version)s') |
|
220 |
+@click.pass_context |
|
221 |
+def main(ctx, nosync, verbose, config, key): |
|
222 |
+ """ |
|
223 |
+ Run the NextClound Note Command Line Interface. No COMMAND means |
|
224 |
+ to open the console GUI. |
|
225 |
+ """ |
|
226 |
+ ctx.obj = Nncli(not nosync, verbose, config) |
|
227 |
+ if ctx.invoked_subcommand is None: |
|
228 |
+ ctx.obj.gui(key) |
|
229 |
+ elif not nosync: |
|
230 |
+ ctx.obj.sync_notes() |
|
231 |
+ |
|
232 |
+main.add_command(create) |
|
233 |
+main.add_command(edit) |
|
234 |
+main.add_command(delete) |
|
235 |
+main.add_command(sync) |
|
236 |
+main.add_command(json_import, name='import') |
|
237 |
+main.add_command(list_notes, name='list') |
|
238 |
+main.add_command(dump) |
|
239 |
+main.add_command(export) |
|
240 |
+main.add_command(favorite) |
|
241 |
+main.add_command(unfavorite) |
|
242 |
+main.add_command(cat) |
nncli/clipboard.py
1 |
|
-# -*- encoding: utf-8 -*- |
|
1 |
+# -*- coding: utf-8 -*- |
2 |
2 |
|
3 |
3 |
import os |
4 |
4 |
from distutils import spawn |
nncli/config.py
1 |
|
-# -*- encoding: utf-8 -*- |
|
1 |
+# -*- coding: utf-8 -*- |
2 |
2 |
|
3 |
3 |
import os, sys, urwid, collections, configparser, subprocess |
4 |
4 |
|
|
|
. . . |
25 |
25 |
'cfg_status_bar' : 'yes', |
26 |
26 |
'cfg_editor' : os.environ['EDITOR'] if 'EDITOR' in os.environ else 'vim {fname} +{line}', |
27 |
27 |
'cfg_pager' : os.environ['PAGER'] if 'PAGER' in os.environ else 'less -c', |
28 |
|
- 'cfg_diff' : 'diff -b -U10', |
29 |
28 |
'cfg_max_logs' : '5', |
30 |
29 |
'cfg_log_timeout' : '5', |
31 |
30 |
'cfg_log_reversed' : 'yes', |
|
|
. . . |
119 |
118 |
|
120 |
119 |
cp = configparser.SafeConfigParser(defaults) |
121 |
120 |
if custom_file is not None: |
122 |
|
- self.configs_read = cp.read([custom_file]) |
|
121 |
+ cp.read([custom_file]) |
123 |
122 |
else: |
124 |
|
- self.configs_read = cp.read([os.path.join(self.config_home, 'config')]) |
|
123 |
+ cp.read([os.path.join(self.config_home, 'config')]) |
125 |
124 |
|
126 |
125 |
cfg_sec = 'nncli' |
127 |
126 |
|
|
|
. . . |
159 |
158 |
self.configs['status_bar'] = [ cp.get(cfg_sec, 'cfg_status_bar'), 'Show the status bar' ] |
160 |
159 |
self.configs['editor'] = [ cp.get(cfg_sec, 'cfg_editor'), 'Editor command' ] |
161 |
160 |
self.configs['pager'] = [ cp.get(cfg_sec, 'cfg_pager'), 'External pager command' ] |
162 |
|
- self.configs['diff'] = [ cp.get(cfg_sec, 'cfg_diff'), 'External diff command' ] |
163 |
161 |
self.configs['max_logs'] = [ cp.get(cfg_sec, 'cfg_max_logs'), 'Max logs in footer' ] |
164 |
162 |
self.configs['log_timeout'] = [ cp.get(cfg_sec, 'cfg_log_timeout'), 'Log timeout' ] |
165 |
163 |
self.configs['log_reversed'] = [ cp.get(cfg_sec, 'cfg_log_reversed'), 'Log file reversed' ] |
nncli/nextcloud_note.py
1 |
1 |
# -*- coding: utf-8 -*- |
2 |
2 |
|
3 |
|
-import urllib.parse |
4 |
3 |
from requests.exceptions import RequestException, ConnectionError |
5 |
|
-import base64 |
6 |
4 |
import time |
7 |
5 |
import datetime |
8 |
6 |
import logging |
|
|
. . . |
17 |
15 |
except ImportError: |
18 |
16 |
# For Google AppEngine |
19 |
17 |
from django.utils import simplejson as json |
20 |
|
- |
21 |
|
-NOTE_FETCH_LENGTH = 100 |
22 |
|
- |
23 |
|
-class NextcloudLoginFailed(Exception): |
24 |
|
- pass |
25 |
18 |
|
26 |
19 |
class NextcloudNote(object): |
27 |
20 |
""" Class for interacting with the NextCloud Notes web service """ |
|
|
. . . |
126 |
119 |
return e, -1 |
127 |
120 |
#logging.debug('RESPONSE OK: ' + str(note)) |
128 |
121 |
return note, 0 |
129 |
|
- |
130 |
|
- def add_note(self, note): |
131 |
|
- """wrapper function to add a note |
132 |
|
- |
133 |
|
- The function can be passed the note as a dict with the `content` |
134 |
|
- property set, which is then directly send to the web service for |
135 |
|
- creation. Alternatively, only the body as string can also be passed. In |
136 |
|
- this case the parameter is used as `content` for the new note. |
137 |
|
- |
138 |
|
- Arguments: |
139 |
|
- - note (dict or string): the note to add |
140 |
|
- |
141 |
|
- Returns: |
142 |
|
- A tuple `(note, status)` |
143 |
|
- |
144 |
|
- - note (dict): the newly created note |
145 |
|
- - status (int): 0 on sucesss and -1 otherwise |
146 |
|
- |
147 |
|
- """ |
148 |
|
- if type(note) == str: |
149 |
|
- return self.update_note({"content": note}) |
150 |
|
- elif (type(note) == dict) and "content" in note: |
151 |
|
- return self.update_note(note) |
152 |
|
- else: |
153 |
|
- return "No string or valid note.", -1 |
154 |
122 |
|
155 |
123 |
def get_note_list(self, category=None): |
156 |
124 |
""" function to get the note list |
nncli/nncli.py
1 |
|
-# -*- encoding: utf-8 -*- |
|
1 |
+# -*- coding: utf-8 -*- |
2 |
2 |
|
3 |
3 |
import os, sys, getopt, re, signal, time, datetime, shlex, hashlib |
4 |
4 |
import subprocess, threading, logging |
5 |
5 |
import copy, json, urwid, datetime |
6 |
|
-import nncli |
7 |
6 |
from . import view_titles, view_note, view_help, view_log, user_input |
8 |
|
-from . import utils, temp |
|
7 |
+from . import utils, temp, __version__ |
9 |
8 |
from .config import Config |
10 |
9 |
from .nextcloud_note import NextcloudNote |
11 |
10 |
from .notes_db import NotesDB, ReadError, WriteError |
12 |
11 |
from logging.handlers import RotatingFileHandler |
13 |
12 |
|
14 |
|
-class nncli: |
|
13 |
+class Nncli: |
15 |
14 |
|
16 |
15 |
def __init__(self, do_server_sync, verbose=False, config_file=None): |
17 |
16 |
self.config = Config(config_file) |
|
|
. . . |
76 |
75 |
return None |
77 |
76 |
return pager |
78 |
77 |
|
79 |
|
- def get_diff(self): |
80 |
|
- diff = self.config.get_config('diff') |
81 |
|
- if not diff: |
82 |
|
- self.log('No diff command configured!') |
83 |
|
- return None |
84 |
|
- return diff |
85 |
|
- |
86 |
78 |
def exec_cmd_on_note(self, note, cmd=None, raw=False): |
87 |
79 |
|
88 |
80 |
if not cmd: |
|
|
. . . |
131 |
123 |
content = None |
132 |
124 |
|
133 |
125 |
temp.tempfile_delete(tf) |
134 |
|
- return content |
135 |
126 |
|
136 |
|
- def exec_diff_on_note(self, note, old_note): |
137 |
|
- |
138 |
|
- diff = self.get_diff() |
139 |
|
- if not diff: |
140 |
|
- return None |
141 |
|
- |
142 |
|
- pager = self.get_pager() |
143 |
|
- if not pager: |
144 |
|
- return None |
145 |
|
- |
146 |
|
- ltf = temp.tempfile_create(note, tempdir=self.tempdir) |
147 |
|
- otf = temp.tempfile_create(old_note, tempdir=self.tempdir) |
148 |
|
- out = temp.tempfile_create(None, tempdir=self.tempdir) |
149 |
|
- |
150 |
|
- try: |
151 |
|
- subprocess.call(diff + ' ' + |
152 |
|
- temp.tempfile_name(ltf) + ' ' + |
153 |
|
- temp.tempfile_name(otf) + ' > ' + |
154 |
|
- temp.tempfile_name(out), |
155 |
|
- shell=True) |
156 |
|
- subprocess.check_call(pager + ' ' + |
157 |
|
- temp.tempfile_name(out), |
158 |
|
- shell=True) |
159 |
|
- except Exception as e: |
160 |
|
- self.log('Command error: ' + str(e)) |
161 |
|
- temp.tempfile_delete(ltf) |
162 |
|
- temp.tempfile_delete(otf) |
163 |
|
- temp.tempfile_delete(out) |
164 |
|
- return None |
|
127 |
+ if self.do_gui: |
|
128 |
+ self.nncli_loop.screen.clear() |
|
129 |
+ self.nncli_loop.draw_screen() |
165 |
130 |
|
166 |
|
- temp.tempfile_delete(ltf) |
167 |
|
- temp.tempfile_delete(otf) |
168 |
|
- temp.tempfile_delete(out) |
169 |
|
- return None |
|
131 |
+ return content |
170 |
132 |
|
171 |
133 |
def gui_header_clear(self): |
172 |
134 |
self.master_frame.contents['header'] = ( None, None ) |
|
|
. . . |
175 |
137 |
def gui_header_set(self, w): |
176 |
138 |
self.master_frame.contents['header'] = ( w, None ) |
177 |
139 |
self.nncli_loop.draw_screen() |
178 |
|
- |
179 |
|
- def gui_header_get(self): |
180 |
|
- return self.master_frame.contents['header'][0] |
181 |
|
- |
182 |
|
- def gui_header_focus(self): |
183 |
|
- self.master_frame.focus_position = 'header' |
184 |
140 |
|
185 |
141 |
def gui_footer_log_clear(self): |
186 |
142 |
ui = self.gui_footer_input_get() |
|
|
. . . |
215 |
171 |
def gui_footer_focus_input(self): |
216 |
172 |
self.master_frame.focus_position = 'footer' |
217 |
173 |
self.master_frame.contents['footer'][0].focus_position = 1 |
218 |
|
- |
219 |
|
- def gui_body_clear(self): |
220 |
|
- self.master_frame.contents['body'] = ( None, None ) |
221 |
|
- self.nncli_loop.draw_screen() |
222 |
174 |
|
223 |
175 |
def gui_body_set(self, w): |
224 |
176 |
self.master_frame.contents['body'] = ( w, None ) |
|
|
. . . |
1108 |
1060 |
sys.exit(1) |
1109 |
1061 |
|
1110 |
1062 |
signal.signal(signal.SIGINT, SIGINT_handler) |
1111 |
|
- |
1112 |
|
-def usage(): |
1113 |
|
- print (''' |
1114 |
|
-Usage: |
1115 |
|
- nncli [OPTIONS] [COMMAND] [COMMAND_ARGS] |
1116 |
|
- |
1117 |
|
- OPTIONS: |
1118 |
|
- -h, --help - usage help |
1119 |
|
- -v, --verbose - verbose output |
1120 |
|
- -n, --nosync - don't perform a server sync |
1121 |
|
- -r, --regex - search string is a regular expression |
1122 |
|
- -k <key>, --key=<key> - note key |
1123 |
|
- -t <title>, --title=<title> - title of note for create (cli mode) |
1124 |
|
- -c <file>, --config=<file> - config file to read from |
1125 |
|
- -V, --version - version information |
1126 |
|
- |
1127 |
|
- COMMANDS: |
1128 |
|
- <none> - console gui mode when no command specified |
1129 |
|
- sync - perform a full sync with the server |
1130 |
|
- list [search_string] - list notes (refined with search string) |
1131 |
|
- export [search_string] - export notes in JSON (refined with search |
1132 |
|
- string) |
1133 |
|
- dump [search_string] - dump notes (refined with search string) |
1134 |
|
- create [-] - create a note ('-' content from stdin) |
1135 |
|
- import [-] - import a note in JSON format ('-' JSON from |
1136 |
|
- stdin) |
1137 |
|
- export - export a note in JSON format (specified by |
1138 |
|
- <key>) |
1139 |
|
- dump - dump a note (specified by <key>) |
1140 |
|
- edit - edit a note (specified by <key>) |
1141 |
|
- delete - delete a note (specified by <key>) |
1142 |
|
- < favorite | unfavorite > - favorite/unfavorite a note (specified by <key>) |
1143 |
|
- cat get - retrieve the category from a note (specified |
1144 |
|
- by <key>) |
1145 |
|
- cat set <category> - set the category for a note (specified by <key>) |
1146 |
|
- cat rm - remove category from a note (specified by <key>) |
1147 |
|
-''') |
1148 |
|
- sys.exit(0) |
1149 |
|
- |
1150 |
|
-def version(): |
1151 |
|
- version_info = 'nncli {}'.format(nncli.__version__) |
1152 |
|
- print(version_info) |
1153 |
|
- exit(0) |
1154 |
|
- |
1155 |
|
-def main(argv=sys.argv[1:]): |
1156 |
|
- verbose = False |
1157 |
|
- sync = True |
1158 |
|
- regex = False |
1159 |
|
- key = None |
1160 |
|
- title = None |
1161 |
|
- config = None |
1162 |
|
- |
1163 |
|
- try: |
1164 |
|
- opts, args = getopt.getopt(argv, |
1165 |
|
- 'hvnrk:t:c:V', |
1166 |
|
- [ 'help', 'verbose', 'nosync', 'regex', 'key=', 'title=', \ |
1167 |
|
- 'config=', 'version' ]) |
1168 |
|
- except: |
1169 |
|
- usage() |
1170 |
|
- |
1171 |
|
- for opt, arg in opts: |
1172 |
|
- if opt in [ '-h', '--help']: |
1173 |
|
- usage() |
1174 |
|
- elif opt in ['-V', '--version' ]: |
1175 |
|
- version() |
1176 |
|
- elif opt in [ '-v', '--verbose']: |
1177 |
|
- verbose = True |
1178 |
|
- elif opt in [ '-n', '--nosync']: |
1179 |
|
- sync = False |
1180 |
|
- elif opt in [ '-r', '--regex']: |
1181 |
|
- regex = True |
1182 |
|
- elif opt in [ '-k', '--key']: |
1183 |
|
- try: |
1184 |
|
- key = int(arg) |
1185 |
|
- except: |
1186 |
|
- print('ERROR: Key specified with -k must be an integer') |
1187 |
|
- elif opt in [ '-t', '--title']: |
1188 |
|
- title = arg |
1189 |
|
- elif opt in [ '-c', '--config']: |
1190 |
|
- config = arg |
1191 |
|
- else: |
1192 |
|
- print('ERROR: Unhandled option') |
1193 |
|
- usage() |
1194 |
|
- |
1195 |
|
- if not args: |
1196 |
|
- nncli(sync, verbose, config).gui(key) |
1197 |
|
- return |
1198 |
|
- |
1199 |
|
- def nncli_start(sync=sync, verbose=verbose, config=config): |
1200 |
|
- sn = nncli(sync, verbose, config) |
1201 |
|
- if sync: sn.sync_notes() |
1202 |
|
- return sn |
1203 |
|
- |
1204 |
|
- if args[0] == 'sync': |
1205 |
|
- sn = nncli_start(True) |
1206 |
|
- |
1207 |
|
- elif args[0] == 'list': |
1208 |
|
- |
1209 |
|
- sn = nncli_start() |
1210 |
|
- sn.cli_list_notes(regex, ' '.join(args[1:])) |
1211 |
|
- |
1212 |
|
- elif args[0] == 'dump': |
1213 |
|
- |
1214 |
|
- sn = nncli_start() |
1215 |
|
- if key: |
1216 |
|
- sn.cli_note_dump(key) |
1217 |
|
- else: |
1218 |
|
- sn.cli_dump_notes(regex, ' '.join(args[1:])) |
1219 |
|
- |
1220 |
|
- elif args[0] == 'create': |
1221 |
|
- |
1222 |
|
- if len(args) == 1: |
1223 |
|
- sn = nncli_start() |
1224 |
|
- sn.cli_note_create(False, title) |
1225 |
|
- elif len(args) == 2 and args[1] == '-': |
1226 |
|
- sn = nncli_start() |
1227 |
|
- sn.cli_note_create(True, title) |
1228 |
|
- else: |
1229 |
|
- usage() |
1230 |
|
- |
1231 |
|
- elif args[0] == 'import': |
1232 |
|
- |
1233 |
|
- if len(args) == 1: |
1234 |
|
- sn = nncli_start() |
1235 |
|
- sn.cli_note_import(False) |
1236 |
|
- elif len(args) == 2 and args[1] == '-': |
1237 |
|
- sn = nncli_start() |
1238 |
|
- sn.cli_note_import(True) |
1239 |
|
- else: |
1240 |
|
- usage() |
1241 |
|
- |
1242 |
|
- elif args[0] == 'export': |
1243 |
|
- |
1244 |
|
- sn = nncli_start() |
1245 |
|
- if key: |
1246 |
|
- sn.cli_note_export(key) |
1247 |
|
- else: |
1248 |
|
- sn.cli_export_notes(regex, ' '.join(args[1:])) |
1249 |
|
- |
1250 |
|
- elif args[0] == 'edit': |
1251 |
|
- |
1252 |
|
- if not key: |
1253 |
|
- usage() |
1254 |
|
- |
1255 |
|
- sn = nncli_start() |
1256 |
|
- sn.cli_note_edit(key) |
1257 |
|
- |
1258 |
|
- elif args[0] == 'delete': |
1259 |
|
- |
1260 |
|
- if not key: |
1261 |
|
- usage() |
1262 |
|
- |
1263 |
|
- sn = nncli_start() |
1264 |
|
- sn.cli_note_delete(key, True) |
1265 |
|
- |
1266 |
|
- elif args[0] == 'favorite' or args[0] == 'unfavorite': |
1267 |
|
- |
1268 |
|
- if not key: |
1269 |
|
- usage() |
1270 |
|
- |
1271 |
|
- sn = nncli_start() |
1272 |
|
- sn.cli_note_favorite(key, 1 if args[0] == 'favorite' else 0) |
1273 |
|
- |
1274 |
|
- # Category API |
1275 |
|
- elif args[0] == 'cat': |
1276 |
|
- |
1277 |
|
- if not key: |
1278 |
|
- usage() |
1279 |
|
- |
1280 |
|
- nargs = len(args) |
1281 |
|
- correct_other = (args[1] in ['get', 'rm'] and nargs == 2) |
1282 |
|
- correct_set = (args[1] == 'set' and nargs == 3) |
1283 |
|
- if not (correct_set or correct_other): |
1284 |
|
- usage() |
1285 |
|
- |
1286 |
|
- if args[1] == 'get': |
1287 |
|
- |
1288 |
|
- sn = nncli_start() |
1289 |
|
- category = sn.cli_note_category_get(key) |
1290 |
|
- if category: |
1291 |
|
- print(category) |
1292 |
|
- |
1293 |
|
- elif args[1] == 'set': |
1294 |
|
- |
1295 |
|
- category = args[2] |
1296 |
|
- sn = nncli_start() |
1297 |
|
- sn.cli_note_category_set(key, category) |
1298 |
|
- |
1299 |
|
- elif args[1] == 'rm': |
1300 |
|
- |
1301 |
|
- sn = nncli_start() |
1302 |
|
- sn.cli_note_category_rm(key) |
1303 |
|
- |
1304 |
|
- else: |
1305 |
|
- usage() |
nncli/notes_db.py
1 |
|
-# -*- encoding: utf-8 -*- |
|
1 |
+# -*- coding: utf-8 -*- |
2 |
2 |
|
3 |
3 |
import os, time, re, glob, json, copy, threading |
4 |
4 |
from . import utils |
5 |
|
-from . import nextcloud_note |
6 |
|
-nextcloud_note.NOTE_FETCH_LENGTH=100 |
7 |
5 |
from .nextcloud_note import NextcloudNote |
8 |
6 |
import logging |
9 |
7 |
|
|
|
. . . |
63 |
61 |
self.note = NextcloudNote(self.config.get_config('nn_username'), |
64 |
62 |
self.config.get_config('nn_password'), |
65 |
63 |
self.config.get_config('nn_host')) |
66 |
|
- |
67 |
|
- # we'll use this to store which notes are currently being synced by |
68 |
|
- # the background thread, so we don't add them anew if they're still |
69 |
|
- # in progress. This variable is only used by the background thread. |
70 |
|
- self.threaded_syncing_keys = {} |
71 |
64 |
|
72 |
65 |
def filtered_notes_sort(self, filtered_notes, sort_mode='date'): |
73 |
66 |
if sort_mode == 'date': |
|
|
. . . |
321 |
314 |
def get_note(self, key): |
322 |
315 |
return self.notes[key] |
323 |
316 |
|
324 |
|
- def get_note_favorite(self, key): |
325 |
|
- return self.notes[key].get('favorite') |
326 |
|
- |
327 |
317 |
def get_note_category(self, key): |
328 |
318 |
return self.notes[key].get('category') |
329 |
319 |
|
330 |
|
- def get_note_content(self, key): |
331 |
|
- return self.notes[key].get('content') |
332 |
|
- |
333 |
320 |
def flag_what_changed(self, note, what_changed): |
334 |
321 |
if 'what_changed' not in note: |
335 |
322 |
note['what_changed'] = [] |
|
|
. . . |
466 |
453 |
del cn['favorite'] |
467 |
454 |
del cn['what_changed'] |
468 |
455 |
|
469 |
|
- if 'favorite' in cn: |
470 |
|
- cn['favorite'] = str.lower(str(cn['favorite'])) |
471 |
|
- |
472 |
456 |
if n['deleted']: |
473 |
457 |
uret = self.note.delete_note(cn) |
474 |
458 |
else: |
|
|
. . . |
520 |
504 |
# a new note and key is not in local store |
521 |
505 |
# retrieve note, update note with response |
522 |
506 |
if not skip_remote_syncing: |
523 |
|
- len_nl = len(nl) |
524 |
507 |
for note_index, n in enumerate(nl): |
525 |
508 |
k = n.get('id') |
526 |
509 |
c = n.get('category') if n.get('category') is not None \ |
|
|
. . . |
602 |
585 |
o = utils.KeyValueObject(saved=False, synced=False, modified=False) |
603 |
586 |
modified = float(n['modified']) |
604 |
587 |
savedate = float(n['savedate']) |
605 |
|
- syncdate = float(n['syncdate']) |
606 |
588 |
|
607 |
589 |
if savedate > modified: |
608 |
590 |
o.saved = True |
609 |
|
- else: |
610 |
|
- o.modified = True |
611 |
|
- |
612 |
|
- if syncdate > modified: |
613 |
|
- o.synced = True |
614 |
|
- |
615 |
591 |
return o |
616 |
592 |
|
617 |
593 |
def verify_all_saved(self): |
nncli/temp.py
1 |
|
-# -*- encoding: utf-8 -*- |
|
1 |
+# -*- coding: utf-8 -*- |
2 |
2 |
|
3 |
3 |
import os, json, tempfile |
4 |
4 |
|
nncli/user_input.py
1 |
|
-# -*- encoding: utf-8 -*- |
|
1 |
+# -*- coding: utf-8 -*- |
2 |
2 |
|
3 |
3 |
import urwid |
4 |
4 |
|
nncli/utils.py
1 |
|
-# -*- encoding: utf-8 -*- |
|
1 |
+# -*- coding: utf-8 -*- |
2 |
2 |
|
3 |
3 |
import datetime, random, re |
4 |
4 |
|
|
|
. . . |
33 |
33 |
return note['title'] |
34 |
34 |
else: |
35 |
35 |
return '' |
36 |
|
- |
37 |
|
-def get_note_title_file(note): |
38 |
|
- mo = note_title_re.match(note.get('content', '')) |
39 |
|
- if mo: |
40 |
|
- fn = mo.groups()[0] |
41 |
|
- fn = fn.replace(' ', '_') |
42 |
|
- fn = fn.replace('/', '_') |
43 |
|
- if not fn: |
44 |
|
- return '' |
45 |
|
- |
46 |
|
- if isinstance(fn, str): |
47 |
|
- fn = str(fn, 'utf-8') |
48 |
|
- else: |
49 |
|
- fn = str(fn) |
50 |
|
- |
51 |
|
- fn += '.mkdn' |
52 |
|
- return fn |
53 |
|
- else: |
54 |
|
- return '' |
55 |
|
- |
56 |
|
-def human_date(timestamp): |
57 |
|
- """ |
58 |
|
- Given a timestamp, return pretty human format representation. |
59 |
|
- |
60 |
|
- For example, if timestamp is: |
61 |
|
- * today, then do "15:11" |
62 |
|
- * else if it is this year, then do "Aug 4" |
63 |
|
- * else do "Dec 11, 2011" |
64 |
|
- """ |
65 |
|
- |
66 |
|
- # this will also give us timestamp in the local timezone |
67 |
|
- dt = datetime.datetime.fromtimestamp(timestamp) |
68 |
|
- # this returns localtime |
69 |
|
- now = datetime.datetime.now() |
70 |
|
- |
71 |
|
- if dt.date() == now.date(): |
72 |
|
- # today: 15:11 |
73 |
|
- return dt.strftime('%H:%M') |
74 |
|
- |
75 |
|
- elif dt.year == now.year: |
76 |
|
- # this year: Aug 6 |
77 |
|
- # format code %d unfortunately 0-pads |
78 |
|
- return dt.strftime('%b') + ' ' + str(dt.day) |
79 |
|
- |
80 |
|
- else: |
81 |
|
- # not today or this year, so we do "Dec 11, 2011" |
82 |
|
- return '%s %d, %d' % (dt.strftime('%b'), dt.day, dt.year) |
83 |
36 |
|
84 |
37 |
def note_favorite(n): |
85 |
38 |
if 'favorite' in n: |
nncli/view_help.py
1 |
|
-# -*- encoding: utf-8 -*- |
|
1 |
+# -*- coding: utf-8 -*- |
2 |
2 |
|
3 |
3 |
import re, urwid |
4 |
4 |
|
nncli/view_log.py
1 |
|
-# -*- encoding: utf-8 -*- |
|
1 |
+# -*- coding: utf-8 -*- |
2 |
2 |
|
3 |
3 |
import urwid |
4 |
4 |
|
nncli/view_note.py
1 |
|
-# -*- encoding: utf-8 -*- |
|
1 |
+# -*- coding: utf-8 -*- |
2 |
2 |
|
3 |
3 |
import time, urwid |
4 |
4 |
from . import utils |
nncli/view_titles.py
1 |
|
-# encoding: utf-8 -*- |
|
1 |
+# -*- coding: utf-8 -*- |
2 |
2 |
|
3 |
3 |
import re, time, datetime, urwid, subprocess |
4 |
4 |
from . import utils, view_note |
pyproject.toml
8 |
8 |
author-email = "daniel@danielmoch.com" |
9 |
9 |
home-page = "https://github.com/djmoch/nncli" |
10 |
10 |
description-file = "README.rst" |
11 |
|
-requires = ["urwid", "requests", "appdirs"] |
|
11 |
+requires = ["urwid", "requests", "appdirs", "click"] |
12 |
12 |
classifiers = ["License :: OSI Approved :: MIT License", |
13 |
13 |
"Development Status :: 4 - Beta", |
14 |
14 |
"Environment :: Console :: Curses", |
|
|
. . . |
17 |
17 |
"Programming Language :: Python :: 3 :: Only"] |
18 |
18 |
requires-python = ">=3" |
19 |
19 |
|
|
20 |
+[tool.flit.metadata.urls] |
|
21 |
+Documentation = "https://nncli.readthedocs.io/en/latest" |
|
22 |
+ |
20 |
23 |
[tool.flit.metadata.requires-extra] |
21 |
24 |
dev = ["pipenv"] |
22 |
25 |
|
23 |
26 |
[tool.flit.scripts] |
24 |
|
-nncli = "nncli.nncli:main" |
|
27 |
+nncli = "nncli.cli:main" |
tests/test_config.py
1 |
|
-# -*- encoding: utf-8 -*- |
|
1 |
+# -*- coding: utf-8 -*- |
2 |
2 |
|
3 |
3 |
import os |
4 |
4 |
import sys |
tests/test_nncli.py
1 |
|
-# -*- encoding: utf-8 -*- |
|
1 |
+# -*- coding: utf-8 -*- |
2 |
2 |
|
3 |
3 |
import logging |
4 |
4 |
import os |
|
|
. . . |
30 |
30 |
|
31 |
31 |
def test_init_no_tempdir(mocker, mock_nncli): |
32 |
32 |
mock_get_config(mocker, ['what', '', 'duh', 'duh', 'duh']) |
33 |
|
- nn = nncli.nncli.nncli(False) |
|
33 |
+ nn = nncli.nncli.Nncli(False) |
34 |
34 |
assert_initialized() |
35 |
35 |
assert nn.tempdir == None |
36 |
36 |
os.mkdir.assert_called_with('duh') |
37 |
37 |
|
38 |
38 |
def test_init(mocker, mock_nncli): |
39 |
39 |
mock_get_config(mocker, ['what', 'blah', 'duh', 'duh', 'duh']) |
40 |
|
- nn = nncli.nncli.nncli(False) |
|
40 |
+ nn = nncli.nncli.Nncli(False) |
41 |
41 |
assert_initialized() |
42 |
42 |
assert nn.tempdir == 'blah' |
43 |
43 |
|
|
|
. . . |
47 |
47 |
new=mocker.MagicMock(side_effect=SystemExit) |
48 |
48 |
) |
49 |
49 |
with pytest.raises(SystemExit): |
50 |
|
- nn = nncli.nncli.nncli(False) |
|
50 |
+ nn = nncli.nncli.Nncli(False) |
51 |
51 |
|
52 |
52 |
def test_get_editor(mocker, mock_nncli): |
53 |
53 |
mock_get_config(mocker, ['what', 'blah', 'duh', 'duh', 'duh', 'vim', '']) |
54 |
|
- nn = nncli.nncli.nncli(False) |
|
54 |
+ nn = nncli.nncli.Nncli(False) |
55 |
55 |
assert_initialized() |
56 |
56 |
assert nn.get_editor() == 'vim' |
57 |
57 |
assert nn.get_editor() == None |
58 |
58 |
|
59 |
59 |
def test_get_pager(mocker, mock_nncli): |
60 |
60 |
mock_get_config(mocker, ['what', 'blah', 'duh', 'duh', 'duh', 'less', '']) |
61 |
|
- nn = nncli.nncli.nncli(False) |
|
61 |
+ nn = nncli.nncli.Nncli(False) |
62 |
62 |
assert_initialized() |
63 |
63 |
assert nn.get_editor() == 'less' |
64 |
64 |
assert nn.get_editor() == None |
65 |
65 |
|
66 |
66 |
def test_get_diff(mocker, mock_nncli): |
67 |
67 |
mock_get_config(mocker, ['what', 'blah', 'duh', 'duh', 'duh', 'diff', '']) |
68 |
|
- nn = nncli.nncli.nncli(False) |
|
68 |
+ nn = nncli.nncli.Nncli(False) |
69 |
69 |
assert_initialized() |
70 |
70 |
assert nn.get_editor() == 'diff' |
71 |
71 |
assert nn.get_editor() == None |
|
|
. . . |
73 |
73 |
@pytest.mark.skip |
74 |
74 |
def test_exec_cmd_on_note(mocker, mock_nncli): |
75 |
75 |
mocker.patch.object( |
76 |
|
- 'nncli.nncli.nncli', |
|
76 |
+ 'nncli.nncli.Nncli', |
77 |
77 |
get_editor, |
78 |
78 |
new=mocker.MagicMock(return_value='vim')) |
79 |
79 |
mocker.patch('nncli.temp.tempfile_create') |