summaryrefslogtreecommitdiff
path: root/pnote
diff options
context:
space:
mode:
Diffstat (limited to 'pnote')
-rw-r--r--pnote/__init__.py3
-rw-r--r--pnote/__main__.py44
-rw-r--r--pnote/layout.py68
-rw-r--r--pnote/metadata.py188
-rw-r--r--pnote/project.py322
-rw-r--r--pnote/tools/__init__.py3
-rw-r--r--pnote/tools/admin.py48
-rw-r--r--pnote/tools/search.py79
-rw-r--r--pnote/tools/tag.py33
-rw-r--r--pnote/tools/tool.py8
10 files changed, 796 insertions, 0 deletions
diff --git a/pnote/__init__.py b/pnote/__init__.py
new file mode 100644
index 0000000..feb7ceb
--- /dev/null
+++ b/pnote/__init__.py
@@ -0,0 +1,3 @@
+__version__ = "0.0.25"
+
+from pnote.__main__ import main
diff --git a/pnote/__main__.py b/pnote/__main__.py
new file mode 100644
index 0000000..e539c36
--- /dev/null
+++ b/pnote/__main__.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+
+import os, argparse
+from pnote.project import *
+from pnote.tools import *
+from pnote import __version__
+
+def main():
+ ## Parse arguments
+ parser = argparse.ArgumentParser(
+ prog='PNote',
+ description='Note management tool',
+ epilog='pnote v'+__version__)
+ parser.add_argument('path', help="Path to a pnote project")
+ parser.add_argument('-t', '--today', help="Open today's note file", action="store_true")
+ parser.add_argument('-o', '--open', help="Open specific note file")
+ subparsers = parser.add_subparsers(dest="tool", help='Tool to use')
+
+ # Tools
+ searcht=ToolSearch()
+ searcht.add_parser(subparsers)
+ tagt=ToolTag()
+ tagt.add_parser(subparsers)
+ admint=ToolAdmin()
+ admint.add_parser(subparsers)
+
+ # Parse arguments
+ args = parser.parse_args()
+
+ ## Load project
+ project=Project(args.path)
+
+ ## Run tool
+ if args.tool == "search":
+ searcht.run(project,args)
+ elif args.tool == "tag":
+ tagt.run(project,args)
+ elif args.tool == "admin":
+ admint.run(project,args)
+ else:
+ if args.today:
+ project.opentoday()
+ elif args.open:
+ project.open(args.open)
diff --git a/pnote/layout.py b/pnote/layout.py
new file mode 100644
index 0000000..8d3fe1d
--- /dev/null
+++ b/pnote/layout.py
@@ -0,0 +1,68 @@
+from pathlib import Path
+from datetime import datetime
+import os
+
+class Layout:
+
+ def __init__(self, conf, paths):
+ self.conf=conf
+ self.paths=paths
+ self.today=datetime.today()
+ self.today_backup=self.today
+
+ def settoday(self,timestamp):
+ self.today=datetime.fromtimestamp(timestamp)
+
+ def restoretoday(self):
+ self.today=self.today_backup
+
+ def gettoday(self):
+ return self.today
+
+ def flatten(self):
+ """
+ List all subpath present on disk.
+ """
+ paths=list(Path(self.paths["files"]).rglob("*"))
+ result=list()
+ for p in paths:
+ if os.path.isfile(p):
+ result.append(p.relative_to(self.paths["files"]))
+ return result
+
+ def create(self):
+ """
+ Create today's note file.
+ """
+ file=self.todaypath()
+ if not os.path.exists(file):
+ open(file, 'a').close()
+ return self.todaysubpath()
+
+ def todayname(self):
+ """
+ Get today's note file name.
+ """
+ return self.today.strftime(self.conf["filename"])
+
+ def todaysubdir(self):
+ """
+ Must be overriden by child classes
+ """
+ subdir=self.today.strftime(self.conf["layout"])
+ if not os.path.exists(subdir):
+ Path(os.path.join(self.paths["files"],subdir)).mkdir(parents=True, exist_ok=True)
+ return subdir
+
+ def todaysubpath(self):
+ """
+ Get the subpath of today's note file.
+ """
+ subdir=self.todaysubdir()
+ return os.path.join(self.todaysubdir(), self.todayname())
+
+ def todaypath(self):
+ """
+ Get the path of today's note file.
+ """
+ return os.path.join(self.paths["files"],self.todaysubpath())
diff --git a/pnote/metadata.py b/pnote/metadata.py
new file mode 100644
index 0000000..f84ebb0
--- /dev/null
+++ b/pnote/metadata.py
@@ -0,0 +1,188 @@
+import os, json, platform, socket, sqlite3
+from datetime import datetime
+from pathlib import Path
+
+class Metadata:
+
+ def __init__(self, paths):
+ self.paths=paths
+ self.today=datetime.today()
+
+ ## Create folders
+ self.paths["metadata"]=os.path.join(self.paths["root"], "metadata.db")
+
+ ## Init database
+ self.con=sqlite3.connect(self.paths["metadata"])
+ cur=self.con.cursor()
+ tables=cur.execute("""SELECT name FROM sqlite_master WHERE type='table' AND name='files'; """).fetchall()
+
+ if len(tables) == 0:
+ cur.execute("CREATE TABLE files(id INTEGER PRIMARY KEY AUTOINCREMENT, subpath TEXT UNIQUE, created REAL, added REAL, hostname TEXT, platform TEXT);")
+ self.con.commit()
+ cur.execute("CREATE TABLE tags(id INTEGER, name TEXT, FOREIGN KEY(id) REFERENCES files(id));")
+ self.con.commit()
+ cur.execute("CREATE TABLE cache(name TEXT PRIMARY KEY, value TEXT);")
+ self.con.commit()
+
+
+ def create(self, subpath, created):
+ """
+ Create a new note file entry.
+ """
+ cur=self.con.cursor()
+ cur.execute("""INSERT INTO files(subpath,created,added,hostname,platform) values('{}','{}','{}','{}','{}')""".format(
+ subpath,
+ created.timestamp(),
+ datetime.today().timestamp(),
+ socket.gethostname(),
+ platform.platform()
+ ))
+ self.con.commit()
+
+ def getfileinfo(self, subpath, name):
+ """
+ Get associated info (name argument e.g hostname) with a subpath.
+ """
+ subpath_id=self.subpathid(subpath, True)
+ cur=self.con.cursor()
+ cur.execute('SELECT {} FROM files WHERE id="{}"'.format(name,subpath_id))
+ return list(cur.fetchone())[0]
+
+ def setcache(self, name, value):
+ """
+ Set the value of a cache entry.
+ """
+ cur=self.con.cursor()
+ cur.execute('SELECT value FROM cache WHERE name="{}"'.format(name))
+ if cur.fetchone() is None:
+ cur.execute('INSERT INTO cache values("{}","{}")'.format(name,value))
+ else:
+ cur.execute('UPDATE cache SET value="{}" WHERE name="{}"'.format(value,name))
+ self.con.commit()
+
+ def getcache(self,name):
+ """
+ Get the value of a cache entry.
+ """
+ cur=self.con.cursor()
+ cur.execute('SELECT value FROM cache WHERE name="{}"'.format(name))
+ result=cur.fetchone()
+ if result is None:
+ return None
+ return result[0]
+
+ def subpathid(self, subpath, required=False):
+ """
+ Get the id (sqlite id) of a subpath. If required=True then abort if subpath not found.
+ """
+ cur=self.con.cursor()
+ cur.execute('SELECT id FROM files WHERE subpath="{}"'.format(subpath))
+ result=cur.fetchone()
+ if result is not None:
+ return list(result)[0]
+ if required:
+ print("Subpath not found: "+subpath)
+ exit(1)
+ return None
+
+ def delete(self,subpath):
+ """
+ Delete subpath and its associated tags from the metadata.
+ """
+ cur=self.con.cursor()
+ subpath_id=self.subpathid(subpath, True)
+ cur.execute('DELETE FROM tags WHERE id={}'.format(subpath_id))
+ cur.execute('DELETE FROM files WHERE id={}'.format(subpath_id))
+ self.con.commit()
+
+ def addtag(self, subpath, tag):
+ """
+ Attach a tag to a specific subpath.
+ """
+ taglist=self.listtags(subpath)
+ if tag not in taglist:
+ cur=self.con.cursor()
+ subpath_id=self.subpathid(subpath, True)
+ cur.execute('INSERT INTO tags(id, name) VALUES({},"{}")'.format(subpath_id,tag))
+ self.con.commit()
+ else:
+ print("{} as already be tagged with {}".format(subpath,tag))
+
+ def deletetag(self, subpath, tag):
+ """
+ Delete a tag attached to a specific subpath.
+ """
+ cur=self.con.cursor()
+ subpath_id=self.subpathid(subpath, True)
+ cur.execute('DELETE FROM tags WHERE id={} AND name="{}"'.format(subpath_id,tag))
+ self.con.commit()
+
+ def obliteratetag(self, tag):
+ """
+ Remove all occurences of a tag from the database.
+ """
+ cur=self.con.cursor()
+ cur.execute('DELETE FROM tags WHERE name="{}"'.format(tag))
+ self.con.commit()
+
+ def searchtag(self,tag):
+ """
+ Get all subpaths associated with a specific tag.
+ """
+ cur=self.con.cursor()
+ ids=[i[0] for i in cur.execute('SELECT id FROM tags WHERE name="{}"'.format(tag)) ]
+ subpaths=[cur.execute('SELECT subpath FROM files WHERE id={}'.format(i)).fetchone()[0] for i in ids]
+ return subpaths
+
+ def listtags(self, subpath=None):
+ """
+ List either all tags (subpath is None), or the ones associated with a subpath.
+ """
+ cur=self.con.cursor()
+ if subpath is not None:
+ subpath_id=self.subpathid(subpath, True)
+ tags=[i[0] for i in cur.execute('SELECT DISTINCT name FROM tags WHERE id={}'.format(subpath_id)) ]
+ else:
+ tags=[i[0] for i in cur.execute('SELECT DISTINCT name FROM tags') ]
+ return tags
+
+ def fix_deleted(self, dry=True):
+ """
+ Search for files deleted by the user and update database accordingly.
+ """
+ cur=self.con.cursor()
+ for result in cur.execute("SELECT subpath FROM files"):
+ subpath=result[0]
+ path=os.path.join(self.paths["files"], subpath)
+ if not os.path.exists(path):
+ if dry:
+ print("Deletion detected => " + subpath)
+ else:
+ print("Fixing file deletion => " + subpath)
+ self.delete(subpath)
+
+ def fix_new(self, layout, dry=True):
+ """
+ Search for new files added by the user and update the database accordingly.
+ """
+ cur=self.con.cursor()
+ for subpath in layout.flatten():
+ result=cur.execute('SELECT * from files where subpath="{}"'.format(subpath))
+ if len(result.fetchall()) <= 0 :
+ if dry:
+ print("New file detected => "+str(subpath))
+ else:
+ print("Fixing new file => "+str(subpath))
+ self.create(subpath,layout.gettoday())
+
+ def flatten_ordered(self, desc=False, ordby="created"):
+ """
+ List all subpaths present in the database. Results are sorted (DESC and ASC) by creation date.
+ """
+ cur=self.con.cursor()
+ if desc:
+ result=cur.execute("SELECT subpath FROM files ORDER BY {} DESC".format(ordby))
+ else:
+ result=cur.execute("SELECT subpath FROM files ORDER BY {} ASC".format(ordby))
+ result=[subpath[0] for subpath in result.fetchall()]
+ return result
diff --git a/pnote/project.py b/pnote/project.py
new file mode 100644
index 0000000..432b688
--- /dev/null
+++ b/pnote/project.py
@@ -0,0 +1,322 @@
+import os, json, socket, re, subprocess, shutil
+from datetime import datetime
+from jsonschema import validate
+from pathlib import Path
+from pnote.layout import *
+from pnote.metadata import *
+
+class ProjectConfig:
+ FILE="config.json"
+ DEFAULT_CONFIG = {
+ "layout": "%Y/%m",
+ "filename": "%Y-%m-%d.md",
+ "editor": ["vim"],
+ "template": ""
+ }
+ SCHEMA_CONFIG = {
+ "type": "object",
+ "properties": {
+ "layout": {"type": "string"},
+ "filename": {"type": "string"},
+ "editor": {"type": "array"},
+ "template": {"type": "string"}
+ },
+ "required":[
+ "layout",
+ "filename",
+ "editor",
+ "template"
+ ]
+ }
+
+ def __init__(self, root):
+ self.pfile=os.path.join(root,self.FILE)
+ if "EDITOR" in os.environ:
+ self.DEFAULT_CONFIG["editor"]=[os.environ["EDITOR"]]
+ self.config=self.DEFAULT_CONFIG
+ self.load()
+
+ def load(self):
+ if os.path.exists(self.pfile):
+ with open(self.pfile) as f:
+ self.config=json.load(f)
+ try:
+ validate(instance=self.config, schema=self.SCHEMA_CONFIG)
+ except:
+ print("Invalid configuration file")
+ exit(1)
+ else:
+ self.save()
+
+ def save(self):
+ with open(self.pfile, "w") as f:
+ f.write(json.dumps(self.config,indent=4, sort_keys=True))
+
+ def __getitem__(self, key):
+ return self.config[key]
+
+ def __setitem__(self, key, value):
+ self.config[key]=value
+
+
+class Project:
+
+ def __init__(self, path):
+ self.paths={
+ "root": path,
+ "files": os.path.join(path,"files"),
+ "lockfile": os.path.join(path,"lockfile"),
+ }
+
+ if not os.path.exists(self.paths["root"]):
+ print("Creating project...")
+ Path(self.paths["root"]).mkdir(parents=True, exist_ok=True)
+ Path(self.paths["files"]).mkdir(parents=True, exist_ok=True)
+
+ self.conf=ProjectConfig(self.paths["root"])
+ self.metadata=Metadata(self.paths)
+ self.layout=Layout(self.conf,self.paths)
+
+ if os.path.exists(self.paths["lockfile"]):
+ print("Your project contains a lock file! Your project might be corrupted :(")
+ exit(1)
+
+ def lock(self):
+ open(self.paths["lockfile"], 'a').close()
+
+ def unlock(self):
+ os.remove(self.paths["lockfile"])
+
+ def create(self,subpath=None):
+ """
+ Create a today's note file (subpath=None) or create the metadata associated with the subpath passed in argument.
+ """
+ self.lock()
+ if subpath is None:
+ subpath=self.layout.create()
+ try:
+ self.metadata.create(subpath, self.layout.gettoday())
+ except sqlite3.IntegrityError:
+ print("The file you are trying to edit was deleted!")
+ answer=input("Do you want to use its old metadata [Y/n]? ")
+ if answer.lower() not in ["yes", "y", ""]:
+ self.metadata.delete(self.layout.todaysubpath())
+ self.metadata.create(subpath, self.layout.gettoday())
+ self.unlock()
+
+ def find(self, string, ignore_case=False):
+ """
+ Find all subpath that contains a specific string.
+ """
+ files=list()
+ for file in self.layout.flatten():
+ if string is None:
+ files.append(str(file))
+ elif ignore_case:
+ if string.lower() in file.name.lower():
+ files.append(str(file))
+ elif string in file.name:
+ files.append(str(file))
+ return files
+
+ def listlastcreated(self):
+ return self.metadata.flatten_ordered()
+
+ def listlastadded(self):
+ return self.metadata.flatten_ordered(ordby="added")
+
+ def getfileinfo(self,subpath, name):
+ """
+ Get a specific info (name argument) associated with a subpath.
+ """
+ return self.metadata.getfileinfo(subpath,name)
+
+ def grep(self, exp, ignore_case=False):
+ """
+ Search for a specific regex on every note file.
+ """
+ if ignore_case:
+ r=re.compile(exp,flags=re.IGNORECASE)
+ else:
+ r=re.compile(exp)
+ results=list()
+ for subpath in self.layout.flatten():
+ path=os.path.join(self.paths["files"],subpath)
+ lines=list()
+ with open(path, "r") as f:
+ ln=1
+ for line in f:
+ if r.search(line):
+ lines.append((ln,line.rstrip()))
+ ln+=1
+ if len(lines) > 0:
+ results.append((str(subpath),lines))
+ return results
+
+ def searchtag(self,tag):
+ """
+ Get all subpaths that have a specific tag.
+ """
+ return self.metadata.searchtag(tag)
+
+ def addtags(self, subpaths, tags):
+ """
+ Add tags to specific note files.
+ """
+ for subpath in subpaths:
+ for tag in tags:
+ self.metadata.addtag(subpath, tag)
+
+ def addtagslastedited(self, tags):
+ """
+ Add tags to the last edited note file.
+ """
+ subpath=self.metadata.getcache("last_edited")
+ if subpath is not None:
+ for tag in tags:
+ self.metadata.addtag(subpath, tag)
+ else:
+ print("You did not edit any files yet!")
+ exit(1)
+
+ def addfile(self,filepath,timestamp=None):
+ """
+ Add a custom file to the note files.
+ Timestamp can be specified or not!
+ """
+ if timestamp is not None:
+ self.layout.settoday(timestamp)
+ path=self.layout.todaypath()
+ ignore=False
+ if not os.path.exists(path):
+ self.create()
+ else:
+ print("The following subpath is already taken: "+self.layout.todaysubpath())
+ answer=""
+ while answer.lower() not in ["ignore","replace","append", "i", "r", "a"]:
+ answer=input("What do you want to do [Ignore/Replace/Append]? ")
+ if answer.lower() in ["ignore", "i"]:
+ ignore=True
+ elif answer.lower() in ["append", "a"]:
+ ignore=True
+ with open(filepath, "r") as src:
+ with open(path, "a") as dst:
+ for line in src:
+ dst.write(line)
+ if timestamp is not None:
+ self.layout.restoretoday()
+ if not ignore:
+ shutil.copyfile(filepath, path)
+
+ def addtagstoday(self,tags):
+ """
+ Add tags to today's note file
+ """
+ path=self.layout.todaypath()
+ subpath=self.layout.todaysubpath()
+ if not os.path.exists(path):
+ print("Today's file not created yet!")
+ exit(1)
+ else:
+ for tag in tags:
+ self.metadata.addtag(subpath, tag)
+
+ def listtags(self,subpath=None):
+ """
+ List all tags (subpath=None) or tags from a specific subpath
+ """
+ return self.metadata.listtags(subpath)
+
+ def deletetags(self, subpaths, tags):
+ """
+ Remove some tags linked to some subpaths
+ """
+ for subpath in subpaths:
+ for tag in tags:
+ self.metadata.deletetag(subpath, tag)
+
+ def obliteratetags(self, tags):
+ """
+ Remove all references of some tags from the metadata
+ """
+ for tag in tags:
+ self.metadata.obliteratetag(tag)
+
+ def getpath(self,subpath):
+ """
+ Get file path from the subpath
+ """
+ return os.path.join(self.paths["files"],subpath)
+
+ def fix(self, dry):
+ """
+ Fixing user's new and deleted not files
+ """
+ for f in self.layout.flatten():
+ path=self.getpath(str(f))
+ if os.path.isfile(path):
+ if os.stat(path).st_size == 0:
+ if dry:
+ print("Empty note file detected => "+f.name)
+ else:
+ print("Fixing empty note file => "+f.name)
+ self.metadata.delete(str(f))
+ os.remove(path)
+
+ self.metadata.fix_deleted(dry)
+ self.metadata.fix_new(self.layout,dry)
+
+ def apply_template(self):
+ """
+ Apply template to today's note file
+ """
+ template_path=os.path.join(self.paths["root"],self.conf["template"])
+ if os.path.isfile(template_path):
+ result = subprocess.run([template_path, self.layout.todaysubpath()], stdout=subprocess.PIPE)
+ with open(self.layout.todaypath(), "w") as f:
+ f.write(result.stdout.decode('utf-8'))
+
+ def opentoday(self):
+ """
+ Open today's note file
+ """
+ path=self.layout.todaypath()
+ if not os.path.exists(path):
+ self.create()
+ self.apply_template()
+ self.exec_editor(self.layout.todaysubpath())
+
+ def open(self,string):
+ """
+ Open a note file that contains a string
+ """
+ files=list()
+ for path in self.layout.flatten():
+ if string in path.name:
+ files.append(path)
+ if len(files) == 0:
+ path=self.getpath(string)
+ if not os.path.exists(path):
+ self.create(string)
+ self.apply_template()
+ self.exec_editor(string)
+ elif len(files) == 1:
+ self.exec_editor(files[0])
+ else:
+ print("Multiple file match:")
+ for path in files:
+ print(path.name)
+
+ def exec_editor(self, subpath):
+ """
+ Open note editor supplied by the user
+ """
+ self.metadata.setcache("last_edited",subpath)
+ path=self.getpath(subpath)
+ command=self.conf["editor"]+[path]
+ try:
+ os.execvp(command[0],command)
+ except:
+ print("Cannot open editor \"{}\"".format(self.conf["editor"]))
+ exit(1)
+
diff --git a/pnote/tools/__init__.py b/pnote/tools/__init__.py
new file mode 100644
index 0000000..20a6981
--- /dev/null
+++ b/pnote/tools/__init__.py
@@ -0,0 +1,3 @@
+from pnote.tools.search import *
+from pnote.tools.tag import *
+from pnote.tools.admin import *
diff --git a/pnote/tools/admin.py b/pnote/tools/admin.py
new file mode 100644
index 0000000..f3e03d5
--- /dev/null
+++ b/pnote/tools/admin.py
@@ -0,0 +1,48 @@
+from pnote.tools.tool import Tool
+import argparse
+from datetime import datetime
+
+class ToolAdmin(Tool):
+
+ def add_parser(self,subparsers):
+ self.p = subparsers.add_parser("admin", description="Manage your notes tags")
+ self.p.add_argument("--fix-dry", help="fix new and deleted note files (DRY RUN)", action='store_true')
+ self.p.add_argument("--fix", help="fix new and delete note files", action='store_true')
+ self.p.add_argument("--import", help="Import file(s) to notes", nargs="+", dest="imports")
+ self.p.add_argument("--timestamp", help="Timestamp to use for file(s) import")
+ self.p.add_argument("--file-infos", help="Get note file(s) infos", action='store_true')
+ self.p.add_argument("--subpath", help="")
+ self.p.add_argument("-s", "--subpaths", help="Subpath to use for file(s) infos", nargs="+")
+
+ def run(self, project, args):
+ if args.fix_dry:
+ project.fix(True)
+ elif args.fix:
+ project.fix(False)
+ elif args.imports:
+ if args.timestamp:
+ for f in args.imports:
+ project.addfile(f,int(args.timestamp))
+ else:
+ for f in args.imports:
+ project.addfile(f)
+ elif args.file_infos:
+ if args.subpaths:
+ subpaths=args.subpaths
+ else:
+ subpaths=project.find(None)
+ first=True
+ for subpath in subpaths:
+ if not first:
+ print()
+ print("=> "+subpath)
+ ts_created=project.getfileinfo(subpath,"created")
+ ts_added=project.getfileinfo(subpath,"added")
+ print("Created on: "+str(datetime.fromtimestamp(int(ts_created))))
+ print("Added on: "+str(datetime.fromtimestamp(int(ts_added))))
+ print("Added with host: "+str(project.getfileinfo(subpath,"hostname")))
+ print("Added host infos: "+str(project.getfileinfo(subpath,"platform")))
+ print("Tags: "+str(project.listtags(subpath)))
+ first=False
+ else:
+ self.p.print_help()
diff --git a/pnote/tools/search.py b/pnote/tools/search.py
new file mode 100644
index 0000000..298010b
--- /dev/null
+++ b/pnote/tools/search.py
@@ -0,0 +1,79 @@
+from pnote.tools.tool import Tool
+import argparse
+
+class ToolSearch(Tool):
+
+ def add_parser(self,subparsers):
+ p = subparsers.add_parser("search", description="Perform search operation on your notes")
+ p.add_argument("-g", "--grep", help="Grep an expression")
+ p.add_argument("-n", "--name", help="Search for a note path")
+ p.add_argument("-i", "--ignore-case", help="Ignore case during search", action='store_true')
+ p.add_argument("-t", "--tag", help="Search for a note with a tag")
+ p.add_argument("-c", "--content-only", help="Show content only", action='store_true')
+ p.add_argument("-s", "--subpath-only", help="Show file subpath only", action='store_true')
+ p.add_argument("--last-created", help="Get last n created note files")
+ p.add_argument("--last-added", help="Get last n added note files")
+
+ def catsubpath(self,project,subpath):
+ with open(project.getpath(subpath),"r") as fp:
+ for line in fp:
+ print(line,end="")
+
+ def catsubpaths(self, project, subpaths, content_only=False, subpath_only=False):
+ first=True
+ for subpath in subpaths:
+ if subpath_only:
+ print(subpath)
+ continue
+ if not content_only:
+ if not first:
+ print()
+ print("=> "+subpath)
+ self.catsubpath(project,subpath)
+ first=False
+
+ def run(self, project, args):
+ ignore_case=True if args.ignore_case else False
+ content_only=True if args.content_only else False
+ subpath_only=True if args.subpath_only else False
+
+ if content_only and subpath_only:
+ print("content and file-path options cannot be used at the same time")
+ exit(1)
+ if args.grep:
+ first=True
+ for entry in project.grep(args.grep, ignore_case):
+ subpath=entry[0]
+ if subpath_only:
+ print(subpath)
+ continue
+ if not content_only:
+ if not first:
+ print()
+ print("=> "+subpath)
+ for line in entry[1]:
+ ln=line[0]
+ content=line[1]
+ if content_only:
+ print(content)
+ else:
+ print("L{}: {}".format(ln,content))
+ first=False
+
+ elif args.tag:
+ self.catsubpaths(project, project.searchtag(args.tag),content_only,subpath_only)
+
+ elif args.last_created:
+ subpaths=project.listlastcreated()
+ self.catsubpaths(project, subpaths[-abs(int(args.last_created)):],content_only,subpath_only)
+
+ elif args.last_added:
+ subpaths=project.listlastadded()
+ self.catsubpaths(project, subpaths[-abs(int(args.last_added)):],content_only,subpath_only)
+
+ else:
+ if args.name:
+ self.catsubpaths(project, project.find(args.name,ignore_case),content_only,subpath_only)
+ else:
+ self.catsubpaths(project, project.find(None),content_only,subpath_only)
+
diff --git a/pnote/tools/tag.py b/pnote/tools/tag.py
new file mode 100644
index 0000000..e45b8c8
--- /dev/null
+++ b/pnote/tools/tag.py
@@ -0,0 +1,33 @@
+from pnote.tools.tool import Tool
+import argparse
+
+class ToolTag(Tool):
+
+ def add_parser(self,subparsers):
+ p = subparsers.add_parser("tag", description="Manage your notes tags")
+ p.add_argument("-s", "--subpaths", help="Subpaths to edit", nargs="+")
+ p.add_argument("-a", "--add", help="Add tags to notes", nargs="+")
+ p.add_argument("-d", "--delete", help="Delete tags from notes", nargs="+")
+ p.add_argument('-l', '--last-edited', help="Tag last edited file", action="store_true")
+
+ def run(self, project, args):
+ if args.subpaths:
+ if args.add:
+ project.addtags(args.subpaths,args.add)
+ elif args.delete:
+ project.deletetags(args.subpaths,args.delete)
+ else:
+ for subpath in args.subpaths:
+ for tag in project.listtags(subpath):
+ print(tag)
+ else:
+ if args.delete:
+ project.obliteratetags(args.delete)
+ elif args.add:
+ if args.last_edited:
+ project.addtagslastedited(args.add)
+ else:
+ project.addtagstoday(args.add)
+ else:
+ for tag in project.listtags():
+ print(tag)
diff --git a/pnote/tools/tool.py b/pnote/tools/tool.py
new file mode 100644
index 0000000..5aa7901
--- /dev/null
+++ b/pnote/tools/tool.py
@@ -0,0 +1,8 @@
+
+class Tool:
+
+ def add_parser(self,subparsers):
+ pass
+
+ def run(self, project, args):
+ pass