diff --git a/.travis.yml b/.travis.yml index 7c33f2a..8ffcbb8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ addons: - devscripts - zsh script: - - ./tests/ci-test-wrapper --watch .travis.yml + - ./ci-test-wrapper --watch .travis.yml - git fetch --unshallow --tags origin - ./tools/makedeb deploy: diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..687be09 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,3 @@ +include CHANGELOG LICENSE README.md buku.1 requirements.txt +recursive-include tests *.py +recursive-include auto-completion * diff --git a/Makefile b/Makefile index ecfce8f..dcc0651 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ install: install -m755 -d $(MANDIR) install -m755 -d $(DOCDIR) gzip -c buku.1 > buku.1.gz - install -m755 buku $(BINDIR) + install -m755 buku.py $(BINDIR)/buku install -m644 buku.1.gz $(MANDIR) install -m644 README.md $(DOCDIR) rm -f buku.1.gz diff --git a/README.md b/README.md index 5b06bc8..7a53ac8 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,8 @@ To remove, run: `buku` is a standalone utility. From the containing directory, run: - $ ./buku + $ chmod +x buku.py + $ ./buku.py #### Debian package diff --git a/buku b/buku.py old mode 100755 new mode 100644 similarity index 99% rename from buku rename to buku.py index bf5888d..8a10389 --- a/buku +++ b/buku.py @@ -38,6 +38,10 @@ try: except ImportError: pass +__version__ = '2.6' +__author__ = 'Arun Prakash Jana ' +__license__ = 'GPLv3' + # Globals update = False # Update a bookmark in DB tags_in = None # Input tags specified at cmdline @@ -47,7 +51,6 @@ tagsearch = False # Search bookmarks by tag title_data = None # Title fetched from a webpage interrupted = False # Received SIGINT DELIM = ',' # Delimiter used to store tags in DB -_VERSION_ = '2.6' # Program version # Crypto globals BLOCKSIZE = 65536 @@ -1734,7 +1737,7 @@ def check_upstream_release(): % response.status) else: latest = json.loads(response.read().decode('utf-8'))[0]['name'] - if latest == 'v' + _VERSION_: + if latest == 'v' + __version__: print('This is the latest release') else: print('Latest upstream release is %s' % latest) @@ -1842,7 +1845,7 @@ Version %s Copyright (C) 2015-2016 Arun Prakash Jana License: GPLv3 Webpage: https://github.com/jarun/Buku -''' % _VERSION_) +''' % __version__) # Help def print_help(self, file=None): @@ -1854,18 +1857,21 @@ Webpage: https://github.com/jarun/Buku # Handle piped input -def main(argv, pipeargs=None): +def piped_input(argv, pipeargs=None): if not sys.stdin.isatty(): pipeargs.extend(argv) for s in sys.stdin.readlines(): pipeargs.extend(s.split()) -if __name__ == '__main__': + +def main(): + global tags_in, title_in, description + pipeargs = [] atexit.register(logging.shutdown) try: - main(sys.argv, pipeargs) + piped_input(sys.argv, pipeargs) except KeyboardInterrupt: pass @@ -2046,7 +2052,7 @@ if __name__ == '__main__': description = ' '.join(args.desc) if args.debug: logger.setLevel(logging.DEBUG) - logger.debug('Version %s', _VERSION_) + logger.debug('Version %s', __version__) # Move pre-1.9 database to new location # BukuDb.move_legacy_dbfile() @@ -2269,3 +2275,6 @@ if __name__ == '__main__': # Close DB connection and quit bdb.close_quit(0) + +if __name__ == '__main__': + main() diff --git a/tests/ci-test-wrapper b/ci-test-wrapper similarity index 97% rename from tests/ci-test-wrapper rename to ci-test-wrapper index e466bf7..942d882 100755 --- a/tests/ci-test-wrapper +++ b/ci-test-wrapper @@ -4,8 +4,8 @@ set -e declare here repo_root here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -repo_root="$here/.." -export GIT_DIR="$here/../.git" +repo_root="$here" +export GIT_DIR="$here/.git" declare -a watchlist watchlist=(buku tests) @@ -89,5 +89,5 @@ fi # Test buku(1) with $repo_root at the beginning of $PATH (so that buku # from this repo is picked up). -cd $here +cd "$here/tests" PATH="$repo_root:$PATH" python -m pytest test_*.py --cov buku diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..0877973 --- /dev/null +++ b/setup.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 + +import re +import sys + +from setuptools import setup + +if sys.version_info < (3, 3): + print('ERROR: Buku requires at least Python 3.3 to run.') + sys.exit(1) + +with open('buku.py') as f: + version = re.search('__version__ = \'([^\']+)\'', f.read()).group(1) + +with open('README.md', encoding='utf-8') as f: + long_description = f.read() + +setup( + name='buku', + version=version, + description='Powerful command-line bookmark manager. Your mini web!', + long_description=long_description, + author='Arun Prakash Jana', + author_email='engineerarun@gmail.com', + url='https://github.com/jarun/Buku', + license='GPLv3', + platforms=['any'], + py_modules=['buku'], + entry_points={ + 'console_scripts': ['buku=buku:main'] + }, + extras_require={ + 'CRYPTO': ['cryptography'], + 'HTML': ['beautifulsoup4'] + }, + test_suite='tests', + tests_require=['pytest-cov', 'pytest-catchlog'], + keywords='cli bookmarks tag utility', + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Environment :: Console', + 'Intended Audience :: End Users/Desktop', + 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', + 'Natural Language :: English', + 'Operating System :: OS Independent', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3 :: Only', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Topic :: Internet :: WWW/HTTP :: Indexing/Search', + 'Topic :: Utilities' + ] +) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_bukuDb.py b/tests/test_bukuDb.py index 013a6e7..5b97581 100644 --- a/tests/test_bukuDb.py +++ b/tests/test_bukuDb.py @@ -1,44 +1,40 @@ #!/usr/bin/env python3 +# # Unit test cases for buku -# -*- coding: utf-8 -*- -from genericpath import exists -import imp +# import os import re -from tempfile import TemporaryDirectory -import unittest, pytest -from unittest import mock -from os.path import join, expanduser import sqlite3 +from genericpath import exists +from tempfile import TemporaryDirectory -buku = imp.load_source('buku', '../buku') +import pytest +import unittest +from unittest import mock as mock + +from buku import BukuDb, parse_tags, prompt TEST_TEMP_DIR_OBJ = TemporaryDirectory(prefix='bukutest_') TEST_TEMP_DIR_PATH = TEST_TEMP_DIR_OBJ.name -TEST_TEMP_DBDIR_PATH = join(TEST_TEMP_DIR_PATH, 'buku') -TEST_TEMP_DBFILE_PATH = join(TEST_TEMP_DBDIR_PATH, 'bookmarks.db') +TEST_TEMP_DBDIR_PATH = os.path.join(TEST_TEMP_DIR_PATH, 'buku') +TEST_TEMP_DBFILE_PATH = os.path.join(TEST_TEMP_DBDIR_PATH, 'bookmarks.db') -from buku import BukuDb, parse_tags - -TEST_BOOKMARKS = [ ['http://slashdot.org', - 'SLASHDOT', - parse_tags(['old,news']), - "News for old nerds, stuff that doesn't matter", - ], - - ['http://www.zażółćgęśląjaźń.pl/', - 'ZAŻÓŁĆ', - parse_tags(['zażółć,gęślą,jaźń']), - "Testing UTF-8, zażółć gęślą jaźń.", - ], - - ['https://test.com:8080', - 'test', - parse_tags(['test,tes,est,es']), - "a case for replace_tag test", - ], +TEST_BOOKMARKS = [ + ['http://slashdot.org', + 'SLASHDOT', + parse_tags(['old,news']), + "News for old nerds, stuff that doesn't matter"], + ['http://www.zażółćgęśląjaźń.pl/', + 'ZAŻÓŁĆ', + parse_tags(['zażółć,gęślą,jaźń']), + "Testing UTF-8, zażółć gęślą jaźń."], + ['https://test.com:8080', + 'test', + parse_tags(['test,tes,est,es']), + "a case for replace_tag test"], ] + @pytest.fixture() def setup(): os.environ['XDG_DATA_HOME'] = TEST_TEMP_DIR_PATH @@ -47,6 +43,7 @@ def setup(): if exists(TEST_TEMP_DBFILE_PATH): os.remove(TEST_TEMP_DBFILE_PATH) + class TestBukuDb(unittest.TestCase): def setUp(self): @@ -65,7 +62,7 @@ class TestBukuDb(unittest.TestCase): # @unittest.skip('skipping') def test_get_dbdir_path(self): dbdir_expected = TEST_TEMP_DBDIR_PATH - dbdir_local_expected = join(expanduser('~'), '.local', 'share', 'buku') + dbdir_local_expected = os.path.join(os.path.expanduser('~'), '.local', 'share', 'buku') dbdir_relative_expected = os.path.abspath('.') # desktop linux @@ -113,12 +110,12 @@ class TestBukuDb(unittest.TestCase): # the expected bookmark expected = (1, 'http://slashdot.org', 'SLASHDOT', ',news,old,', - "News for old nerds, stuff that doesn't matter", 0) + "News for old nerds, stuff that doesn't matter", 0) bookmark_from_db = self.bdb.get_bm_by_id(1) # asserting bookmark matches expected self.assertEqual(expected, bookmark_from_db) # asserting None returned if index out of range - self.assertIsNone(self.bdb.get_bm_by_id( len(self.bookmarks[0]) + 1 )) + self.assertIsNone(self.bdb.get_bm_by_id(len(self.bookmarks[0]) + 1)) # @unittest.skip('skipping') def test_get_bm_id(self): @@ -190,19 +187,18 @@ class TestBukuDb(unittest.TestCase): # tags to add new_tags = ",foo,bar,baz" # record of original tags for each bookmark - old_tagsets = { i: self.bdb.get_bm_by_id(i)[3] for i in inclusive_range(1, len(self.bookmarks)) } + old_tagsets = {i: self.bdb.get_bm_by_id(i)[3] for i in inclusive_range(1, len(self.bookmarks))} with mock.patch('builtins.input', return_value='y'): self.bdb.append_tag_at_index(0, new_tags) # updated tags for each bookmark - from_db = [ (i, self.bdb.get_bm_by_id(i)[3]) for i in inclusive_range(1, len(self.bookmarks)) ] + from_db = [(i, self.bdb.get_bm_by_id(i)[3]) for i in inclusive_range(1, len(self.bookmarks))] for index, tagset in from_db: # checking if new tags added to bookmark self.assertTrue(split_and_test_membership(new_tags, tagset)) # checking if old tags still exist for boomark self.assertTrue(split_and_test_membership(old_tagsets[index], tagset)) - # @unittest.skip('skipping') def test_delete_tag_at_index(self): # adding bookmarks @@ -211,7 +207,7 @@ class TestBukuDb(unittest.TestCase): get_tags_at_idx = lambda i: self.bdb.get_bm_by_id(i)[3] # list of two-tuples, each containg bookmark index and corresponding tags - tags_by_index = [ (i, get_tags_at_idx(i)) for i in inclusive_range(1, len(self.bookmarks)) ] + tags_by_index = [(i, get_tags_at_idx(i)) for i in inclusive_range(1, len(self.bookmarks))] for i, tags in tags_by_index: # get the first tag from the bookmark @@ -242,10 +238,10 @@ class TestBukuDb(unittest.TestCase): title_search = bookmark[1] # Expect a five-tuple containing all bookmark data # db index, URL, title, tags, description - expected = [(i + 1,) + tuple(bookmark)] + expected = [(i + 1,) + tuple(bookmark)] # search db by tag, url (domain name), and title for keyword in (tag_search, url_search, title_search): - with mock.patch('buku.prompt') as mock_prompt: + with mock.patch('buku.prompt'): # search by keyword results = self.bdb.searchdb([keyword]) self.assertEqual(results, expected) @@ -256,7 +252,7 @@ class TestBukuDb(unittest.TestCase): for bookmark in self.bookmarks: self.bdb.add_bm(*bookmark) - with mock.patch('buku.prompt') as mock_prompt: + with mock.patch('buku.prompt'): get_first_tag = lambda x: ''.join(x[2].split(',')[:2]) for i in range(len(self.bookmarks)): # search for bookmark with a tag that is known to exist @@ -280,16 +276,16 @@ class TestBukuDb(unittest.TestCase): # search the db with keywords from each bookmark # searching using the first tag from bookmarks get_first_tag = lambda x: x[2].split(',')[1] - results = self.bdb.searchdb([ get_first_tag(bm) for bm in self.bookmarks ]) - buku.prompt(results) + results = self.bdb.searchdb([get_first_tag(bm) for bm in self.bookmarks]) + prompt(results) except StopIteration: # catch exception thrown by reaching the end of the side effect iterable pass # collect arguments passed to open_in_browser - arg_list = [ args[0] for args, _ in mock_open_in_browser.call_args_list ] + arg_list = [args[0] for args, _ in mock_open_in_browser.call_args_list] # expect a list of one-tuples that are bookmark URLs - expected = [ x[0] for x in self.bookmarks] + expected = [x[0] for x in self.bookmarks] # checking if open_in_browser called with expected arguments self.assertEqual(arg_list, expected) @@ -306,16 +302,16 @@ class TestBukuDb(unittest.TestCase): # search the db with keywords from each bookmark # searching using the first tag from bookmarks get_first_tag = lambda x: x[2].split(',')[1] - results = self.bdb.searchdb([ get_first_tag(bm) for bm in self.bookmarks[:2] ]) - buku.prompt(results) + results = self.bdb.searchdb([get_first_tag(bm) for bm in self.bookmarks[:2]]) + prompt(results) except StopIteration: # catch exception thrown by reaching the end of the side effect iterable pass # collect arguments passed to open_in_browser - arg_list = [ args[0] for args, _ in mock_open_in_browser.call_args_list ] + arg_list = [args[0] for args, _ in mock_open_in_browser.call_args_list] # expect a list of one-tuples that are bookmark URLs - expected = [ x[0] for x in self.bookmarks][:2] + expected = [x[0] for x in self.bookmarks][:2] # checking if open_in_browser called with expected arguments self.assertEqual(arg_list, expected) @@ -377,7 +373,7 @@ class TestBukuDb(unittest.TestCase): # removing nonexistent tag which is also a substring of other tag self.bdb.replace_tag("e") - for url, title, _, _ in self.bookmarks: + for url, title, _, _ in self.bookmarks: # retrieving from db index = self.bdb.get_bm_id(url) from_db = self.bdb.get_bm_by_id(index) @@ -408,6 +404,7 @@ class TestBukuDb(unittest.TestCase): # def test_import_bookmark(self): # self.fail() + def test_print_bm(capsys, caplog, setup): bdb = BukuDb() out, err = capsys.readouterr() @@ -445,6 +442,7 @@ def test_print_bm(capsys, caplog, setup): assert out == "\x1b[1m3 records found\x1b[21m\n\n\x1b[1m\x1b[93m2. \x1b[0m\x1b[92mhttp://blank-title.com\x1b[0m\n \x1b[91m+\x1b[0m blank title\n \x1b[91m#\x1b[0m blank,title\n\n\x1b[1m\x1b[93m3. \x1b[0m\x1b[92mhttp://empty-tags.com\x1b[0m\n \x1b[91m>\x1b[0m empty tags\n \x1b[91m+\x1b[0m empty tags\n\n\x1b[1m\x1b[93m4. \x1b[0m\x1b[92mhttp://all-empty.com\x1b[0m\n \x1b[91m+\x1b[0m all empty\n\n" assert err == '' + def test_list_tags(capsys, setup): bdb = BukuDb() @@ -460,6 +458,7 @@ def test_list_tags(capsys, setup): assert out == " 1. 1\n 2. 2\n 3. 3\n 4. Ant\n 5. ant\n 6. bee\n 7. Bee\n 8. Cat\n 9. cat\n" assert err == '' + def test_compactdb(setup): bdb = BukuDb() @@ -478,10 +477,12 @@ def test_compactdb(setup): # Helper functions for testcases + def split_and_test_membership(a, b): # :param a, b: comma separated strings to split # test everything in a in b - return all( x in b.split(',') for x in a.split(',') ) + return all(x in b.split(',') for x in a.split(',')) + def inclusive_range(start, end): return range(start, end + 1) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 96c771a..b4e5f2b 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- -import imp, unittest -buku = imp.load_source('buku', '../buku') +import os +import signal +import unittest -from buku import * +from buku import is_int, parse_tags class TestHelpers(unittest.TestCase):