diff --git a/README.md b/README.md index 50b00eb..578e6af 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,126 @@ # SyncBoostNote -Python script to sync your Notes from BoostNote into a github repo. +[![forthebadge](https://forthebadge.com/images/badges/built-with-love.svg)](https://forthebadge.com) [![forthebadge](https://forthebadge.com/images/badges/made-with-python.svg)](https://forthebadge.com) +[![forthebadge](https://img.shields.io/badge/Made%20For-Boostnote-brightgreen.svg?style=for-the-badge)](https://github.com/BoostIO/Boostnote) [![forthebadge](https://img.shields.io/badge/STATUS-WIP-blueviolet.svg?style=for-the-badge)](https://github.com/BoostIO/Boostnote) + + +### A simple cli to save your notes from Boostnotes directly to a Github repo. + +## Features: + + +## Requirements: +### Before using this cli, make sure you have the following: +- git + - If not installed visit, [Git](https://git-scm.com/downloads) +- python + - IF note installed visit, [Python](https://www.python.org/downloads/) +- boostnote (also know where it is installed) + - If not installed visit, [BoostNote](https://boostnote.io/#download) +## Installation: +- To install from PyPi: +```bash +$ pip install syncboostnote +``` +- To install from source: +```bash +$ git clone https://github.com/DumbMachine/SyncBoostNote +$ cd syncboostnote +$ python setup.py install +``` +## Usage: +### Default Usage: +This method assumes that the location of ``Boostnote`` local storage is the following: +```bash +/home/$USER/Boostnote +``` +If this indeed is the location of the installation, then you don't really have to do anything. +1. Create a Repo on github ( or use an existing one ), where you would like notes to be saved. +2. Go to installation directory: +```bash +$ cd ~/Boostnote +``` +3. Initialise a git repository and add the remote to your desired repository. +```bash +$ git init +$ git remote add origin +``` +4. Let the cli take control now. Since your already have the correct location for ``Boostnote`` folder, all you have to do now is: +```bash +$ syncboostnote # This will call the cli +``` +This will add all your notes to repo and also generate ``.md`` files for them. ( placed in the */Boostnote/note/syncboostnote ). +5. To publish the added notes to your github repo +```bash +$ syncboostnote --sync +``` +This will upload the folder ``Boostnote`` to github with the following tree: +```bash +$ tree Boostnote +Boosnote +├── boostnote.json +├── history.json +├── notes +| ├── ....cson +| ├── ....cson +| └── syncboostnote +| ├── ....md +| ├── ....md +├──── README.md + +``` +- Directory `boostnote`: + - boostnote.json ``Created by boostnote`` + - history.json ``Created by SyncBoostnote`` + - Directory `notes`: + - Raw `.cson` files used by BoostNote. + - Directory `syncboostnote`: + - `.md` files used display content on Github. + - ``README.md`` Created by ``SyncBoostnote``. Will help you keep track + + **README.md** will have links to all your notes on the Github repo. + +```bash +(base) dumbmachine@dumbmachine  ~/Boostnote   master ●  git init +Initialized empty Git repository in /home/dumbmachine/Boostnote/.git/ +(base) dumbmachine@dumbmachine  ~/Boostnote   master  git remote add origin git@github.com:DumbMachine/temp.git +(base) ✘ dumbmachine@dumbmachine  ~/Boostnote   master  git remote add origin git@github.com:DumbMachine/SyncBoostNoteExample.git +(base) dumbmachine@dumbmachine  ~/Boostnote   master  syncboostnote +(base) dumbmachine@dumbmachine  ~/Boostnote   master  syncboostnote --sync +Adding all the things +[master (root-commit) 9b9bf03] . + 12 files changed, 1302 insertions(+) + create mode 100644 .gitignore + create mode 100644 README.md + create mode 100644 boostnote.json + create mode 100644 history.json + create mode 100644 notes/40c68663-6c75-4c85-a219-a60b137ad262.cson + create mode 100644 notes/ad76eb68-c488-4e9e-bb7e-3a912d1df252.cson + create mode 100644 notes/bbbcc9eb-2cbc-43ba-b6af-fdbcca305e71.cson + create mode 100644 notes/cc0c17cb-25bb-45f6-bdf0-fc8b54e88bb2.cson + create mode 100644 notes/syncboostnote/Day: Tuesday Date: June 18.md + create mode 100644 notes/syncboostnote/Dillinger.md + create mode 100644 notes/syncboostnote/Stolen Content.md + create mode 100644 notes/syncboostnote/SyncBoostNote.md +Enumerating objects: 16, done. +Counting objects: 100% (16/16), done. +Delta compression using up to 8 threads +Compressing objects: 100% (16/16), done. +Writing objects: 100% (16/16), 12.82 KiB | 2.56 MiB/s, done. +Total 16 (delta 3), reused 0 (delta 0) +remote: Resolving deltas: 100% (3/3), done. +To github.com:DumbMachine/SyncBoostNoteExample.git + * [new branch] master -> master +Everything up-to-date +(base) dumbmachine@dumbmachine  ~/Boostnote   master  +``` +![image](https://user-images.githubusercontent.com/23381512/60123229-9a42a380-97a4-11e9-9da0-e38b4460933d.png) + +## Example of Repository Generated can be found [here](https://github.com/DumbMachine/SyncBoostNoteExample). TODOS: -- [ ] Have tags at a newline together and seperate from other shields. -- [ ] MAke syncboostnotes directory. -- [ ] Make Python Package. -- [ ] Make this repo Pulic. -- [ ] Generate README - - [ ] Sort the names by tag. - - [ ] Sort the names by date. - - [ ] sort the names by name (alphabetically) -- [ ] Sick boi. -- [ ] Puts checks for, if the ".md" is somehow deleted, put it back in. -- [ ] Brings in Opps. +- [ ] Order Shields inline, ( Dates in one line, tags in one, folder specific in one ) ## Thanks to this repo: -- pycson - - This reduced alot of the load. \ No newline at end of file +- [pycson](https://github.com/avakar/pycson) + - This helped in saving me alot of time. + diff --git a/SyncBoostNote/bns.yaml b/SyncBoostNote/bns.yaml deleted file mode 100644 index a3946be..0000000 --- a/SyncBoostNote/bns.yaml +++ /dev/null @@ -1,5 +0,0 @@ -BOOSTNOTE_PATH: ~/BoostNotes -FREQUENCY: hourly -SHIELDS: true -SHIELDS_TYPE: for-the-badge -TIME: 1200 diff --git a/SyncBoostNote/cli.py b/SyncBoostNote/cli.py deleted file mode 100644 index 49d29e8..0000000 --- a/SyncBoostNote/cli.py +++ /dev/null @@ -1,105 +0,0 @@ -import argparse -import json -import os -import shutil -import site -import sys -import textwrap -import time -import warnings - -from colorama import Fore, init - -from config import Config, interactive, config_reader -from test import ultimate - -init(autoreset=True) - - -def options(): - ''' - Parsing the Arguments here - ''' - ap = argparse.ArgumentParser( - prog="projectpy", - usage="%(prog)s [options]", - formatter_class=argparse.RawDescriptionHelpFormatter, - description=textwrap.dedent('''SyncBoostNotes - ======================================= - - A CLI to Sync your BoostNotes Notes - - ======================================= - ''')) - - ap.add_argument("-l", "--location", required=True, - help="Location of the BoostNotes local storage.") - - ap.add_argument( - "-c", - "--config", - required=False, - help="Location of the config.yaml/config.yml file", - default=True) - - ap.add_argument( - "-col", - "--color", - required=False, - help="Toggle Colors on the print", - default=True) - - ap.add_argument( - "-g", - "--generate", - required=False, - help="Generate config.yaml file for ya", - default=True) - - ap.add_argument( - "-i", - "--interactive", - required=False, - help="Get an Interactive prompt to fill the forms.", - default=False) - - return vars(ap.parse_args()) - - -def initialize(args): - ''' - Sets the Config for the installation - ''' - if args['interactive']: - print('>>> Interactive <<<') - return interactive() - - elif args['config']: - print('>>> CUSTOM <<<') - ultimate(config_reader('./config.yaml')) - - else: - # ! Weird - raise Exception('Weird') - - -def main(): - args = options() - initialize(args) - - -def run_as_command(): - main() - - -run_as_command() -# print( -# ''' -# _________________________________ -# | | -# | Generation was successful | -# | Below is the generated config: | -# | ------------------------- | -# | $ cd repo_name | -# | $ python setup.py install | -# ------------------------------ -# ''' -# ) diff --git a/SyncBoostNote/config.yaml b/SyncBoostNote/config.yaml deleted file mode 100644 index 2883286..0000000 --- a/SyncBoostNote/config.yaml +++ /dev/null @@ -1,7 +0,0 @@ -BOOSTNOTE_PATH: ~/BoostNotes - -SHIELDS: True -SHIELDS_TYPE: "for-the-badge" - -FREQUENCY: hourly # ["hourly", "daily", "weekly", "monthly"] -TIME: 12 # 24 Hour format for now diff --git a/SyncBoostNote/history.json b/SyncBoostNote/history.json deleted file mode 100644 index e2d36c0..0000000 --- a/SyncBoostNote/history.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "48b6616e-2dae-4a4e-a86e-e8133bcdbe05.cson": { - "title": "Snippet note example", - "updated": false - }, - "a0681b02-531d-4212-b35e-3bab8b60beea.cson": { - "title": "create-python-project", - "updated": false - }, - "b8b2dcb5-8c0e-43bf-84ab-0a7605bf31c1.cson": { - "title": "\ud83d\udd34 THIS DOCUMENTATION IS WIP and will be completed soon.", - "updated": false - }, - "2a3fd7c0-8c21-472a-bf95-05ea84d0d41e.cson": { - "title": "Day: Monday", - "updated": false - }, - "a7469891-2c80-41e0-b7be-e1c6f05f92a5.cson": { - "title": "TEST", - "updated": false - }, - "cc0c17cb-25bb-45f6-bdf0-fc8b54e88bb2.cson": { - "title": "Day: Tuesday Date: June 18", - "updated": false - }, - "43cbad10-3808-42c2-8439-1478b45b6293.cson": { - "title": "DAY: SUNDAY", - "updated": false - }, - "b008ef56-e95c-4a09-9268-349bd4fbf77d.cson": { - "title": "", - "updated": false - }, - "8d91b880-2462-4a8b-a914-1712b8579bc1.cson": { - "title": "Welcome to Boostnote!", - "updated": false - } -} \ No newline at end of file diff --git a/SyncBoostNote/test.py b/SyncBoostNote/test.py deleted file mode 100644 index 1dbb49e..0000000 --- a/SyncBoostNote/test.py +++ /dev/null @@ -1,391 +0,0 @@ -# ! TODO: Add Highligthing shit - -import json -import os -import subprocess -import time -from collections import deque -from datetime import datetime -from glob import glob - -import cson - -from config import git_commands - -home = os.path.expanduser("~") -BOOSTNOTE_PATH = os.path.join(home, 'Boostnote') -BOOSTNOTE_NOTES_PATH = os.path.join(home, 'Boostnote/notes') -BOOSTNOTE_SYNCNOTES_PATH = os.path.join( - home, 'Boostnote/notes/syncboostnote') - - -def boostnote_exists(location=os.path.join(home, 'Boostnote')): - if os.path.isdir(BOOSTNOTE_PATH): - return 1 - else: - # ! YAML Config - return 0 - # raise NotADirectoryError("BoostNode Base Directory doesn't exist. Either make sure BoostNote is installed or add PATH to it in syncboostnote.yaml")\ - - -def boostnote_notes_exist(location=os.path.join(home, 'notes')): - if os.path.isdir(BOOSTNOTE_NOTES_PATH): - return 1 - else: - # ! YAML Config - raise NotADirectoryError( - f"No NOTES were found in the {BOOSTNOTE_NOTES_PATH} directory") - - -def get_notes(): - if boostnote_notes_exist(): - notes = glob(os.path.join(BOOSTNOTE_NOTES_PATH, '*.cson')) - if notes: - return notes - else: - raise EnvironmentError("Emptry Notes Folder, nothing to work on.") - else: - raise NotADirectoryError( - "BoostNode Base Directory doesn't exist. Either make sure BoostNote is installed or add PATH to it in syncboostnote.yaml") - - -def cson_reader(location): - if os.path.isfile(location): - data = cson.load(open(location, 'r')) - return data - else: - raise FileNotFoundError(f'The cson file at {location} was not found') - - -def customshield( - label='label', - message='message', - style='plastic', - color='orange', - mode='markdown', - name='Custom Shield'): - ''' - - ''' - if mode not in ['markdown', 'md', 'restructuredtext', 'rst']: - raise NotImplementedError(f'{mode} is not implemented yet.') - else: - if mode in ['markdown', 'md']: - return f"![{name}](https://img.shields.io/badge/{label}-{message}-{color}.svg?style={style})" - else: - return f".. image:: https://img.shields.io/badge/{label}-{message}-{color}.svg?style={style} :alt: {name}" - - -def markdown_writer(things, location, shields=True, - options={ - 'style': 'for-the-badge', - 'option': 2 - }): - ''' - Used to write the markdown files - -------------------------------- - PARAMETERS: - things: dict, cson_reader(fp): - The dict{} which contains shit that was read via the cson - - ''' - - embels = ['isStarred', 'isTrashed', - 'updatedAt', 'type', 'folder', 'tags'] - shelds = [] - if shields: - for key in embels: - x = None - if things[key]: - if key == 'isStarred': - shelds.append(customshield( - key, '⭐', color='black', style=options['style'])) - - if key == 'isTrashed': - shelds.append(customshield( - key, '🗑', color='black', style=options['style'])) - - elif key == 'updatedAt': - shelds.append(customshield( - key, things[key].split(':')[0][:-3].replace('-', '/'), color='green', style=options['style'])) - - elif key in ['type', 'folder']: - shelds.append(customshield( - key, things[key], color='blue', style=options['style'])) - - elif key == 'tags': - if options['option']: - # OPTION 1: {tag| gay} {tag| notgay} - for tag in things[key]: - shelds.append( - customshield(label='tag', message=tag, - color='purple', style=options['style']) - ) - else: - # OPTION 2: {tag| gay, notgay} - tags = [] - for tag in things[key]: - tags.append(tag) - shelds.append(customshield( - label='tags', message='_'.join(tags), color='blueviolet', style=options['style'])) - - file = open(os.path.join(location, - f"{things['title']}.md"), 'w+') - - for count, shield in enumerate(shelds): - if count != len(shelds) - 1: - file.write(shield) - file.write(' ') - else: - file.write(shield) - file.write('\n') - - try: - file.write(things['content']) - - except: - pass - try: - file.write(things['snippets']) - - except: - pass - - -# for note in get_notes(): -# markdown_writer( -# cson_reader(note), -# options={ -# 'style': 'for-the-badge', -# 'option': 2 -# } -# ) -# boostnote_exists() -# boostnote_notes_exist() -# cson_reader() -# cson_reader() - -def get_changes(): - ''' - uses git status to find the files which have changes. - ------------------------------------------------------ - - Returns: - list: - A List containing all the files names which have changed. - >>> get_changes() - >>> ['test.py', 'cli.py', 'config.py', 'utils.py'] - ''' - files = [] - os.chdir(BOOSTNOTE_NOTES_PATH) - p = subprocess.Popen( - git_commands.status, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - for line in p.stdout.readlines(): - # Putting checks to see if any rendered file is deleted. - if '.md' in line.decode("utf-8"): - files.append( - line.decode( - "utf-8").replace('modified:', '').strip().split('/')[-1] - ) - # Checking if the diff file is .cson - if '.cson' in line.decode("utf-8"): - files.append(line.decode( - "utf-8").replace('modified:', '').strip().split('/')[-1] - ) - retval = p.wait() - return files - - -def update_changes(): - changed_files = get_changes() - history_json = json.load(open(os.path.join( - BOOSTNOTE_PATH, 'history.json'), 'r')) - for file in changed_files: - if '.md' in file: - # These files have been deleted or changed without telling us 😢😢😢 - # Thus we will re render them - # 1. get the filename - history_json = json.load(open(os.path.join( - BOOSTNOTE_PATH, 'history.json'), 'r')) - for filename in history_json.keys(): - if history_json[filename]['title'] == file.replace('.md', ''): - # 2. Rendering the missing files. - markdown_writer( - cson_reader( - os.path.join( - BOOSTNOTE_PATH, 'notes', filename) - ), - location=BOOSTNOTE_SYNCNOTES_PATH, - options={ - 'style': 'for-the-badge', - 'option': 2 - } - ) - pass - else: - history_json[file]['updated'] = False - json.dump( - history_json, - open(os.path.join(BOOSTNOTE_PATH, 'history.json'), 'w+') - ) - - -def create_history(): - files = {} - for note in get_notes(): - files[note.split('/')[-1]] = { - 'title': cson.load(open(note, 'r'))['title'], - 'updated': False - } - json.dump( - files, - open(os.path.join(BOOSTNOTE_PATH, 'history.json'), 'w+') - ) - - -def create_readme(config): - - notes = get_notes() - # data = {} - file = open(os.path.join(config['BOOSTNOTE_PATH'], 'README.md'), 'w+') - file.write( - '''# SnycBoostNotes -# This repo consists of two directories: -```bash -$ tree -. -├── boostnote.json -├── history.json -└── notes - ├── ....cson - ├── ....cson - └── syncboostnote - ├── ....md - ├── ....md -``` -- Directory `base`: - - boostnote.json ``Created by boostnote`` - - history.json ``Created by SyncBoostnote`` - - Directory `notes`: - - Raw `.cson` files used by BoostNote. - - Directory `syncboostnote`: - - `.md` files used display content on Github. - -# Index: -# This following are the documents: - - ''' - ) - for note in get_notes(): - data = cson_reader(note) - # data[note.split("/")[-1]] = { - # 'title': cson_reader(note)['title'], - # 'createdAt': cson_reader(note)['createdAt'], - # 'tags': cson_reader(note)['tags'], - # } - # ! Generate Github link here - file.write( - f"- [{data['title']}](https://github.com/DumbMachine/temp/blob/master/notes/syncboostnote/{data['title'].replace(' ','%20')}.md)") - file.write("\n") - # return data - - awesome = 'https://img.shields.io/badge/made--with--%E2%99%A5--by-ProjectPy-blueviolet.svg' - - file.write( - f"\n---\nThis README was generated with ❤ by [SyncBoostnote](https://github.com/DumbMachine/SyncBoostNote) ") - - -def ultimate(config): - if not os.path.isfile(os.path.join(config['BOOSTNOTE_PATH'], 'history.json')): - # Create the History json again. - create_history() - if boostnote_exists(config['BOOSTNOTE_PATH']): - print() - print('[PASSED] BOOSTNOTE_EXISTS ') - if boostnote_notes_exist(os.path.join(config['BOOSTNOTE_PATH'], 'notes')): - print('[PASSED] BOOSTNOTE_NOTES_EXISTS ') - history_json = json.load(open(os.path.join( - BOOSTNOTE_PATH, 'history.json'), 'r')) - for file in history_json.keys(): - print(file, history_json[file]['updated']) - if not history_json[file]['updated']: - # If not updated, re render the file - markdown_writer( - cson_reader( - os.path.join( - config['BOOSTNOTE_PATH'], 'notes', file) - ), - location=BOOSTNOTE_SYNCNOTES_PATH, - options={ - 'style': config['SHIELDS_TYPE'], - 'option': 2 - } - ) - # Update the file render - history_json[file]['updated'] = True - - create_readme(config) - # Writing the changes of render. - json.dump( - history_json, - open(os.path.join(BOOSTNOTE_PATH, 'history.json'), 'w+') - ) - print('[PASSED] README_GEN ') - - else: - print("FUCKKK") - - -ultimate({ - "BOOSTNOTE_PATH": os.path.join(home, 'Boostnote'), - "SHIELDS": True, - "SHIELDS_TYPE": "for-the-badge", - "FREQUENCY": "hourly", - "TIME": 1200 -}) - - -# def timely_check(config): -# time_check = config['TIME'] -# frequency = config['FREQUENCY'] - -# if frequency == 'onchange': -# raise NotImplementedError('This THING is not implemented currently') -# else: -# if datetime.now().hour == time_check: -# print("ITS HIGH NOON") -# else: -# time.sleep(2) -# print("Has the time come yet?") - - -# # timely_check( -# # { -# # "BOOSTNOTE_PATH": os.path.join(home, 'Boostnote'), -# # "SHIELDS": True, -# # "SHIELDS_TYPE": "for-the-badge", -# # "FREQUENCY": "hourly", -# # "TIME": 11 -# # } -# # ) - - -# def watch_file(filename, time_limit=3600, check_interval=60): -# ''' -# use get changes to see changes -# ''' -# now = time.time() -# last_time = now + time_limit - -# while time.time() <= last_time: -# if os.path.exists(filename): -# print('yes') -# else: -# # Wait for check interval seconds, then check again. -# time.sleep(check_interval) - -# print('no') - - -# def create_readme(): -# raise NotImplementedError diff --git a/SyncBoostNote/utils.py b/SyncBoostNote/utils.py deleted file mode 100644 index e69de29..0000000 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a5a5114 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +colorama>=0.4.0 +pyyaml>=4.0 +cson >=0.8 \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..45b41d1 --- /dev/null +++ b/setup.py @@ -0,0 +1,54 @@ +import os +import platform + +from setuptools import find_packages, setup + +with open('./requirements.txt') as f: + required = f.read().splitlines() + + +setup(name="syncboostnote", + version="0.1a", + install_requires=required, + packages=find_packages(exclude=["tests"]), + scripts=[], + description="CLI tool to help syncing BoostNote notes.", + long_description="A simple CLI, created in Python, to help users to Sync their notes created in BoostNote Application with their Github Account. Along with syncing, it also creates a repo with a beautiful layout and interface to consume the notes on mobile devices.", + author="Ratin Kumar", + author_email="ratin.kumar.2k@gmail.com", + maintainer="Ratin Kumar (@DumbMachine)", + maintainer_email="ratin.kumar.2k@gmail.com", + url="https://github.com/DumbMachine/syncboostnote", + download_url="https://github.com/DumbMachine/syncboostnote/releases", + license="GNU-gpl3", + test_suite="tests", + # setup_requires = ["pytest-runner"], + # tests_require = ["pytest"], + classifiers=[ + # "Development Status :: 1 - Planning", + # "Development Status :: 2 - Pre-Alpha", + # "Development Status :: 3 - Alpha", + "Development Status :: 4 - Beta", + # "Development Status :: 5 - Production/Stable", + # "Development Status :: 6 - Mature", + "Intended Audience :: Developers", + "Intended Audience :: Information Technology", + "Operating System :: MacOS", + "Operating System :: POSIX", + "Operating System :: Unix", + "Programming Language :: Python", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Topic :: Software Development", + "Topic :: Utilities", + ], + entry_points={ + 'console_scripts': [ + 'syncboostnote = syncboostnote.cli:run_as_command', + ], + }, + platforms="Any", + ) diff --git a/syncboostnote/bns.yaml b/syncboostnote/bns.yaml new file mode 100644 index 0000000..053364f --- /dev/null +++ b/syncboostnote/bns.yaml @@ -0,0 +1,5 @@ +BOOSTNOTE_PATH: default # default: /home/$USER/Boostnote +FREQUENCY: manual # ["hourly", "daily", "weekly", "onchange", "manual"] Others will be implemented soon. +SHIELDS: true +SHIELDS_TYPE: for-the-badge # ['plastic' , 'flat', 'flat-square', 'for-the-badge', 'popout', 'popout-square', 'social'] +# TIME: 1200 Not recommended diff --git a/syncboostnote/cli.py b/syncboostnote/cli.py new file mode 100644 index 0000000..9bce22d --- /dev/null +++ b/syncboostnote/cli.py @@ -0,0 +1,123 @@ +import argparse +import json +import os +import shutil +import site +import sys +import textwrap +import time +# import warnings + +import yaml +from colorama import Fore, init + +from .config import Config, config_reader, interactive, generate_config +from .utils import initialize +from .test import sync + +home = os.path.expanduser("~") + +init(autoreset=True) + + +def options(): + ''' + Parsing the Arguments here + ''' + ap = argparse.ArgumentParser( + prog="syncboostnote", + usage="%(prog)s [options]", + formatter_class=argparse.RawDescriptionHelpFormatter, + description=textwrap.dedent('''SyncBoostNotes +======================================= +- A CLI to Sync your BoostNotes Notes - +======================================= + ''')) + + ap.add_argument( + "-l", + "--location", + required=False, + help="Location of the BoostNotes local storage.", + default=os.path.join(home, 'Boostnote')) + + # ap.add_argument( + # "-ini", + # "--init", + # required=False, + # help="Initialise the CLI", + # default=False) + + ap.add_argument( + "--sync", + # dest=False, + action='store_true' + ) + + ap.add_argument( + "-co", + "--config", + required=False, + help="Location of the config.yaml/config.yml file", + default=False) + + ap.add_argument( + "-g", + "--generate", + help="Generate config.yaml file for ya", + action='store_true' + ) + + ap.add_argument( + "-i", + "--interactive", + required=False, + help="Get an Interactive prompt to fill the forms.", + default=False) + + ap.set_defaults(feature=False) + return vars(ap.parse_args()) + + +def starter(args): + ''' + Sets the Config for the installation + ''' + if args['sync']: + sync() + + elif args['generate']: + return generate_config() + + elif args['interactive']: + return interactive() + + elif args['location']: + # Location is at the default and the user wants default shit. + initialize({ + "BOOSTNOTE_PATH": args['location'], + "SHIELDS": True, + "SHIELDS_TYPE": "for-the-badge", + "FREQUENCY": "hourly", + "TIME": 1200 + }) + + elif args['config']: + print(args['generate']) + conf = yaml.load( + open(args['generate'], 'r'), + Loader=yaml.Loader + ) + initialize(conf) + + else: + # ! Weird + raise Exception('Weird') + + +def run_as_command(): + args = options() + starter(args) + + +run_as_command() diff --git a/SyncBoostNote/config.py b/syncboostnote/config.py similarity index 76% rename from SyncBoostNote/config.py rename to syncboostnote/config.py index 0e99157..fc2d56c 100644 --- a/SyncBoostNote/config.py +++ b/syncboostnote/config.py @@ -29,7 +29,6 @@ def interactive(): ['BOOSTNOTE_PATH: ', ' ~/BoostNotes', 'BOOSTNOTE_PATH'], ['SHIELDS: ', ' True', 'SHIELDS'], ['SHIELDS_TYPE: ', 'for-the-badge', 'SHIELDS_TYPE'], - # ["hourly", "daily", "weekly", "monthly", "onchange"] ['FREQUENCY: ', 'hourly', 'FREQUENCY'], ['TIME: ', '1200', 'TIME'], # 24 gour format for now @@ -58,14 +57,15 @@ def hook(): return result -def create_config(location='.'): +def generate_config(): ''' Creates a default config for users to see. ''' - yaml.dump( - Config, - open(os.path.join(location, 'bns.yaml'), 'w+') - ) - - -# create_config() + open( + 'config.yaml', 'w+' + ).write('''BOOSTNOTE_PATH: default # default: /home/$USER/Boostnote +FREQUENCY: manual # ["hourly", "daily", "weekly", "onchange", "manual"] Others will be implemented soon. +SHIELDS: true +SHIELDS_TYPE: for-the-badge # ['plastic' , 'flat', 'flat-square', 'for-the-badge', 'popout', 'popout-square', 'social'] +# TIME: 1200 Not recommended +''') diff --git a/syncboostnote/test.py b/syncboostnote/test.py new file mode 100644 index 0000000..d102ca3 --- /dev/null +++ b/syncboostnote/test.py @@ -0,0 +1,63 @@ +# # ! TODO: Add Highligthing shit + +import json +import os +import platform +import subprocess +import time +from collections import deque +from datetime import datetime +from glob import glob + +# import cson +home = os.path.expanduser("~") + + +def sync( + location=os.path.join(home, 'Boostnote') +): + os.chdir(location) + p = subprocess.Popen( + "git status", shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + # print(p.stdout.readlines()[1].strip()) + if p.stdout.readlines()[1].decode("utf=8").strip() == 'nothing to commit, working tree clean': + os.system("git push origin master") + else: + print("Adding all the things") + p = subprocess.Popen( + "git add -A", shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + + # print("Commiting all the things") + p1 = subprocess.Popen( + f"git commit -m '{datetime.now()}'", shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + for line in p1.stdout.readlines()[2:]: + print(line.decode("utf-8")) + + # print("Pushing all the things") + p2 = subprocess.Popen( + "git push origin master", shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + + retval = p.wait() + + +def boostnote_exists(location=os.path.join(home, 'Boostnote')): + if os.path.isdir(BOOSTNOTE_PATH): + return 1 + else: + # ! YAML Config + return 0 + # raise NotADirectoryError("BoostNode Base Directory doesn't exist. Either make sure BoostNote is installed or add PATH to it in syncboostnote.yaml")\ + + +def boostnote_notes_exist(location=os.path.join(home, 'notes')): + if os.path.isdir(BOOSTNOTE_NOTES_PATH): + return 1 + else: + # ! YAML Config + raise NotADirectoryError( + f"No NOTES were found in the {BOOSTNOTE_NOTES_PATH} directory") + + +def git_update(message='nothing'): + os.system('git add -A') + os.system(f"git commit -m '{message}'") diff --git a/syncboostnote/utils.py b/syncboostnote/utils.py new file mode 100644 index 0000000..631f171 --- /dev/null +++ b/syncboostnote/utils.py @@ -0,0 +1,425 @@ +from __future__ import print_function + +import json +import os +import platform +import sys +import time +from glob import glob + +import cson +import subprocess +from .test import git_update + +home = os.path.expanduser("~") + + +def initialize(config): + ''' + Initialize will do the following: + - Check for the directories + - Make directory for storing notes. + - Create History + # ! - Initialize the git repo. + -----------------s------------------- + called: + - Is called upon using --initialize flag of the CLI + ''' + # print(config['BOOSTNOTE_PATH']) + BOOSTNOTE_PATH = config['BOOSTNOTE_PATH'] + if config['BOOSTNOTE_PATH'] == 'default': + BOOSTNOTE_PATH = os.path.join(home, 'Boostnote') + if boostnote_exists(location=BOOSTNOTE_PATH): + BOOSTNOTE_NOTES = os.path.join(BOOSTNOTE_PATH, 'notes') + create_syncnotes_dir( + location=os.path.join(BOOSTNOTE_NOTES, 'syncboostnote') + ) + create_history(BOOSTNOTE_PATH) + # update_changes() + ultimate(config) + else: + raise NotADirectoryError( + f"Boostnote not found at the given location. {BOOSTNOTE_PATH}") + + +def boostnote_exists(location=os.path.join(home, 'Boostnote')): + if os.path.isdir(location): + return 1 + else: + # ! YAML Config + return 0 + # raise NotADirectoryError("BoostNode Base Directory doesn't exist. Either make sure BoostNote is installed or add PATH to it in syncboostnote.yaml")\ + + +def create_syncnotes_dir( + location=os.path.join(home, 'Boostnote/notes/syncboostnote') +): + """ + Create Directory for syncboostnotes1 + """ + current_platform = platform.system().lower() + if current_platform != 'windows': + import pwd + + # create the necessary directory structure for storing config details + # in the syncboostnote directory + required_dirs = [location] + for dir in required_dirs: + if not os.path.exists(dir): + try: + os.makedirs(dir) + if (current_platform != 'windows') and os.getenv("SUDO_USER"): + # owner of .syncboostnote should be user even when installing + # w/sudo + pw = pwd.getpwnam(os.getenv("SUDO_USER")) + os.chown(dir, pw.pw_uid, pw.pw_gid) + except OSError: + print("syncboostnotes lacks permission to " + f"access the '{location}/notes/syncboostnotes' directory.") + raise + + else: + pass + + +def create_history( + location=os.path.join(home, 'Boostnote') +): + files = {} + for note in get_notes(): + files[note.split('/')[-1]] = { + 'title': cson.load(open(note, 'r'))['title'], + 'updated': False + } + try: + json.dump( + files, + open(os.path.join(location, 'history.json'), 'w+') + ) + except Exception as e: + print( + f"EXiting, got the following error {e}.\nReport the error on Github") + + +def get_notes( + location=os.path.join(home, 'Boostnote', 'notes') + +): + notes = glob(os.path.join(location, '*.cson')) + if notes: + return notes + else: + raise EnvironmentError("Emptry Notes Folder, nothing to work on.") + + +def ultimate(config): + ''' + Performs the followings: + - Creates history.json, if it doesn't exist + - If it does: + - reads it. + - updates the .md files, which require it. + ----------------------------------------------- + ''' + if not os.path.isfile(os.path.join(config['BOOSTNOTE_PATH'], 'history.json')): + + # Create the History json again. + create_history(config['BOOSTNOTE_PATH']) + if boostnote_exists(config['BOOSTNOTE_PATH']): + + # Creating History again, as this will track if new files have been added. + + create_history(config['BOOSTNOTE_PATH']) + + history_json = json.load(open(os.path.join( + config['BOOSTNOTE_PATH'], 'history.json'), 'r')) + for file in history_json.keys(): + if not history_json[file]['updated']: + + # If not updated, re render the file + markdown_writer( + cson_reader( + os.path.join( + config['BOOSTNOTE_PATH'], 'notes', file) + ), + location=os.path.join( + config['BOOSTNOTE_PATH'], 'notes', 'syncboostnote' + ), + options={ + 'style': config['SHIELDS_TYPE'], + 'option': 2 + } + ) + # Update the file render + history_json[file]['updated'] = True + + create_readme(config) + # Writing the changes of render. + json.dump( + history_json, + open(os.path.join(config['BOOSTNOTE_PATH'], 'history.json'), 'w+') + ) + + else: + print("FUCKKK") + + +def cson_reader(location): + if os.path.isfile(location): + data = cson.load(open(location, 'r')) + return data + else: + return 0 + + +def customshield( + label='label', + message='message', + style='plastic', + color='orange', + mode='markdown', + name='Custom Shield'): + ''' + + ''' + if mode not in ['markdown', 'md', 'restructuredtext', 'rst']: + raise NotImplementedError(f'{mode} is not implemented yet.') + else: + if mode in ['markdown', 'md']: + return f"![{name}](https://img.shields.io/badge/{label}-{message}-{color}.svg?style={style})" + else: + return f".. image:: https://img.shields.io/badge/{label}-{message}-{color}.svg?style={style} :alt: {name}" + + +def markdown_writer(things, location, shields=True, + options={ + 'style': 'for-the-badge', + 'option': 2 + }): + ''' + Used to write the markdown files + -------------------------------- + PARAMETERS: + things: dict, cson_reader(fp): + The dict{} which contains shit that was read via the cson + + ''' + if things: + embels = ['isStarred', 'isTrashed', 'createdAt', + 'updatedAt', 'type', 'folder', 'tags'] + shelds = [] + if shields: + for key in embels: + x = None + if things[key]: + if key == 'isStarred': + shelds.append(customshield( + key, '⭐', color='black', style=options['style'])) + + if key == 'isTrashed': + shelds.append(customshield( + key, '🗑', color='black', style=options['style'])) + + elif key == 'updatedAt': + year, month, day = things[key].split( + ':')[0][:-3].split('-') # 2019/06/29 + shelds.append(customshield( + "UpdatedAt".replace(" ", "%20"), f"{day}%20{month}%20{year}", color='green', style=options['style'])) + + elif key == 'createdAt': + year, month, day = things[key].split( + ':')[0][:-3].split('-') # 2019/06/29 + shelds.append(customshield( + "createdAt".replace(" ", "%20"), f"{day}%20{month}%20{year}", color='green', style=options['style'])) + + elif key in ['type', 'folder']: + shelds.append(customshield( + key, things[key], color='blue', style=options['style'])) + + elif key == 'tags': + if options['option']: + # OPTION 1: {tag| gay} {tag| notgay} + for tag in things[key]: + shelds.append( + customshield(label='tag', message=tag, + color='purple', style=options['style']) + ) + else: + # OPTION 2: {tag| gay, notgay} + tags = [] + for tag in things[key]: + tags.append(tag) + shelds.append(customshield( + label='tags', message='_'.join(tags), color='blueviolet', style=options['style'])) + + file = open(os.path.join(location, + f"{things['title']}.md"), 'w+') + + for count, shield in enumerate(shelds): + if count != len(shelds) - 1: + file.write(shield) + file.write(' ') + else: + file.write(shield) + file.write('\n') + + try: + file.write(things['content']) + + except: + pass + try: + file.write(things['snippets']) + + except: + pass + + +def get_changes( + location=os.path.join(home, 'Boostnote') +): + ''' + uses git status to find the files which have changes. + ------------------------------------------------------ + + Returns: + list: + A List containing all the files names which have changed. + >>> get_changes() + >>> ['test.py', 'cli.py', 'config.py', 'utils.py'] + ''' + files = [] + os.chdir(location) + p = subprocess.Popen( + "git status", shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + for line in p.stdout.readlines(): + # Putting checks to see if any rendered file is deleted. + if '.md' in line.decode("utf-8"): + files.append( + line.decode( + "utf-8").replace('modified:', '').strip().split('/')[-1] + ) + # Checking if the diff file is .cson + if '.cson' in line.decode("utf-8"): + files.append(line.decode( + "utf-8").replace('modified:', '').strip().split('/')[-1] + ) + retval = p.wait() + return files + + +def update_changes( + location=os.path.join(home, 'Boostnote') +): + # create_history(location) + changed_files = get_changes() + history_json = json.load(open(os.path.join( + location, 'history.json'), 'r')) + for file in changed_files: + if '.md' in file: + # These files have been deleted or changed without telling us 😢😢😢 + # Thus we will re render them + + # 1. get the filename + history_json = json.load(open(os.path.join( + location, 'history.json'), 'r')) + for filename in history_json.keys(): + print(file) + if history_json[filename]['title'] == file.replace('.md', ''): + if file == 'SnycBoostNotes.md': + continue + + # 2. Rendering the missing files. + markdown_writer( + cson_reader( + os.path.join( + location, 'notes', filename) + ), + location=os.path.join( + location, 'notes', 'syncboostnote'), + options={ + 'style': 'for-the-badge', + 'option': 2 + } + ) + else: + history_json[file]['updated'] = False + git_update(message='shit') + json.dump( + history_json, + open(os.path.join(location, 'history.json'), 'w+') + ) + + +def create_history( + location=os.path.join(home, 'Boostnote') +): + files = {} + for note in get_notes(): + files[note.split('/')[-1]] = { + 'title': cson.load(open(note, 'r'))['title'], + 'updated': False + } + json.dump( + files, + open(os.path.join(location, 'history.json'), 'w+') + ) + + +def create_readme(config): + + notes = get_notes() + file = open(os.path.join(config['BOOSTNOTE_PATH'], 'README.md'), 'w+') + file.write( + '''# SnycBoostNotes +# This repo consists of two directories: +```bash +$ tree +. +├── boostnote.json +├── history.json +└── notes + ├── ....cson + ├── ....cson + └── syncboostnote + ├── ....md + ├── ....md +``` +- Directory `base`: + - boostnote.json ``Created by boostnote`` + - history.json ``Created by SyncBoostnote`` + - Directory `notes`: + - Raw `.cson` files used by BoostNote. + - Directory `syncboostnote`: + - `.md` files used display content on Github. + +# Index: +# This following are the documents: +''' + ) + for note in get_notes(): + data = cson_reader(note) + # ! Generate Github link here + file.write( + # https://github.com/DumbMachine/SyncBoostNoteExample/blob/master/notes/syncboostnote/Stolen%20Content.md + f"- [{data['title']}](https://github.com/DumbMachine/{repo_name()}/blob/master/notes/syncboostnote/{data['title'].replace(' ','%20')}.md)") + if data['tags']: + file.write(" {}".format(customshield( + label='tags', message="%20,%20".join(data['tags']), color='purple'))) + file.write("\n") + + awesome = 'https://img.shields.io/badge/made--with--%E2%99%A5--by-ProjectPy-blueviolet.svg' + + file.write( + f"\n---\nThis README was generated with ❤ by [SyncBoostnote](https://github.com/DumbMachine/SyncBoostNote) ") + + +def repo_name( + location=os.path.join(home, 'Boostnote', '.git', 'config') +): + ''' + Reads .git/config for information on the Github repo name + ''' + for line in open(location, 'r').readlines(): + if ".git" in line.strip().split('/')[-1]: + return(line.strip().split('/')[-1].strip(".git"))