diff options
Diffstat (limited to 'pnote/project.py')
| -rw-r--r-- | pnote/project.py | 322 |
1 files changed, 322 insertions, 0 deletions
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) + |
