Diff

from v0.1.1 to v0.1.2

Diffstat

 .gitignore | 19 -
 Pipfile | 19 ++
 Pipfile.lock | 287 ++++++++++++++++++++++++++++++++++++++
 README.md | 22 +-
 nnotes_cli/__init__.py | 11 +
 nnotes_cli/config.py | 27 --
 nnotes_cli/nextcloud_note.py | 45 +++--
 nnotes_cli/nncli.py | 38 +++-
 requirements.txt | 2 
 setup.cfg | 12 +
 setup.py | 8 
 tests/test_config.py | 99 +++++++++++++
 tests/test_nncli.py | 278 ++++++++++++++++++++++++++++++++++++
 tests/test_version.py | 35 ++++

.gitignore

1 -# byte-compiled python files
2 -*.pyc
3 -
4 -# venv dirs
5 -env/
6 -venv/
7 -
8 -# python packaging files/dirs
9 -build
10 -dist
1 +build/
2 +dist/
11 3 MANIFEST
12 -*.egg-info/*
13 -
14 -.ropeproject
4 +*.egg-info/
5 +nnotes_cli/version.py
6 +.coverage
7 +.pytest_cache/

Pipfile (created)

1 +[[source]]
2 +url = "https://pypi.org/simple"
3 +verify_ssl = true
4 +name = "pypi"
5 +
6 +[packages]
7 +appdirs = "*"
8 +requests = "*"
9 +urwid = "*"
10 +setuptools = "*"
11 +setuptools-scm = "*"
12 +
13 +[dev-packages]
14 +pytest = "*"
15 +pytest-cov = "*"
16 +pytest-mock = "*"
17 +pytest-runner = "*"
18 +pylint = "*"
19 +pudb = "*"

Pipfile.lock (created)

1 +{
2 + "_meta": {
3 + "hash": {
4 + "sha256": "b4bc66838172733ffa35a5998d87982699b21d67eccdb8d913fffd8c7dcd101a"
5 + },
6 + "pipfile-spec": 6,
7 + "requires": {},
8 + "sources": [
9 + {
10 + "name": "pypi",
11 + "url": "https://pypi.org/simple",
12 + "verify_ssl": true
13 + }
14 + ]
15 + },
16 + "default": {
17 + "appdirs": {
18 + "hashes": [
19 + "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92",
20 + "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"
21 + ],
22 + "index": "pypi",
23 + "version": "==1.4.3"
24 + },
25 + "certifi": {
26 + "hashes": [
27 + "sha256:376690d6f16d32f9d1fe8932551d80b23e9d393a8578c5633a2ed39a64861638",
28 + "sha256:456048c7e371c089d0a77a5212fb37a2c2dce1e24146e3b7e0261736aaeaa22a"
29 + ],
30 + "version": "==2018.8.24"
31 + },
32 + "chardet": {
33 + "hashes": [
34 + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
35 + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
36 + ],
37 + "version": "==3.0.4"
38 + },
39 + "idna": {
40 + "hashes": [
41 + "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
42 + "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"
43 + ],
44 + "version": "==2.7"
45 + },
46 + "requests": {
47 + "hashes": [
48 + "sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1",
49 + "sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a"
50 + ],
51 + "index": "pypi",
52 + "version": "==2.19.1"
53 + },
54 + "setuptools-scm": {
55 + "hashes": [
56 + "sha256:1191f2a136b5e86f7ca8ab00a97ef7aef997131f1f6d4971be69a1ef387d8b40",
57 + "sha256:cc6953d224a22f10e933fa2f55c95979317c55259016adcf93310ba2997febfa"
58 + ],
59 + "index": "pypi",
60 + "version": "==3.1.0"
61 + },
62 + "urllib3": {
63 + "hashes": [
64 + "sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf",
65 + "sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5"
66 + ],
67 + "markers": "python_version != '3.1.*' and python_version != '3.3.*' and python_version >= '2.6' and python_version != '3.0.*' and python_version != '3.2.*' and python_version < '4'",
68 + "version": "==1.23"
69 + },
70 + "urwid": {
71 + "hashes": [
72 + "sha256:644d3e3900867161a2fc9287a9762753d66bd194754679adb26aede559bcccbc"
73 + ],
74 + "index": "pypi",
75 + "version": "==2.0.1"
76 + }
77 + },
78 + "develop": {
79 + "astroid": {
80 + "hashes": [
81 + "sha256:292fa429e69d60e4161e7612cb7cc8fa3609e2e309f80c224d93a76d5e7b58be",
82 + "sha256:c7013d119ec95eb626f7a2011f0b63d0c9a095df9ad06d8507b37084eada1a8d"
83 + ],
84 + "version": "==2.0.4"
85 + },
86 + "atomicwrites": {
87 + "hashes": [
88 + "sha256:6b5282987b21cd79151f51caccead7a09d0a32e89c568bd9e3c4aaa7bbdf3f3a",
89 + "sha256:e16334d50fe0f90919ef7339c24b9b62e6abaa78cd2d226f3d94eb067eb89043"
90 + ],
91 + "markers": "python_version != '3.2.*' and python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.0.*'",
92 + "version": "==1.2.0"
93 + },
94 + "attrs": {
95 + "hashes": [
96 + "sha256:4b90b09eeeb9b88c35bc642cbac057e45a5fd85367b985bd2809c62b7b939265",
97 + "sha256:e0d0eb91441a3b53dab4d9b743eafc1ac44476296a2053b6ca3af0b139faf87b"
98 + ],
99 + "version": "==18.1.0"
100 + },
101 + "coverage": {
102 + "hashes": [
103 + "sha256:03481e81d558d30d230bc12999e3edffe392d244349a90f4ef9b88425fac74ba",
104 + "sha256:0b136648de27201056c1869a6c0d4e23f464750fd9a9ba9750b8336a244429ed",
105 + "sha256:10a46017fef60e16694a30627319f38a2b9b52e90182dddb6e37dcdab0f4bf95",
106 + "sha256:198626739a79b09fa0a2f06e083ffd12eb55449b5f8bfdbeed1df4910b2ca640",
107 + "sha256:23d341cdd4a0371820eb2b0bd6b88f5003a7438bbedb33688cd33b8eae59affd",
108 + "sha256:28b2191e7283f4f3568962e373b47ef7f0392993bb6660d079c62bd50fe9d162",
109 + "sha256:2a5b73210bad5279ddb558d9a2bfedc7f4bf6ad7f3c988641d83c40293deaec1",
110 + "sha256:2eb564bbf7816a9d68dd3369a510be3327f1c618d2357fa6b1216994c2e3d508",
111 + "sha256:337ded681dd2ef9ca04ef5d93cfc87e52e09db2594c296b4a0a3662cb1b41249",
112 + "sha256:3a2184c6d797a125dca8367878d3b9a178b6fdd05fdc2d35d758c3006a1cd694",
113 + "sha256:3c79a6f7b95751cdebcd9037e4d06f8d5a9b60e4ed0cd231342aa8ad7124882a",
114 + "sha256:3d72c20bd105022d29b14a7d628462ebdc61de2f303322c0212a054352f3b287",
115 + "sha256:3eb42bf89a6be7deb64116dd1cc4b08171734d721e7a7e57ad64cc4ef29ed2f1",
116 + "sha256:4635a184d0bbe537aa185a34193898eee409332a8ccb27eea36f262566585000",
117 + "sha256:56e448f051a201c5ebbaa86a5efd0ca90d327204d8b059ab25ad0f35fbfd79f1",
118 + "sha256:5a13ea7911ff5e1796b6d5e4fbbf6952381a611209b736d48e675c2756f3f74e",
119 + "sha256:69bf008a06b76619d3c3f3b1983f5145c75a305a0fea513aca094cae5c40a8f5",
120 + "sha256:6bc583dc18d5979dc0f6cec26a8603129de0304d5ae1f17e57a12834e7235062",
121 + "sha256:701cd6093d63e6b8ad7009d8a92425428bc4d6e7ab8d75efbb665c806c1d79ba",
122 + "sha256:7608a3dd5d73cb06c531b8925e0ef8d3de31fed2544a7de6c63960a1e73ea4bc",
123 + "sha256:76ecd006d1d8f739430ec50cc872889af1f9c1b6b8f48e29941814b09b0fd3cc",
124 + "sha256:7aa36d2b844a3e4a4b356708d79fd2c260281a7390d678a10b91ca595ddc9e99",
125 + "sha256:7d3f553904b0c5c016d1dad058a7554c7ac4c91a789fca496e7d8347ad040653",
126 + "sha256:7e1fe19bd6dce69d9fd159d8e4a80a8f52101380d5d3a4d374b6d3eae0e5de9c",
127 + "sha256:8c3cb8c35ec4d9506979b4cf90ee9918bc2e49f84189d9bf5c36c0c1119c6558",
128 + "sha256:9d6dd10d49e01571bf6e147d3b505141ffc093a06756c60b053a859cb2128b1f",
129 + "sha256:be6cfcd8053d13f5f5eeb284aa8a814220c3da1b0078fa859011c7fffd86dab9",
130 + "sha256:c1bb572fab8208c400adaf06a8133ac0712179a334c09224fb11393e920abcdd",
131 + "sha256:de4418dadaa1c01d497e539210cb6baa015965526ff5afc078c57ca69160108d",
132 + "sha256:e05cb4d9aad6233d67e0541caa7e511fa4047ed7750ec2510d466e806e0255d6",
133 + "sha256:f3f501f345f24383c0000395b26b726e46758b71393267aeae0bd36f8b3ade80"
134 + ],
135 + "markers": "python_version < '4' and python_version != '3.2.*' and python_version != '3.0.*' and python_version != '3.1.*' and python_version >= '2.6'",
136 + "version": "==4.5.1"
137 + },
138 + "isort": {
139 + "hashes": [
140 + "sha256:1153601da39a25b14ddc54955dbbacbb6b2d19135386699e2ad58517953b34af",
141 + "sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8",
142 + "sha256:ec9ef8f4a9bc6f71eec99e1806bfa2de401650d996c59330782b89a5555c1497"
143 + ],
144 + "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.*'",
145 + "version": "==4.3.4"
146 + },
147 + "lazy-object-proxy": {
148 + "hashes": [
149 + "sha256:0ce34342b419bd8f018e6666bfef729aec3edf62345a53b537a4dcc115746a33",
150 + "sha256:1b668120716eb7ee21d8a38815e5eb3bb8211117d9a90b0f8e21722c0758cc39",
151 + "sha256:209615b0fe4624d79e50220ce3310ca1a9445fd8e6d3572a896e7f9146bbf019",
152 + "sha256:27bf62cb2b1a2068d443ff7097ee33393f8483b570b475db8ebf7e1cba64f088",
153 + "sha256:27ea6fd1c02dcc78172a82fc37fcc0992a94e4cecf53cb6d73f11749825bd98b",
154 + "sha256:2c1b21b44ac9beb0fc848d3993924147ba45c4ebc24be19825e57aabbe74a99e",
155 + "sha256:2df72ab12046a3496a92476020a1a0abf78b2a7db9ff4dc2036b8dd980203ae6",
156 + "sha256:320ffd3de9699d3892048baee45ebfbbf9388a7d65d832d7e580243ade426d2b",
157 + "sha256:50e3b9a464d5d08cc5227413db0d1c4707b6172e4d4d915c1c70e4de0bbff1f5",
158 + "sha256:5276db7ff62bb7b52f77f1f51ed58850e315154249aceb42e7f4c611f0f847ff",
159 + "sha256:61a6cf00dcb1a7f0c773ed4acc509cb636af2d6337a08f362413c76b2b47a8dd",
160 + "sha256:6ae6c4cb59f199d8827c5a07546b2ab7e85d262acaccaacd49b62f53f7c456f7",
161 + "sha256:7661d401d60d8bf15bb5da39e4dd72f5d764c5aff5a86ef52a042506e3e970ff",
162 + "sha256:7bd527f36a605c914efca5d3d014170b2cb184723e423d26b1fb2fd9108e264d",
163 + "sha256:7cb54db3535c8686ea12e9535eb087d32421184eacc6939ef15ef50f83a5e7e2",
164 + "sha256:7f3a2d740291f7f2c111d86a1c4851b70fb000a6c8883a59660d95ad57b9df35",
165 + "sha256:81304b7d8e9c824d058087dcb89144842c8e0dea6d281c031f59f0acf66963d4",
166 + "sha256:933947e8b4fbe617a51528b09851685138b49d511af0b6c0da2539115d6d4514",
167 + "sha256:94223d7f060301b3a8c09c9b3bc3294b56b2188e7d8179c762a1cda72c979252",
168 + "sha256:ab3ca49afcb47058393b0122428358d2fbe0408cf99f1b58b295cfeb4ed39109",
169 + "sha256:bd6292f565ca46dee4e737ebcc20742e3b5be2b01556dafe169f6c65d088875f",
170 + "sha256:cb924aa3e4a3fb644d0c463cad5bc2572649a6a3f68a7f8e4fbe44aaa6d77e4c",
171 + "sha256:d0fc7a286feac9077ec52a927fc9fe8fe2fabab95426722be4c953c9a8bede92",
172 + "sha256:ddc34786490a6e4ec0a855d401034cbd1242ef186c20d79d2166d6a4bd449577",
173 + "sha256:e34b155e36fa9da7e1b7c738ed7767fc9491a62ec6af70fe9da4a057759edc2d",
174 + "sha256:e5b9e8f6bda48460b7b143c3821b21b452cb3a835e6bbd5dd33aa0c8d3f5137d",
175 + "sha256:e81ebf6c5ee9684be8f2c87563880f93eedd56dd2b6146d8a725b50b7e5adb0f",
176 + "sha256:eb91be369f945f10d3a49f5f9be8b3d0b93a4c2be8f8a5b83b0571b8123e0a7a",
177 + "sha256:f460d1ceb0e4a5dcb2a652db0904224f367c9b3c1470d5a7683c0480e582468b"
178 + ],
179 + "version": "==1.3.1"
180 + },
181 + "mccabe": {
182 + "hashes": [
183 + "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
184 + "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
185 + ],
186 + "version": "==0.6.1"
187 + },
188 + "more-itertools": {
189 + "hashes": [
190 + "sha256:c187a73da93e7a8acc0001572aebc7e3c69daf7bf6881a2cea10650bd4420092",
191 + "sha256:c476b5d3a34e12d40130bc2f935028b5f636df8f372dc2c1c01dc19681b2039e",
192 + "sha256:fcbfeaea0be121980e15bc97b3817b5202ca73d0eae185b4550cbfce2a3ebb3d"
193 + ],
194 + "version": "==4.3.0"
195 + },
196 + "pluggy": {
197 + "hashes": [
198 + "sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1",
199 + "sha256:95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1"
200 + ],
201 + "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'",
202 + "version": "==0.7.1"
203 + },
204 + "pudb": {
205 + "hashes": [
206 + "sha256:8d8b974641b7a7a2a721af01c9dce5eac8e05a2ceebc2680725ba8eef1ca876e"
207 + ],
208 + "index": "pypi",
209 + "version": "==2018.1"
210 + },
211 + "py": {
212 + "hashes": [
213 + "sha256:06a30435d058473046be836d3fc4f27167fd84c45b99704f2fb5509ef61f9af1",
214 + "sha256:50402e9d1c9005d759426988a492e0edaadb7f4e68bcddfea586bc7432d009c6"
215 + ],
216 + "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'",
217 + "version": "==1.6.0"
218 + },
219 + "pygments": {
220 + "hashes": [
221 + "sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d",
222 + "sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc"
223 + ],
224 + "version": "==2.2.0"
225 + },
226 + "pylint": {
227 + "hashes": [
228 + "sha256:1d6d3622c94b4887115fe5204982eee66fdd8a951cf98635ee5caee6ec98c3ec",
229 + "sha256:31142f764d2a7cd41df5196f9933b12b7ee55e73ef12204b648ad7e556c119fb"
230 + ],
231 + "index": "pypi",
232 + "version": "==2.1.1"
233 + },
234 + "pytest": {
235 + "hashes": [
236 + "sha256:2e7c330338b2732ddb992217962e3454aa7290434e75329b1a6739cea41bea6b",
237 + "sha256:4abcd98faeea3eb95bd05aa6a7b121d5f89d72e4d36ddb0dcbbfd1ec9f3651d1"
238 + ],
239 + "index": "pypi",
240 + "version": "==3.7.3"
241 + },
242 + "pytest-cov": {
243 + "hashes": [
244 + "sha256:03aa752cf11db41d281ea1d807d954c4eda35cfa1b21d6971966cc041bbf6e2d",
245 + "sha256:890fe5565400902b0c78b5357004aab1c814115894f4f21370e2433256a3eeec"
246 + ],
247 + "index": "pypi",
248 + "version": "==2.5.1"
249 + },
250 + "pytest-mock": {
251 + "hashes": [
252 + "sha256:53801e621223d34724926a5c98bd90e8e417ce35264365d39d6c896388dcc928",
253 + "sha256:d89a8209d722b8307b5e351496830d5cc5e192336003a485443ae9adeb7dd4c0"
254 + ],
255 + "index": "pypi",
256 + "version": "==1.10.0"
257 + },
258 + "pytest-runner": {
259 + "hashes": [
260 + "sha256:d23f117be39919f00dd91bffeb4f15e031ec797501b717a245e377aee0f577be",
261 + "sha256:d987fec1e31287592ffe1cb823a8c613c533db4c6aaca0ee1191dbc91e2fcc61"
262 + ],
263 + "index": "pypi",
264 + "version": "==4.2"
265 + },
266 + "six": {
267 + "hashes": [
268 + "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
269 + "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
270 + ],
271 + "version": "==1.11.0"
272 + },
273 + "urwid": {
274 + "hashes": [
275 + "sha256:644d3e3900867161a2fc9287a9762753d66bd194754679adb26aede559bcccbc"
276 + ],
277 + "index": "pypi",
278 + "version": "==2.0.1"
279 + },
280 + "wrapt": {
281 + "hashes": [
282 + "sha256:d4d560d479f2c21e1b5443bbd15fe7ec4b37fe7e53d335d3b9b0a7b1226fe3c6"
283 + ],
284 + "version": "==1.10.11"
285 + }
286 + }
287 +}

README.md

1 1 nncli
2 2 =====
3 3
4 +![GitHub](https://img.shields.io/github/license/djmoch/nncli.svg)
5 +[![PyPI](https://img.shields.io/pypi/v/nncli.svg)](https://pypi.org/project/nncli/)
6 +
4 7 NextCloud Notes Command Line Interface
5 8
6 9 nncli is a Python application that gives you access to your NextCloud
. . .
32 35 * Manually:
33 36 - Clone this repository to your hard disk: `git clone
34 37 https://github.com/djmoch/nncli.git`
35 - - Install the requirements `pip3 install -r requirements.txt`
36 38 - Install _nncli_: `python3 setup.py install`
39 +* Development:
40 + - Clone the repo
41 + - Install Pipenv: `pip install pipenv`
42 + - Stand up development environment: `pipenv install --dev`
37 43
38 44 ### Features
39 45
. . .
81 87 -r, --regex - search string is a regular expression
82 88 -k <key>, --key=<key> - note key
83 89 -t <title>, --title=<title> - title of note for create (cli mode)
84 - -c <file>, --config=<file> - config file to read from (defaults to
85 - ~/.config/nncli/config)
90 + -c <file>, --config=<file> - config file to read from
91 + --version - version information
86 92
87 93 COMMANDS:
88 94 <none> - console gui mode when no command specified
. . .
109 115 accessible to nncli. Use of the `cfg_nn_password_eval` option is
110 116 recommended (see below).
111 117
112 -nncli pulls in configuration from the `config` file located in your
113 -`$XDG_CONFIG_HOME/nncli` directory. (By default,
114 -`XDG_CONFIG_HOME=$HOME/.config`.) At the very least, the following example
115 -`config` will get you going (using your account information):
118 +nncli pulls in configuration from the `config` file located in the
119 +standard location for your platform. At the very least, the following
120 +example `config` will get you going (using your account information):
116 121
117 122 ```
118 123 [nncli]
. . .
146 151
147 152 # see http://urwid.org/manual/userinput.html for examples of more key
148 153 # combinations
149 -kb_edit_note = space kb_page_down = ctrl f
154 +kb_edit_note = space
155 +kb_page_down = ctrl f
150 156
151 157 # note that values must not be quoted
152 158 clr_note_focus_bg = light blue

nnotes_cli/__init__.py

1 +try:
2 + from . import version
3 + __version__ = version.version
4 +except ImportError:
5 + try:
6 + from setuptools_scm import get_version
7 + __version__ = get_version(root='..', relative_to=__file__)
8 + except:
9 + __version__ = '??-dev'
10 +
1 11 __productname__ = 'nncli'
2 -__version__ = '0.1.1'
3 12 __copyright__ = "Copyright (c) 2018 Daniel Moch"
4 13 __author__ = "Daniel Moch"
5 14 __author_email__ = "daniel@danielmoch.com"

nnotes_cli/config.py

27 27
28 28 import os, sys, urwid, collections, configparser, subprocess
29 29
30 +from appdirs import user_cache_dir, user_config_dir
31 +
30 32 class Config:
31 33
32 34 def __init__(self, custom_file=None):
33 - self.home = os.path.abspath(os.path.expanduser('~'))
34 - if 'XDG_CONFIG_HOME' in os.environ.keys():
35 - self.config_home = \
36 - os.path.join(os.environ['XDG_CONFIG_HOME'], 'nncli')
37 - else:
38 - self.config_home = \
39 - os.path.join(
40 - os.path.expanduser('~'),
41 - '.config',
42 - 'nncli'
43 - )
44 - if 'XDG_CACHE_HOME' in os.environ.keys():
45 - self.cache_home = \
46 - os.path.join(os.environ['XDG_CACHE_HOME'], 'nncli')
47 - else:
48 - self.cache_home = \
49 - os.path.join(
50 - os.path.expanduser('~'),
51 - '.cache',
52 - 'nncli'
53 - )
35 + self.config_home = user_config_dir('nncli', 'djmoch')
36 + self.cache_home = user_cache_dir('nncli', 'djmoch')
37 +
54 38 defaults = \
55 39 {
56 40 'cfg_nn_username' : '',
57 41 'cfg_nn_password' : '',
42 + 'cfg_nn_password_eval' : '',
58 43 'cfg_db_path' : self.cache_home,
59 44 'cfg_search_categories' : 'yes', # with regex searches
60 45 'cfg_sort_mode' : 'date', # 'alpha' or 'date'

nnotes_cli/nextcloud_note.py

65 65
66 66 def __init__(self, username, password, host):
67 67 """ object constructor """
68 - self.username = urllib.parse.quote(username)
69 - self.password = urllib.parse.quote(password)
70 - self.api_url = \
71 - 'https://{}:{}@{}/index.php/apps/notes/api/v0.2/notes'. \
72 - format(username, password, host)
73 - self.sanitized_url = \
74 - 'https://{}:****@{}/index.php/apps/notes/api/v0.2/notes'. \
75 - format(username, host)
68 + self.username = username
69 + self.password = password
70 + self.url = \
71 + 'https://{}/index.php/apps/notes/api/v0.2/notes'. \
72 + format(host)
76 73 self.status = 'offline'
77 74
78 75 def get_note(self, noteid):
. . .
89 86
90 87 """
91 88 # request note
92 - url = '{}/{}'.format(self.api_url, str(noteid))
93 - #logging.debug('REQUEST: ' + self.sanitized_url+params)
89 + url = '{}/{}'.format(self.url, str(noteid))
90 + #logging.debug('REQUEST: ' + self.url+params)
94 91 try:
95 - res = requests.get(url)
92 + res = requests.get(url, auth=(self.username, self.password))
96 93 res.raise_for_status()
97 94 note = res.json()
98 95 self.status = 'online'
. . .
136 133 if 'modified' not in note:
137 134 note["modified"] = int(time.time())
138 135
139 - url = '{}/{}'.format(self.api_url, note["id"])
136 + url = '{}/{}'.format(self.url, note["id"])
140 137 del note["id"]
141 138 else:
142 - url = self.api_url
139 + url = self.url
143 140
144 141 #logging.debug('REQUEST: ' + url + ' - ' + str(note))
145 142 try:
146 143 logging.debug('NOTE: ' + str(note))
147 - if url != self.api_url:
148 - res = requests.put(url, data=note)
144 + if url != self.url:
145 + res = requests.put(url, auth=(self.username,
146 + self.password), json=note)
149 147 else:
150 - res = requests.post(url, data=note)
148 + res = requests.post(url, auth=(self.username,
149 + self.password), json=note)
151 150 note = res.json()
152 151 res.raise_for_status()
153 152 logging.debug('NOTE (from response): ' + str(res.json()))
. . .
219 218
220 219 # perform initial HTTP request
221 220 try:
222 - logging.debug('REQUEST: ' + self.sanitized_url + \
221 + logging.debug('REQUEST: ' + self.url + \
223 222 '?exclude=content')
224 - res = requests.get(self.api_url, params=params)
223 + res = requests.get(self.url, auth=(self.username, self.password), params=params)
225 224 res.raise_for_status()
226 225 #logging.debug('RESPONSE OK: ' + str(res))
227 226 note_list = res.json()
228 227 self.status = 'online'
229 228 except ConnectionError as e:
229 + logging.exception('connection error')
230 230 self.status = 'offline, connection error'
231 231 status = -1
232 232 except RequestException as e:
233 233 # if problem with network request/response
234 + logging.exception('request error')
234 235 status = -1
235 236 except ValueError as e:
236 237 # if invalid json data
237 238 status = -1
239 + logging.exception('request returned bad JSON data')
238 240
239 241 # Can only filter for category at end, once all notes have been
240 242 # retrieved. Below based on simplenote.vim, except we return
. . .
258 260 - status (int): 0 on sucesss and -1 otherwise
259 261
260 262 """
261 - url = '{}/{}'.format(self.api_url, str(note['id']))
262 - logurl = '{}/{}'.format(self.sanitized_url, str(note['id']))
263 + url = '{}/{}'.format(self.url, str(note['id']))
263 264
264 265 try:
265 - logging.debug('REQUEST DELETE: ' + logurl)
266 - res = requests.delete(url)
266 + logging.debug('REQUEST DELETE: ' + url)
267 + res = requests.delete(url, auth=(self.username, self.password))
267 268 res.raise_for_status()
268 269 self.status = 'online'
269 270 except ConnectionError as e:

nnotes_cli/nncli.py

28 28 import os, sys, getopt, re, signal, time, datetime, shlex, hashlib
29 29 import subprocess, threading, logging
30 30 import copy, json, urwid, datetime
31 +import nnotes_cli
31 32 from . import view_titles, view_note, view_help, view_log, user_input
32 33 from . import utils, temp
33 34 from .config import Config
. . .
1145 1146 -r, --regex - search string is a regular expression
1146 1147 -k <key>, --key=<key> - note key
1147 1148 -t <title>, --title=<title> - title of note for create (cli mode)
1148 - -c <file>, --config=<file> - config file to read from (defaults to
1149 - ~/.config/nncli/config)
1149 + -c <file>, --config=<file> - config file to read from
1150 + -V, --version - version information
1150 1151
1151 1152 COMMANDS:
1152 1153 <none> - console gui mode when no command specified
1153 1154 sync - perform a full sync with the server
1154 1155 list [search_string] - list notes (refined with search string)
1155 - export [search_string] - export notes in JSON (refined with search string)
1156 + export [search_string] - export notes in JSON (refined with search
1157 + string)
1156 1158 dump [search_string] - dump notes (refined with search string)
1157 1159 create [-] - create a note ('-' content from stdin)
1158 - import [-] - import a note in JSON format ('-' JSON from stdin)
1159 - export - export a note in JSON format (specified by <key>)
1160 + import [-] - import a note in JSON format ('-' JSON from
1161 + stdin)
1162 + export - export a note in JSON format (specified by
1163 + <key>)
1160 1164 dump - dump a note (specified by <key>)
1161 1165 edit - edit a note (specified by <key>)
1162 1166 delete - delete a note (specified by <key>)
1163 1167 < favorite | unfavorite > - favorite/unfavorite a note (specified by <key>)
1164 - cat get - retrieve the category from a note (specified by <key>)
1168 + cat get - retrieve the category from a note (specified
1169 + by <key>)
1165 1170 cat set <category> - set the category for a note (specified by <key>)
1166 1171 cat rm - remove category from a note (specified by <key>)
1167 1172 ''')
1168 1173 sys.exit(0)
1169 1174
1175 +def version():
1176 + version_info = ''
1177 + version_info += nnotes_cli.__productname__ + ' v' + \
1178 + nnotes_cli.__version__ + "\n"
1179 + version_info += nnotes_cli.__description__ + "\n\n"
1180 + version_info += nnotes_cli.__copyright__ + "\n"
1181 + version_info += "Written by " + nnotes_cli.__author__ + \
1182 + " and others\n"
1183 + version_info += "Licensed under the terms of the " + \
1184 + nnotes_cli.__license__ + " license\n"
1185 + version_info += "The latest code is available at: " + \
1186 + nnotes_cli.__url__
1187 + print(version_info)
1188 + exit(0)
1170 1189
1171 1190 def main(argv=sys.argv[1:]):
1172 1191 verbose = False
. . .
1178 1197
1179 1198 try:
1180 1199 opts, args = getopt.getopt(argv,
1181 - 'hvnrk:t:c:',
1182 - [ 'help', 'verbose', 'nosync', 'regex', 'key=', 'title=', 'config=' ])
1200 + 'hvnrk:t:c:V',
1201 + [ 'help', 'verbose', 'nosync', 'regex', 'key=', 'title=', \
1202 + 'config=', 'version' ])
1183 1203 except:
1184 1204 usage()
1185 1205
1186 1206 for opt, arg in opts:
1187 1207 if opt in [ '-h', '--help']:
1188 1208 usage()
1209 + elif opt in ['-V', '--version' ]:
1210 + version()
1189 1211 elif opt in [ '-v', '--verbose']:
1190 1212 verbose = True
1191 1213 elif opt in [ '-n', '--nosync']:

requirements.txt (deleted)

1 -urwid
2 -requests

setup.cfg (created)

1 +[aliases]
2 +test=pytest
3 +
4 +[tool:pytest]
5 +testpaths = tests
6 +addopts = --cov=nnotes_cli --cov-report=term-missing
7 +
8 +[yapf]
9 +coalesce_brackets = true
10 +dedent_closing_brackets = true
11 +space_between_ending_comma_and_closing_bracket = false
12 +split_arguments_when_comma_terminated = true

setup.py

1 -#!/usr/bin/env python3
2 1 #
3 2 # The MIT License (MIT)
4 3 #
. . .
29 28 from setuptools import setup
30 29 import nnotes_cli
31 30
32 -deps = ['urwid', 'requests']
31 +deps = ['urwid', 'requests', 'appdirs']
32 +test_deps = ['pytest', 'pytest-cov', 'pytest-runner', 'pytest-mock']
33 33
34 34 with open("README.md", "r") as fh:
35 35 long_description = fh.read()
. . .
39 39 description=nnotes_cli.__description__,
40 40 long_description=long_description,
41 41 long_description_content_type="text/markdown",
42 - version=nnotes_cli.__version__,
43 42 author=nnotes_cli.__author__,
44 43 author_email=nnotes_cli.__author_email__,
45 44 url=nnotes_cli.__url__,
46 45 license=nnotes_cli.__license__,
47 46 requires=deps,
48 47 install_requires=deps,
48 + tests_require=test_deps,
49 + use_scm_version= {'write_to': 'nnotes_cli/version.py'},
50 + setup_requires=['setuptools_scm'],
49 51 packages=['nnotes_cli'],
50 52 entry_points={
51 53 'console_scripts': [

tests/test_config.py (created)

1 +#
2 +# The MIT License (MIT)
3 +#
4 +# Copyright (c) 2018 Daniel Moch
5 +#
6 +# Permission is hereby granted, free of charge, to any person obtaining a copy
7 +# of this software and associated documentation files (the "Software"), to deal
8 +# in the Software without restriction, including without limitation the rights
9 +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 +# copies of the Software, and to permit persons to whom the Software is
11 +# furnished to do so, subject to the following conditions:
12 +#
13 +# The above copyright notice and this permission notice shall be included in all
14 +# copies or substantial portions of the Software.
15 +#
16 +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 +# SOFTWARE.
23 +#
24 +import os
25 +import sys
26 +
27 +from nnotes_cli.config import Config
28 +from pytest import raises
29 +
30 +def test_init():
31 + config = Config()
32 +
33 + if sys.platform == 'linux':
34 + assert config.config_home == os.path.join(os.path.expanduser('~'), \
35 + '.config', 'nncli')
36 + assert config.cache_home == os.path.join(os.path.expanduser('~'), \
37 + '.cache', 'nncli')
38 + if sys.platform == 'darwin':
39 + assert config.config_home == os.path.join(os.path.expanduser('~'), \
40 + 'Library', 'Preferences', 'nncli')
41 + assert config.cache_home == os.path.join(os.path.expanduser('~'), \
42 + 'Library', 'Caches', 'nncli')
43 +
44 +
45 +def test_custom_file():
46 + with open('test_cfg', 'w') as config_file:
47 + config_file.write('[nncli]\n')
48 + config_file.write('cfg_nn_username=user\n')
49 + config_file.write('cfg_nn_password=password\n')
50 + config_file.write('cfg_nn_host=nextcloud.example.org\n')
51 +
52 + config = Config('test_cfg')
53 + os.remove('test_cfg')
54 +
55 +def test_bad_password_eval():
56 + with open('test_cfg', 'w') as config_file:
57 + config_file.write('[nncli]\n')
58 + config_file.write('cfg_nn_username=user\n')
59 + config_file.write('cfg_nn_password_eval=password\n')
60 + config_file.write('cfg_nn_host=nextcloud.example.org\n')
61 +
62 + with raises(SystemExit):
63 + config = Config('test_cfg')
64 + os.remove('test_cfg')
65 +
66 +def test_empty_config():
67 + with open('test_cfg', 'w') as config_file:
68 + config_file.write('\n')
69 +
70 + config = Config('test_cfg')
71 + os.remove('test_cfg')
72 +
73 +def test_get_config():
74 + config = Config('test_cfg')
75 + assert config.get_config('sort_mode') == 'date'
76 +
77 +def test_get_config_descr():
78 + config = Config('test_cfg')
79 + assert config.get_config_descr('sort_mode') == 'Sort mode'
80 +
81 +def test_get_keybind():
82 + config = Config('test_cfg')
83 + assert config.get_keybind('help') == 'h'
84 +
85 +def test_get_keybind_use():
86 + config = Config('test_cfg')
87 + assert config.get_keybind_use('help') == [ 'common' ]
88 +
89 +def test_get_keybind_descr():
90 + config = Config('test_cfg')
91 + assert config.get_keybind_descr('help') == 'Help'
92 +
93 +def test_get_color():
94 + config = Config('test_cfg')
95 + assert config.get_color('default_fg') == 'default'
96 +
97 +def test_get_color_descr():
98 + config = Config('test_cfg')
99 + assert config.get_color_descr('default_fg') == 'Default fg'

tests/test_nncli.py (created)

1 +#
2 +# The MIT License (MIT)
3 +#
4 +# Copyright (c) 2018 Daniel Moch
5 +#
6 +# Permission is hereby granted, free of charge, to any person obtaining a copy
7 +# of this software and associated documentation files (the "Software"), to deal
8 +# in the Software without restriction, including without limitation the rights
9 +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 +# copies of the Software, and to permit persons to whom the Software is
11 +# furnished to do so, subject to the following conditions:
12 +#
13 +# The above copyright notice and this permission notice shall be included in all
14 +# copies or substantial portions of the Software.
15 +#
16 +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 +# SOFTWARE.
23 +#
24 +import logging
25 +import os
26 +import pytest
27 +import shutil
28 +
29 +from logging.handlers import RotatingFileHandler
30 +from nnotes_cli.nncli import nncli
31 +
32 +def mock_nncli(mocker):
33 + mocker.patch('logging.getLogger')
34 + mocker.patch('nnotes_cli.config.Config')
35 + mocker.patch('nnotes_cli.notes_db.NotesDB')
36 + mocker.patch('os.mkdir')
37 + mocker.patch.object(RotatingFileHandler, '_open')
38 +
39 +def assert_initialized():
40 + logging.getLogger.assert_called_once()
41 + RotatingFileHandler._open.assert_called_once()
42 + assert os.mkdir.call_count == 2
43 +
44 +@pytest.mark.parametrize('mock_nncli', [mock_nncli])
45 +def test_init_no_tempdir(mocker, mock_nncli):
46 + mock_nncli(mocker)
47 +
48 + with open('test_cfg', 'w') as config_file:
49 + config_file.write('[nncli]\n')
50 + config_file.write('cfg_db_path=duh')
51 +
52 + nn = nncli(False, config_file='test_cfg')
53 + assert_initialized()
54 + assert nn.tempdir == None
55 + os.mkdir.assert_called_with('duh')
56 +
57 + os.remove('test_cfg')
58 +
59 +@pytest.mark.parametrize('mock_nncli', [mock_nncli])
60 +def test_init(mocker, mock_nncli):
61 + mock_nncli(mocker)
62 +
63 + with open('test_cfg', 'w') as config_file:
64 + config_file.write('[nncli]\n')
65 + config_file.write('cfg_tempdir=blah\n')
66 + config_file.write('cfg_db_path=duh')
67 +
68 + nn = nncli(False, config_file='test_cfg')
69 + assert_initialized()
70 + assert nn.tempdir == 'blah'
71 +
72 + os.remove('test_cfg')
73 +
74 +@pytest.mark.parametrize('mock_nncli', [mock_nncli])
75 +def test_init_notesdb_fail(mocker, mock_nncli):
76 + os.mkdir('duh')
77 + mock_nncli(mocker)
78 +
79 + with open('duh/1.json', 'w') as bad_file:
80 + bad_file.write('bad_json_data')
81 +
82 + with open('test_cfg', 'w') as config_file:
83 + config_file.write('[nncli]\n')
84 + config_file.write('cfg_db_path=duh')
85 +
86 + with pytest.raises(SystemExit):
87 + nn = nncli(False, config_file='test_cfg')
88 +
89 + shutil.rmtree('duh')
90 +
91 +@pytest.mark.parametrize('mock_nncli', [mock_nncli])
92 +def test_get_editor(mocker, mock_nncli):
93 + mock_nncli(mocker)
94 +
95 + with open('test_cfg', 'w') as config_file:
96 + config_file.write('[nncli]\n')
97 + config_file.write('cfg_db_path=duh')
98 + config_file.write('cfg_editor=vim')
99 +
100 + nn = nncli(False, config_file='test_cfg')
101 + assert_initialized()
102 + assert nn.get_editor() == 'vim'
103 +
104 + os.remove('test_cfg')
105 +
106 +@pytest.mark.parametrize('mock_nncli', [mock_nncli])
107 +def test_no_editor(mocker, mock_nncli):
108 + mock_nncli(mocker)
109 +
110 + with open('test_cfg', 'w') as config_file:
111 + config_file.write('[nncli]\n')
112 + config_file.write('cfg_db_path=duh')
113 +
114 + nn = nncli(False, config_file='test_cfg')
115 + nn.config.configs['editor'] = ['']
116 + assert_initialized()
117 + assert nn.get_editor() == None
118 +
119 + os.remove('test_cfg')
120 +
121 +def test_get_pager():
122 + pass
123 +
124 +def test_get_diff():
125 + pass
126 +
127 +def test_exec_cmd_on_note():
128 + pass
129 +
130 +def test_exec_diff_on_note():
131 + pass
132 +
133 +def test_gui_header_clear():
134 + pass
135 +
136 +def test_gui_header_set():
137 + pass
138 +
139 +def test_gui_header_get():
140 + pass
141 +
142 +def test_gui_header_focus():
143 + pass
144 +
145 +def test_gui_footer_log_clear():
146 + pass
147 +
148 +def test_gui_footer_log_set():
149 + pass
150 +
151 +def test_gui_footer_log_get():
152 + pass
153 +
154 +def test_gui_footer_input_clear():
155 + pass
156 +
157 +def test_gui_footer_input_set():
158 + pass
159 +
160 +def test_gui_footer_input_get():
161 + pass
162 +
163 +def test_gui_footer_focus_input():
164 + pass
165 +
166 +def test_gui_body_clear():
167 + pass
168 +
169 +def test_gui_body_set():
170 + pass
171 +
172 +def test_gui_body_get():
173 + pass
174 +
175 +def test_gui_body_focus():
176 + pass
177 +
178 +def test_log_timeout():
179 + pass
180 +
181 +def test_log():
182 + pass
183 +
184 +def test_gui_update_view():
185 + pass
186 +
187 +def test_gui_update_status_bar():
188 + pass
189 +
190 +def test_gui_switch_frame_body():
191 + pass
192 +
193 +def test_delete_note_callback():
194 + pass
195 +
196 +def test_gui_yes_no_input():
197 + pass
198 +
199 +def test_gui_search_input():
200 + pass
201 +
202 +def test_gui_category_input():
203 + pass
204 +
205 +def test_gui_pipe_input():
206 + pass
207 +
208 +def test_gui_frame_keypress():
209 + pass
210 +
211 +def test_gui_init_view():
212 + pass
213 +
214 +def test_gui_clear():
215 + pass
216 +
217 +def test_gui_reset():
218 + pass
219 +
220 +def test_gui_stop():
221 + pass
222 +
223 +def test_gui():
224 + pass
225 +
226 +def test_cli_list_notes():
227 + pass
228 +
229 +def test_cli_note_dump():
230 + pass
231 +
232 +def test_cli_dump_notes():
233 + pass
234 +
235 +def test_cli_note_create():
236 + pass
237 +
238 +def test_cli_note_import():
239 + pass
240 +
241 +def test_cli_note_export():
242 + pass
243 +
244 +def test_cli_export_notes():
245 + pass
246 +
247 +def test_cli_note_edit():
248 + pass
249 +
250 +def test_cli_note_delete():
251 + pass
252 +
253 +def test_cli_note_favorite():
254 + pass
255 +
256 +def test_cli_note_category_get():
257 + pass
258 +
259 +def test_cli_note_category_set():
260 + pass
261 +
262 +def test_cli_note_category_rm():
263 + pass
264 +
265 +def test_SIGINT_handler():
266 + pass
267 +
268 +def test_usage():
269 + pass
270 +
271 +def test_version():
272 + pass
273 +
274 +def test_main():
275 + pass
276 +
277 +def test_nncli_start():
278 + pass

tests/test_version.py (created)

1 +#
2 +# The MIT License (MIT)
3 +#
4 +# Copyright (c) 2018 Daniel Moch
5 +#
6 +# Permission is hereby granted, free of charge, to any person obtaining a copy
7 +# of this software and associated documentation files (the "Software"), to deal
8 +# in the Software without restriction, including without limitation the rights
9 +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 +# copies of the Software, and to permit persons to whom the Software is
11 +# furnished to do so, subject to the following conditions:
12 +#
13 +# The above copyright notice and this permission notice shall be included in all
14 +# copies or substantial portions of the Software.
15 +#
16 +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 +# SOFTWARE.
23 +#
24 +import nnotes_cli
25 +import pytest
26 +
27 +from nnotes_cli import version
28 +from setuptools_scm import get_version
29 +
30 +@pytest.mark.skip(reason="test_version will fail outside of a Git repo")
31 +def test_version():
32 + vers = get_version(root="..", relative_to=__file__)
33 +
34 + assert nnotes_cli.__version__ == vers
35 + assert version.version == vers