aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.gitignore19
-rw-r--r--Pipfile19
-rw-r--r--Pipfile.lock287
-rw-r--r--README.md22
-rw-r--r--nnotes_cli/__init__.py11
-rw-r--r--nnotes_cli/config.py27
-rw-r--r--nnotes_cli/nextcloud_note.py45
-rw-r--r--nnotes_cli/nncli.py38
-rw-r--r--requirements.txt2
-rw-r--r--setup.cfg12
-rw-r--r--[-rwxr-xr-x]setup.py8
-rw-r--r--tests/test_config.py99
-rw-r--r--tests/test_nncli.py278
-rw-r--r--tests/test_version.py35
14 files changed, 824 insertions, 78 deletions
diff --git a/.gitignore b/.gitignore
index 0500cc8..85a6fc5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,14 +1,7 @@
-# byte-compiled python files
-*.pyc
-
-# venv dirs
-env/
-venv/
-
-# python packaging files/dirs
-build
-dist
+build/
+dist/
MANIFEST
-*.egg-info/*
-
-.ropeproject
+*.egg-info/
+nnotes_cli/version.py
+.coverage
+.pytest_cache/
diff --git a/Pipfile b/Pipfile
new file mode 100644
index 0000000..3f88c39
--- /dev/null
+++ b/Pipfile
@@ -0,0 +1,19 @@
+[[source]]
+url = "https://pypi.org/simple"
+verify_ssl = true
+name = "pypi"
+
+[packages]
+appdirs = "*"
+requests = "*"
+urwid = "*"
+setuptools = "*"
+setuptools-scm = "*"
+
+[dev-packages]
+pytest = "*"
+pytest-cov = "*"
+pytest-mock = "*"
+pytest-runner = "*"
+pylint = "*"
+pudb = "*"
diff --git a/Pipfile.lock b/Pipfile.lock
new file mode 100644
index 0000000..8525be7
--- /dev/null
+++ b/Pipfile.lock
@@ -0,0 +1,287 @@
+{
+ "_meta": {
+ "hash": {
+ "sha256": "b4bc66838172733ffa35a5998d87982699b21d67eccdb8d913fffd8c7dcd101a"
+ },
+ "pipfile-spec": 6,
+ "requires": {},
+ "sources": [
+ {
+ "name": "pypi",
+ "url": "https://pypi.org/simple",
+ "verify_ssl": true
+ }
+ ]
+ },
+ "default": {
+ "appdirs": {
+ "hashes": [
+ "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92",
+ "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"
+ ],
+ "index": "pypi",
+ "version": "==1.4.3"
+ },
+ "certifi": {
+ "hashes": [
+ "sha256:376690d6f16d32f9d1fe8932551d80b23e9d393a8578c5633a2ed39a64861638",
+ "sha256:456048c7e371c089d0a77a5212fb37a2c2dce1e24146e3b7e0261736aaeaa22a"
+ ],
+ "version": "==2018.8.24"
+ },
+ "chardet": {
+ "hashes": [
+ "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
+ "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
+ ],
+ "version": "==3.0.4"
+ },
+ "idna": {
+ "hashes": [
+ "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
+ "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"
+ ],
+ "version": "==2.7"
+ },
+ "requests": {
+ "hashes": [
+ "sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1",
+ "sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a"
+ ],
+ "index": "pypi",
+ "version": "==2.19.1"
+ },
+ "setuptools-scm": {
+ "hashes": [
+ "sha256:1191f2a136b5e86f7ca8ab00a97ef7aef997131f1f6d4971be69a1ef387d8b40",
+ "sha256:cc6953d224a22f10e933fa2f55c95979317c55259016adcf93310ba2997febfa"
+ ],
+ "index": "pypi",
+ "version": "==3.1.0"
+ },
+ "urllib3": {
+ "hashes": [
+ "sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf",
+ "sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5"
+ ],
+ "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'",
+ "version": "==1.23"
+ },
+ "urwid": {
+ "hashes": [
+ "sha256:644d3e3900867161a2fc9287a9762753d66bd194754679adb26aede559bcccbc"
+ ],
+ "index": "pypi",
+ "version": "==2.0.1"
+ }
+ },
+ "develop": {
+ "astroid": {
+ "hashes": [
+ "sha256:292fa429e69d60e4161e7612cb7cc8fa3609e2e309f80c224d93a76d5e7b58be",
+ "sha256:c7013d119ec95eb626f7a2011f0b63d0c9a095df9ad06d8507b37084eada1a8d"
+ ],
+ "version": "==2.0.4"
+ },
+ "atomicwrites": {
+ "hashes": [
+ "sha256:6b5282987b21cd79151f51caccead7a09d0a32e89c568bd9e3c4aaa7bbdf3f3a",
+ "sha256:e16334d50fe0f90919ef7339c24b9b62e6abaa78cd2d226f3d94eb067eb89043"
+ ],
+ "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.*'",
+ "version": "==1.2.0"
+ },
+ "attrs": {
+ "hashes": [
+ "sha256:4b90b09eeeb9b88c35bc642cbac057e45a5fd85367b985bd2809c62b7b939265",
+ "sha256:e0d0eb91441a3b53dab4d9b743eafc1ac44476296a2053b6ca3af0b139faf87b"
+ ],
+ "version": "==18.1.0"
+ },
+ "coverage": {
+ "hashes": [
+ "sha256:03481e81d558d30d230bc12999e3edffe392d244349a90f4ef9b88425fac74ba",
+ "sha256:0b136648de27201056c1869a6c0d4e23f464750fd9a9ba9750b8336a244429ed",
+ "sha256:10a46017fef60e16694a30627319f38a2b9b52e90182dddb6e37dcdab0f4bf95",
+ "sha256:198626739a79b09fa0a2f06e083ffd12eb55449b5f8bfdbeed1df4910b2ca640",
+ "sha256:23d341cdd4a0371820eb2b0bd6b88f5003a7438bbedb33688cd33b8eae59affd",
+ "sha256:28b2191e7283f4f3568962e373b47ef7f0392993bb6660d079c62bd50fe9d162",
+ "sha256:2a5b73210bad5279ddb558d9a2bfedc7f4bf6ad7f3c988641d83c40293deaec1",
+ "sha256:2eb564bbf7816a9d68dd3369a510be3327f1c618d2357fa6b1216994c2e3d508",
+ "sha256:337ded681dd2ef9ca04ef5d93cfc87e52e09db2594c296b4a0a3662cb1b41249",
+ "sha256:3a2184c6d797a125dca8367878d3b9a178b6fdd05fdc2d35d758c3006a1cd694",
+ "sha256:3c79a6f7b95751cdebcd9037e4d06f8d5a9b60e4ed0cd231342aa8ad7124882a",
+ "sha256:3d72c20bd105022d29b14a7d628462ebdc61de2f303322c0212a054352f3b287",
+ "sha256:3eb42bf89a6be7deb64116dd1cc4b08171734d721e7a7e57ad64cc4ef29ed2f1",
+ "sha256:4635a184d0bbe537aa185a34193898eee409332a8ccb27eea36f262566585000",
+ "sha256:56e448f051a201c5ebbaa86a5efd0ca90d327204d8b059ab25ad0f35fbfd79f1",
+ "sha256:5a13ea7911ff5e1796b6d5e4fbbf6952381a611209b736d48e675c2756f3f74e",
+ "sha256:69bf008a06b76619d3c3f3b1983f5145c75a305a0fea513aca094cae5c40a8f5",
+ "sha256:6bc583dc18d5979dc0f6cec26a8603129de0304d5ae1f17e57a12834e7235062",
+ "sha256:701cd6093d63e6b8ad7009d8a92425428bc4d6e7ab8d75efbb665c806c1d79ba",
+ "sha256:7608a3dd5d73cb06c531b8925e0ef8d3de31fed2544a7de6c63960a1e73ea4bc",
+ "sha256:76ecd006d1d8f739430ec50cc872889af1f9c1b6b8f48e29941814b09b0fd3cc",
+ "sha256:7aa36d2b844a3e4a4b356708d79fd2c260281a7390d678a10b91ca595ddc9e99",
+ "sha256:7d3f553904b0c5c016d1dad058a7554c7ac4c91a789fca496e7d8347ad040653",
+ "sha256:7e1fe19bd6dce69d9fd159d8e4a80a8f52101380d5d3a4d374b6d3eae0e5de9c",
+ "sha256:8c3cb8c35ec4d9506979b4cf90ee9918bc2e49f84189d9bf5c36c0c1119c6558",
+ "sha256:9d6dd10d49e01571bf6e147d3b505141ffc093a06756c60b053a859cb2128b1f",
+ "sha256:be6cfcd8053d13f5f5eeb284aa8a814220c3da1b0078fa859011c7fffd86dab9",
+ "sha256:c1bb572fab8208c400adaf06a8133ac0712179a334c09224fb11393e920abcdd",
+ "sha256:de4418dadaa1c01d497e539210cb6baa015965526ff5afc078c57ca69160108d",
+ "sha256:e05cb4d9aad6233d67e0541caa7e511fa4047ed7750ec2510d466e806e0255d6",
+ "sha256:f3f501f345f24383c0000395b26b726e46758b71393267aeae0bd36f8b3ade80"
+ ],
+ "markers": "python_version < '4' and python_version != '3.2.*' and python_version != '3.0.*' and python_version != '3.1.*' and python_version >= '2.6'",
+ "version": "==4.5.1"
+ },
+ "isort": {
+ "hashes": [
+ "sha256:1153601da39a25b14ddc54955dbbacbb6b2d19135386699e2ad58517953b34af",
+ "sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8",
+ "sha256:ec9ef8f4a9bc6f71eec99e1806bfa2de401650d996c59330782b89a5555c1497"
+ ],
+ "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.*'",
+ "version": "==4.3.4"
+ },
+ "lazy-object-proxy": {
+ "hashes": [
+ "sha256:0ce34342b419bd8f018e6666bfef729aec3edf62345a53b537a4dcc115746a33",
+ "sha256:1b668120716eb7ee21d8a38815e5eb3bb8211117d9a90b0f8e21722c0758cc39",
+ "sha256:209615b0fe4624d79e50220ce3310ca1a9445fd8e6d3572a896e7f9146bbf019",
+ "sha256:27bf62cb2b1a2068d443ff7097ee33393f8483b570b475db8ebf7e1cba64f088",
+ "sha256:27ea6fd1c02dcc78172a82fc37fcc0992a94e4cecf53cb6d73f11749825bd98b",
+ "sha256:2c1b21b44ac9beb0fc848d3993924147ba45c4ebc24be19825e57aabbe74a99e",
+ "sha256:2df72ab12046a3496a92476020a1a0abf78b2a7db9ff4dc2036b8dd980203ae6",
+ "sha256:320ffd3de9699d3892048baee45ebfbbf9388a7d65d832d7e580243ade426d2b",
+ "sha256:50e3b9a464d5d08cc5227413db0d1c4707b6172e4d4d915c1c70e4de0bbff1f5",
+ "sha256:5276db7ff62bb7b52f77f1f51ed58850e315154249aceb42e7f4c611f0f847ff",
+ "sha256:61a6cf00dcb1a7f0c773ed4acc509cb636af2d6337a08f362413c76b2b47a8dd",
+ "sha256:6ae6c4cb59f199d8827c5a07546b2ab7e85d262acaccaacd49b62f53f7c456f7",
+ "sha256:7661d401d60d8bf15bb5da39e4dd72f5d764c5aff5a86ef52a042506e3e970ff",
+ "sha256:7bd527f36a605c914efca5d3d014170b2cb184723e423d26b1fb2fd9108e264d",
+ "sha256:7cb54db3535c8686ea12e9535eb087d32421184eacc6939ef15ef50f83a5e7e2",
+ "sha256:7f3a2d740291f7f2c111d86a1c4851b70fb000a6c8883a59660d95ad57b9df35",
+ "sha256:81304b7d8e9c824d058087dcb89144842c8e0dea6d281c031f59f0acf66963d4",
+ "sha256:933947e8b4fbe617a51528b09851685138b49d511af0b6c0da2539115d6d4514",
+ "sha256:94223d7f060301b3a8c09c9b3bc3294b56b2188e7d8179c762a1cda72c979252",
+ "sha256:ab3ca49afcb47058393b0122428358d2fbe0408cf99f1b58b295cfeb4ed39109",
+ "sha256:bd6292f565ca46dee4e737ebcc20742e3b5be2b01556dafe169f6c65d088875f",
+ "sha256:cb924aa3e4a3fb644d0c463cad5bc2572649a6a3f68a7f8e4fbe44aaa6d77e4c",
+ "sha256:d0fc7a286feac9077ec52a927fc9fe8fe2fabab95426722be4c953c9a8bede92",
+ "sha256:ddc34786490a6e4ec0a855d401034cbd1242ef186c20d79d2166d6a4bd449577",
+ "sha256:e34b155e36fa9da7e1b7c738ed7767fc9491a62ec6af70fe9da4a057759edc2d",
+ "sha256:e5b9e8f6bda48460b7b143c3821b21b452cb3a835e6bbd5dd33aa0c8d3f5137d",
+ "sha256:e81ebf6c5ee9684be8f2c87563880f93eedd56dd2b6146d8a725b50b7e5adb0f",
+ "sha256:eb91be369f945f10d3a49f5f9be8b3d0b93a4c2be8f8a5b83b0571b8123e0a7a",
+ "sha256:f460d1ceb0e4a5dcb2a652db0904224f367c9b3c1470d5a7683c0480e582468b"
+ ],
+ "version": "==1.3.1"
+ },
+ "mccabe": {
+ "hashes": [
+ "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
+ "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
+ ],
+ "version": "==0.6.1"
+ },
+ "more-itertools": {
+ "hashes": [
+ "sha256:c187a73da93e7a8acc0001572aebc7e3c69daf7bf6881a2cea10650bd4420092",
+ "sha256:c476b5d3a34e12d40130bc2f935028b5f636df8f372dc2c1c01dc19681b2039e",
+ "sha256:fcbfeaea0be121980e15bc97b3817b5202ca73d0eae185b4550cbfce2a3ebb3d"
+ ],
+ "version": "==4.3.0"
+ },
+ "pluggy": {
+ "hashes": [
+ "sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1",
+ "sha256:95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1"
+ ],
+ "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'",
+ "version": "==0.7.1"
+ },
+ "pudb": {
+ "hashes": [
+ "sha256:8d8b974641b7a7a2a721af01c9dce5eac8e05a2ceebc2680725ba8eef1ca876e"
+ ],
+ "index": "pypi",
+ "version": "==2018.1"
+ },
+ "py": {
+ "hashes": [
+ "sha256:06a30435d058473046be836d3fc4f27167fd84c45b99704f2fb5509ef61f9af1",
+ "sha256:50402e9d1c9005d759426988a492e0edaadb7f4e68bcddfea586bc7432d009c6"
+ ],
+ "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'",
+ "version": "==1.6.0"
+ },
+ "pygments": {
+ "hashes": [
+ "sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d",
+ "sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc"
+ ],
+ "version": "==2.2.0"
+ },
+ "pylint": {
+ "hashes": [
+ "sha256:1d6d3622c94b4887115fe5204982eee66fdd8a951cf98635ee5caee6ec98c3ec",
+ "sha256:31142f764d2a7cd41df5196f9933b12b7ee55e73ef12204b648ad7e556c119fb"
+ ],
+ "index": "pypi",
+ "version": "==2.1.1"
+ },
+ "pytest": {
+ "hashes": [
+ "sha256:2e7c330338b2732ddb992217962e3454aa7290434e75329b1a6739cea41bea6b",
+ "sha256:4abcd98faeea3eb95bd05aa6a7b121d5f89d72e4d36ddb0dcbbfd1ec9f3651d1"
+ ],
+ "index": "pypi",
+ "version": "==3.7.3"
+ },
+ "pytest-cov": {
+ "hashes": [
+ "sha256:03aa752cf11db41d281ea1d807d954c4eda35cfa1b21d6971966cc041bbf6e2d",
+ "sha256:890fe5565400902b0c78b5357004aab1c814115894f4f21370e2433256a3eeec"
+ ],
+ "index": "pypi",
+ "version": "==2.5.1"
+ },
+ "pytest-mock": {
+ "hashes": [
+ "sha256:53801e621223d34724926a5c98bd90e8e417ce35264365d39d6c896388dcc928",
+ "sha256:d89a8209d722b8307b5e351496830d5cc5e192336003a485443ae9adeb7dd4c0"
+ ],
+ "index": "pypi",
+ "version": "==1.10.0"
+ },
+ "pytest-runner": {
+ "hashes": [
+ "sha256:d23f117be39919f00dd91bffeb4f15e031ec797501b717a245e377aee0f577be",
+ "sha256:d987fec1e31287592ffe1cb823a8c613c533db4c6aaca0ee1191dbc91e2fcc61"
+ ],
+ "index": "pypi",
+ "version": "==4.2"
+ },
+ "six": {
+ "hashes": [
+ "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
+ "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
+ ],
+ "version": "==1.11.0"
+ },
+ "urwid": {
+ "hashes": [
+ "sha256:644d3e3900867161a2fc9287a9762753d66bd194754679adb26aede559bcccbc"
+ ],
+ "index": "pypi",
+ "version": "==2.0.1"
+ },
+ "wrapt": {
+ "hashes": [
+ "sha256:d4d560d479f2c21e1b5443bbd15fe7ec4b37fe7e53d335d3b9b0a7b1226fe3c6"
+ ],
+ "version": "==1.10.11"
+ }
+ }
+}
diff --git a/README.md b/README.md
index e7fff21..0852303 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,9 @@
nncli
=====
+![GitHub](https://img.shields.io/github/license/djmoch/nncli.svg)
+[![PyPI](https://img.shields.io/pypi/v/nncli.svg)](https://pypi.org/project/nncli/)
+
NextCloud Notes Command Line Interface
nncli is a Python application that gives you access to your NextCloud
@@ -32,8 +35,11 @@ Check your OS distribution for installation packages.
* Manually:
- Clone this repository to your hard disk: `git clone
https://github.com/djmoch/nncli.git`
- - Install the requirements `pip3 install -r requirements.txt`
- Install _nncli_: `python3 setup.py install`
+* Development:
+ - Clone the repo
+ - Install Pipenv: `pip install pipenv`
+ - Stand up development environment: `pipenv install --dev`
### Features
@@ -81,8 +87,8 @@ Usage:
-r, --regex - search string is a regular expression
-k <key>, --key=<key> - note key
-t <title>, --title=<title> - title of note for create (cli mode)
- -c <file>, --config=<file> - config file to read from (defaults to
- ~/.config/nncli/config)
+ -c <file>, --config=<file> - config file to read from
+ --version - version information
COMMANDS:
<none> - console gui mode when no command specified
@@ -109,10 +115,9 @@ your NextCloud Notes account password must be stored someplace
accessible to nncli. Use of the `cfg_nn_password_eval` option is
recommended (see below).
-nncli pulls in configuration from the `config` file located in your
-`$XDG_CONFIG_HOME/nncli` directory. (By default,
-`XDG_CONFIG_HOME=$HOME/.config`.) At the very least, the following example
-`config` will get you going (using your account information):
+nncli pulls in configuration from the `config` file located in the
+standard location for your platform. At the very least, the following
+example `config` will get you going (using your account information):
```
[nncli]
@@ -146,7 +151,8 @@ cfg_nn_password_eval = gpg --quiet --for-your-eyes-only --no-tty --decrypt ~/.nn
# see http://urwid.org/manual/userinput.html for examples of more key
# combinations
-kb_edit_note = space kb_page_down = ctrl f
+kb_edit_note = space
+kb_page_down = ctrl f
# note that values must not be quoted
clr_note_focus_bg = light blue
diff --git a/nnotes_cli/__init__.py b/nnotes_cli/__init__.py
index 054095f..9908156 100644
--- a/nnotes_cli/__init__.py
+++ b/nnotes_cli/__init__.py
@@ -1,5 +1,14 @@
+try:
+ from . import version
+ __version__ = version.version
+except ImportError:
+ try:
+ from setuptools_scm import get_version
+ __version__ = get_version(root='..', relative_to=__file__)
+ except:
+ __version__ = '??-dev'
+
__productname__ = 'nncli'
-__version__ = '0.1.1'
__copyright__ = "Copyright (c) 2018 Daniel Moch"
__author__ = "Daniel Moch"
__author_email__ = "daniel@danielmoch.com"
diff --git a/nnotes_cli/config.py b/nnotes_cli/config.py
index 72b5992..31b5172 100644
--- a/nnotes_cli/config.py
+++ b/nnotes_cli/config.py
@@ -27,34 +27,19 @@
import os, sys, urwid, collections, configparser, subprocess
+from appdirs import user_cache_dir, user_config_dir
+
class Config:
def __init__(self, custom_file=None):
- self.home = os.path.abspath(os.path.expanduser('~'))
- if 'XDG_CONFIG_HOME' in os.environ.keys():
- self.config_home = \
- os.path.join(os.environ['XDG_CONFIG_HOME'], 'nncli')
- else:
- self.config_home = \
- os.path.join(
- os.path.expanduser('~'),
- '.config',
- 'nncli'
- )
- if 'XDG_CACHE_HOME' in os.environ.keys():
- self.cache_home = \
- os.path.join(os.environ['XDG_CACHE_HOME'], 'nncli')
- else:
- self.cache_home = \
- os.path.join(
- os.path.expanduser('~'),
- '.cache',
- 'nncli'
- )
+ self.config_home = user_config_dir('nncli', 'djmoch')
+ self.cache_home = user_cache_dir('nncli', 'djmoch')
+
defaults = \
{
'cfg_nn_username' : '',
'cfg_nn_password' : '',
+ 'cfg_nn_password_eval' : '',
'cfg_db_path' : self.cache_home,
'cfg_search_categories' : 'yes', # with regex searches
'cfg_sort_mode' : 'date', # 'alpha' or 'date'
diff --git a/nnotes_cli/nextcloud_note.py b/nnotes_cli/nextcloud_note.py
index 64ce1b2..f26fb33 100644
--- a/nnotes_cli/nextcloud_note.py
+++ b/nnotes_cli/nextcloud_note.py
@@ -65,14 +65,11 @@ class NextcloudNote(object):
def __init__(self, username, password, host):
""" object constructor """
- self.username = urllib.parse.quote(username)
- self.password = urllib.parse.quote(password)
- self.api_url = \
- 'https://{}:{}@{}/index.php/apps/notes/api/v0.2/notes'. \
- format(username, password, host)
- self.sanitized_url = \
- 'https://{}:****@{}/index.php/apps/notes/api/v0.2/notes'. \
- format(username, host)
+ self.username = username
+ self.password = password
+ self.url = \
+ 'https://{}/index.php/apps/notes/api/v0.2/notes'. \
+ format(host)
self.status = 'offline'
def get_note(self, noteid):
@@ -89,10 +86,10 @@ class NextcloudNote(object):
"""
# request note
- url = '{}/{}'.format(self.api_url, str(noteid))
- #logging.debug('REQUEST: ' + self.sanitized_url+params)
+ url = '{}/{}'.format(self.url, str(noteid))
+ #logging.debug('REQUEST: ' + self.url+params)
try:
- res = requests.get(url)
+ res = requests.get(url, auth=(self.username, self.password))
res.raise_for_status()
note = res.json()
self.status = 'online'
@@ -136,18 +133,20 @@ class NextcloudNote(object):
if 'modified' not in note:
note["modified"] = int(time.time())
- url = '{}/{}'.format(self.api_url, note["id"])
+ url = '{}/{}'.format(self.url, note["id"])
del note["id"]
else:
- url = self.api_url
+ url = self.url
#logging.debug('REQUEST: ' + url + ' - ' + str(note))
try:
logging.debug('NOTE: ' + str(note))
- if url != self.api_url:
- res = requests.put(url, data=note)
+ if url != self.url:
+ res = requests.put(url, auth=(self.username,
+ self.password), json=note)
else:
- res = requests.post(url, data=note)
+ res = requests.post(url, auth=(self.username,
+ self.password), json=note)
note = res.json()
res.raise_for_status()
logging.debug('NOTE (from response): ' + str(res.json()))
@@ -219,22 +218,25 @@ class NextcloudNote(object):
# perform initial HTTP request
try:
- logging.debug('REQUEST: ' + self.sanitized_url + \
+ logging.debug('REQUEST: ' + self.url + \
'?exclude=content')
- res = requests.get(self.api_url, params=params)
+ res = requests.get(self.url, auth=(self.username, self.password), params=params)
res.raise_for_status()
#logging.debug('RESPONSE OK: ' + str(res))
note_list = res.json()
self.status = 'online'
except ConnectionError as e:
+ logging.exception('connection error')
self.status = 'offline, connection error'
status = -1
except RequestException as e:
# if problem with network request/response
+ logging.exception('request error')
status = -1
except ValueError as e:
# if invalid json data
status = -1
+ logging.exception('request returned bad JSON data')
# Can only filter for category at end, once all notes have been
# retrieved. Below based on simplenote.vim, except we return
@@ -258,12 +260,11 @@ class NextcloudNote(object):
- status (int): 0 on sucesss and -1 otherwise
"""
- url = '{}/{}'.format(self.api_url, str(note['id']))
- logurl = '{}/{}'.format(self.sanitized_url, str(note['id']))
+ url = '{}/{}'.format(self.url, str(note['id']))
try:
- logging.debug('REQUEST DELETE: ' + logurl)
- res = requests.delete(url)
+ logging.debug('REQUEST DELETE: ' + url)
+ res = requests.delete(url, auth=(self.username, self.password))
res.raise_for_status()
self.status = 'online'
except ConnectionError as e:
diff --git a/nnotes_cli/nncli.py b/nnotes_cli/nncli.py
index 060062f..7ef76a6 100644
--- a/nnotes_cli/nncli.py
+++ b/nnotes_cli/nncli.py
@@ -28,6 +28,7 @@
import os, sys, getopt, re, signal, time, datetime, shlex, hashlib
import subprocess, threading, logging
import copy, json, urwid, datetime
+import nnotes_cli
from . import view_titles, view_note, view_help, view_log, user_input
from . import utils, temp
from .config import Config
@@ -1145,28 +1146,46 @@ Usage:
-r, --regex - search string is a regular expression
-k <key>, --key=<key> - note key
-t <title>, --title=<title> - title of note for create (cli mode)
- -c <file>, --config=<file> - config file to read from (defaults to
- ~/.config/nncli/config)
+ -c <file>, --config=<file> - config file to read from
+ -V, --version - version information
COMMANDS:
<none> - console gui mode when no command specified
sync - perform a full sync with the server
list [search_string] - list notes (refined with search string)
- export [search_string] - export notes in JSON (refined with search string)
+ export [search_string] - export notes in JSON (refined with search
+ string)
dump [search_string] - dump notes (refined with search string)
create [-] - create a note ('-' content from stdin)
- import [-] - import a note in JSON format ('-' JSON from stdin)
- export - export a note in JSON format (specified by <key>)
+ import [-] - import a note in JSON format ('-' JSON from
+ stdin)
+ export - export a note in JSON format (specified by
+ <key>)
dump - dump a note (specified by <key>)
edit - edit a note (specified by <key>)
delete - delete a note (specified by <key>)
< favorite | unfavorite > - favorite/unfavorite a note (specified by <key>)
- cat get - retrieve the category from a note (specified by <key>)
+ cat get - retrieve the category from a note (specified
+ by <key>)
cat set <category> - set the category for a note (specified by <key>)
cat rm - remove category from a note (specified by <key>)
''')
sys.exit(0)
+def version():
+ version_info = ''
+ version_info += nnotes_cli.__productname__ + ' v' + \
+ nnotes_cli.__version__ + "\n"
+ version_info += nnotes_cli.__description__ + "\n\n"
+ version_info += nnotes_cli.__copyright__ + "\n"
+ version_info += "Written by " + nnotes_cli.__author__ + \
+ " and others\n"
+ version_info += "Licensed under the terms of the " + \
+ nnotes_cli.__license__ + " license\n"
+ version_info += "The latest code is available at: " + \
+ nnotes_cli.__url__
+ print(version_info)
+ exit(0)
def main(argv=sys.argv[1:]):
verbose = False
@@ -1178,14 +1197,17 @@ def main(argv=sys.argv[1:]):
try:
opts, args = getopt.getopt(argv,
- 'hvnrk:t:c:',
- [ 'help', 'verbose', 'nosync', 'regex', 'key=', 'title=', 'config=' ])
+ 'hvnrk:t:c:V',
+ [ 'help', 'verbose', 'nosync', 'regex', 'key=', 'title=', \
+ 'config=', 'version' ])
except:
usage()
for opt, arg in opts:
if opt in [ '-h', '--help']:
usage()
+ elif opt in ['-V', '--version' ]:
+ version()
elif opt in [ '-v', '--verbose']:
verbose = True
elif opt in [ '-n', '--nosync']:
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index 839715a..0000000
--- a/requirements.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-urwid
-requests
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..b0bd258
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,12 @@
+[aliases]
+test=pytest
+
+[tool:pytest]
+testpaths = tests
+addopts = --cov=nnotes_cli --cov-report=term-missing
+
+[yapf]
+coalesce_brackets = true
+dedent_closing_brackets = true
+space_between_ending_comma_and_closing_bracket = false
+split_arguments_when_comma_terminated = true
diff --git a/setup.py b/setup.py
index 10426ef..9391552 100755..100644
--- a/setup.py
+++ b/setup.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python3
#
# The MIT License (MIT)
#
@@ -29,7 +28,8 @@
from setuptools import setup
import nnotes_cli
-deps = ['urwid', 'requests']
+deps = ['urwid', 'requests', 'appdirs']
+test_deps = ['pytest', 'pytest-cov', 'pytest-runner', 'pytest-mock']
with open("README.md", "r") as fh:
long_description = fh.read()
@@ -39,13 +39,15 @@ setup(
description=nnotes_cli.__description__,
long_description=long_description,
long_description_content_type="text/markdown",
- version=nnotes_cli.__version__,
author=nnotes_cli.__author__,
author_email=nnotes_cli.__author_email__,
url=nnotes_cli.__url__,
license=nnotes_cli.__license__,
requires=deps,
install_requires=deps,
+ tests_require=test_deps,
+ use_scm_version= {'write_to': 'nnotes_cli/version.py'},
+ setup_requires=['setuptools_scm'],
packages=['nnotes_cli'],
entry_points={
'console_scripts': [
diff --git a/tests/test_config.py b/tests/test_config.py
new file mode 100644
index 0000000..75fe1e9
--- /dev/null
+++ b/tests/test_config.py
@@ -0,0 +1,99 @@
+#
+# The MIT License (MIT)
+#
+# Copyright (c) 2018 Daniel Moch
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+import os
+import sys
+
+from nnotes_cli.config import Config
+from pytest import raises
+
+def test_init():
+ config = Config()
+
+ if sys.platform == 'linux':
+ assert config.config_home == os.path.join(os.path.expanduser('~'), \
+ '.config', 'nncli')
+ assert config.cache_home == os.path.join(os.path.expanduser('~'), \
+ '.cache', 'nncli')
+ if sys.platform == 'darwin':
+ assert config.config_home == os.path.join(os.path.expanduser('~'), \
+ 'Library', 'Preferences', 'nncli')
+ assert config.cache_home == os.path.join(os.path.expanduser('~'), \
+ 'Library', 'Caches', 'nncli')
+
+
+def test_custom_file():
+ with open('test_cfg', 'w') as config_file:
+ config_file.write('[nncli]\n')
+ config_file.write('cfg_nn_username=user\n')
+ config_file.write('cfg_nn_password=password\n')
+ config_file.write('cfg_nn_host=nextcloud.example.org\n')
+
+ config = Config('test_cfg')
+ os.remove('test_cfg')
+
+def test_bad_password_eval():
+ with open('test_cfg', 'w') as config_file:
+ config_file.write('[nncli]\n')
+ config_file.write('cfg_nn_username=user\n')
+ config_file.write('cfg_nn_password_eval=password\n')
+ config_file.write('cfg_nn_host=nextcloud.example.org\n')
+
+ with raises(SystemExit):
+ config = Config('test_cfg')
+ os.remove('test_cfg')
+
+def test_empty_config():
+ with open('test_cfg', 'w') as config_file:
+ config_file.write('\n')
+
+ config = Config('test_cfg')
+ os.remove('test_cfg')
+
+def test_get_config():
+ config = Config('test_cfg')
+ assert config.get_config('sort_mode') == 'date'
+
+def test_get_config_descr():
+ config = Config('test_cfg')
+ assert config.get_config_descr('sort_mode') == 'Sort mode'
+
+def test_get_keybind():
+ config = Config('test_cfg')
+ assert config.get_keybind('help') == 'h'
+
+def test_get_keybind_use():
+ config = Config('test_cfg')
+ assert config.get_keybind_use('help') == [ 'common' ]
+
+def test_get_keybind_descr():
+ config = Config('test_cfg')
+ assert config.get_keybind_descr('help') == 'Help'
+
+def test_get_color():
+ config = Config('test_cfg')
+ assert config.get_color('default_fg') == 'default'
+
+def test_get_color_descr():
+ config = Config('test_cfg')
+ assert config.get_color_descr('default_fg') == 'Default fg'
diff --git a/tests/test_nncli.py b/tests/test_nncli.py
new file mode 100644
index 0000000..394135a
--- /dev/null
+++ b/tests/test_nncli.py
@@ -0,0 +1,278 @@
+#
+# The MIT License (MIT)
+#
+# Copyright (c) 2018 Daniel Moch
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+import logging
+import os
+import pytest
+import shutil
+
+from logging.handlers import RotatingFileHandler
+from nnotes_cli.nncli import nncli
+
+def mock_nncli(mocker):
+ mocker.patch('logging.getLogger')
+ mocker.patch('nnotes_cli.config.Config')
+ mocker.patch('nnotes_cli.notes_db.NotesDB')
+ mocker.patch('os.mkdir')
+ mocker.patch.object(RotatingFileHandler, '_open')
+
+def assert_initialized():
+ logging.getLogger.assert_called_once()
+ RotatingFileHandler._open.assert_called_once()
+ assert os.mkdir.call_count == 2
+
+@pytest.mark.parametrize('mock_nncli', [mock_nncli])
+def test_init_no_tempdir(mocker, mock_nncli):
+ mock_nncli(mocker)
+
+ with open('test_cfg', 'w') as config_file:
+ config_file.write('[nncli]\n')
+ config_file.write('cfg_db_path=duh')
+
+ nn = nncli(False, config_file='test_cfg')
+ assert_initialized()
+ assert nn.tempdir == None
+ os.mkdir.assert_called_with('duh')
+
+ os.remove('test_cfg')
+
+@pytest.mark.parametrize('mock_nncli', [mock_nncli])
+def test_init(mocker, mock_nncli):
+ mock_nncli(mocker)
+
+ with open('test_cfg', 'w') as config_file:
+ config_file.write('[nncli]\n')
+ config_file.write('cfg_tempdir=blah\n')
+ config_file.write('cfg_db_path=duh')
+
+ nn = nncli(False, config_file='test_cfg')
+ assert_initialized()
+ assert nn.tempdir == 'blah'
+
+ os.remove('test_cfg')
+
+@pytest.mark.parametrize('mock_nncli', [mock_nncli])
+def test_init_notesdb_fail(mocker, mock_nncli):
+ os.mkdir('duh')
+ mock_nncli(mocker)
+
+ with open('duh/1.json', 'w') as bad_file:
+ bad_file.write('bad_json_data')
+
+ with open('test_cfg', 'w') as config_file:
+ config_file.write('[nncli]\n')
+ config_file.write('cfg_db_path=duh')
+
+ with pytest.raises(SystemExit):
+ nn = nncli(False, config_file='test_cfg')
+
+ shutil.rmtree('duh')
+
+@pytest.mark.parametrize('mock_nncli', [mock_nncli])
+def test_get_editor(mocker, mock_nncli):
+ mock_nncli(mocker)
+
+ with open('test_cfg', 'w') as config_file:
+ config_file.write('[nncli]\n')
+ config_file.write('cfg_db_path=duh')
+ config_file.write('cfg_editor=vim')
+
+ nn = nncli(False, config_file='test_cfg')
+ assert_initialized()
+ assert nn.get_editor() == 'vim'
+
+ os.remove('test_cfg')
+
+@pytest.mark.parametrize('mock_nncli', [mock_nncli])
+def test_no_editor(mocker, mock_nncli):
+ mock_nncli(mocker)
+
+ with open('test_cfg', 'w') as config_file:
+ config_file.write('[nncli]\n')
+ config_file.write('cfg_db_path=duh')
+
+ nn = nncli(False, config_file='test_cfg')
+ nn.config.configs['editor'] = ['']
+ assert_initialized()
+ assert nn.get_editor() == None
+
+ os.remove('test_cfg')
+
+def test_get_pager():
+ pass
+
+def test_get_diff():
+ pass
+
+def test_exec_cmd_on_note():
+ pass
+
+def test_exec_diff_on_note():
+ pass
+
+def test_gui_header_clear():
+ pass
+
+def test_gui_header_set():
+ pass
+
+def test_gui_header_get():
+ pass
+
+def test_gui_header_focus():
+ pass
+
+def test_gui_footer_log_clear():
+ pass
+
+def test_gui_footer_log_set():
+ pass
+
+def test_gui_footer_log_get():
+ pass
+
+def test_gui_footer_input_clear():
+ pass
+
+def test_gui_footer_input_set():
+ pass
+
+def test_gui_footer_input_get():
+ pass
+
+def test_gui_footer_focus_input():
+ pass
+
+def test_gui_body_clear():
+ pass
+
+def test_gui_body_set():
+ pass
+
+def test_gui_body_get():
+ pass
+
+def test_gui_body_focus():
+ pass
+
+def test_log_timeout():
+ pass
+
+def test_log():
+ pass
+
+def test_gui_update_view():
+ pass
+
+def test_gui_update_status_bar():
+ pass
+
+def test_gui_switch_frame_body():
+ pass
+
+def test_delete_note_callback():
+ pass
+
+def test_gui_yes_no_input():
+ pass
+
+def test_gui_search_input():
+ pass
+
+def test_gui_category_input():
+ pass
+
+def test_gui_pipe_input():
+ pass
+
+def test_gui_frame_keypress():
+ pass
+
+def test_gui_init_view():
+ pass
+
+def test_gui_clear():
+ pass
+
+def test_gui_reset():
+ pass
+
+def test_gui_stop():
+ pass
+
+def test_gui():
+ pass
+
+def test_cli_list_notes():
+ pass
+
+def test_cli_note_dump():
+ pass
+
+def test_cli_dump_notes():
+ pass
+
+def test_cli_note_create():
+ pass
+
+def test_cli_note_import():
+ pass
+
+def test_cli_note_export():
+ pass
+
+def test_cli_export_notes():
+ pass
+
+def test_cli_note_edit():
+ pass
+
+def test_cli_note_delete():
+ pass
+
+def test_cli_note_favorite():
+ pass
+
+def test_cli_note_category_get():
+ pass
+
+def test_cli_note_category_set():
+ pass
+
+def test_cli_note_category_rm():
+ pass
+
+def test_SIGINT_handler():
+ pass
+
+def test_usage():
+ pass
+
+def test_version():
+ pass
+
+def test_main():
+ pass
+
+def test_nncli_start():
+ pass
diff --git a/tests/test_version.py b/tests/test_version.py
new file mode 100644
index 0000000..af1224a
--- /dev/null
+++ b/tests/test_version.py
@@ -0,0 +1,35 @@
+#
+# The MIT License (MIT)
+#
+# Copyright (c) 2018 Daniel Moch
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+import nnotes_cli
+import pytest
+
+from nnotes_cli import version
+from setuptools_scm import get_version
+
+@pytest.mark.skip(reason="test_version will fail outside of a Git repo")
+def test_version():
+ vers = get_version(root="..", relative_to=__file__)
+
+ assert nnotes_cli.__version__ == vers
+ assert version.version == vers