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')