From d1b829f647b7bae5febc9e0c374f53cd2b876ea6 Mon Sep 17 00:00:00 2001 From: randogoth Date: Sat, 10 Jun 2023 16:56:04 +0300 Subject: [PATCH] bugfixes --- context.txt | 18 +++++++++ .context.yaml => context.yaml | 3 +- orvani/cmd.py | 6 +++ orvani/orvani.py | 39 ++++++++++++++++--- orvani/plugins/hashdoubleslash.py | 15 +++---- orvani/plugins/id3.py | 7 +--- orvani/plugins/office.py | 36 +++++++---------- orvani/plugins/pdf.py | 2 +- test/.context.yaml | 6 +++ test/folder with spaces/file with spaces.txt | 3 ++ test/office.odt | Bin 8958 -> 9200 bytes 11 files changed, 94 insertions(+), 41 deletions(-) create mode 100644 context.txt rename .context.yaml => context.yaml (93%) create mode 100644 test/folder with spaces/file with spaces.txt diff --git a/context.txt b/context.txt new file mode 100644 index 0000000..f1a81e3 --- /dev/null +++ b/context.txt @@ -0,0 +1,18 @@ +./README.md --- @frrreee #fool #writeme +./TODO.md --- +bokertov @python #rusty #frrr Chawera +./requirements.txt --- pip reqs +./test --- @folder #spaces +./COMMANDS.md --- +coding @erfwegf #super #great This is awesome +./test/folder with spaces --- +totally #spaces +./test/office.odt --- +oopsie +orvani @this #whoa #that and a note +./test/sheet.xlsx --- +orvani @calc #tessssting +./test/sub --- @folder +./test/test.txt --- +test @test #faa #test frrr +./test/table.ods --- @calc #tessssting +./test/tableold.xls --- @calc #tessssting +./test/chirpy.wav --- +sensorwatch +parakeetio @Chripo @watch @Chripo McFlu #funk McFlu McFlu This is a title This is a title This is a title +./test/oldword.doc --- and a note +./test/word.docx --- @concon #tagz +./test/sub/statement_14793553_USD_2022-11-01_2023-01-31.pdf --- +yeeha @pdfcontext #yay #frrrrrrrr And a comment too +./test/folder with spaces/file with spaces.txt --- #spaces +./orvani/plugins/hashdoubleslash.py --- +orvani @python #example It even works in Python code diff --git a/.context.yaml b/context.yaml similarity index 93% rename from .context.yaml rename to context.yaml index 9690647..b893c3b 100644 --- a/.context.yaml +++ b/context.yaml @@ -21,4 +21,5 @@ items: contexts: - folder projects: [] - tags: [] + tags: + - spaces diff --git a/orvani/cmd.py b/orvani/cmd.py index 2c5e2fc..b511060 100644 --- a/orvani/cmd.py +++ b/orvani/cmd.py @@ -152,5 +152,11 @@ def main(): if args.file and args.context: orvani.write(args.context[0], args.file) + elif args.command in ['export']: + orvani.export(args.file) + + elif args.command in ['import']: + orvani.load(args.file) + if __name__ == '__main__': main() diff --git a/orvani/orvani.py b/orvani/orvani.py index 19f7ece..4751157 100644 --- a/orvani/orvani.py +++ b/orvani/orvani.py @@ -1,4 +1,5 @@ import os +import re import yaml import shutil from pathlib import Path @@ -37,6 +38,7 @@ class OrvaniTag: CTX = {} ENABLE_PLUGINS = True PLUGINS = [] + REGEX = r'\s*([@+]\w+|#\w+)' def __init__(self, path: str=CONTEXT_FILE, cache: str=CACHE_FILE, contexts=CONTEXTS, plugins:list=DEFAULT_PLUGINS): """ @@ -121,7 +123,7 @@ class OrvaniTag: for context_key in self.CTX.keys(): if self.CTX[context_key] != None: context.update( { context_key : []}) - for arg in argv: + for arg in [x for x in argv if len(x) > 0]: arg.replace(',', '') for context_key in self.CTX.keys(): if str(arg)[0] in self.CTX.values(): @@ -427,12 +429,20 @@ class OrvaniTag: self.PLUGINS.append(function) return function + def get_argv(self, context_string: str): + tags = re.findall(self.REGEX, context_string) + note = re.sub(self.REGEX, '', context_string).lstrip() + argv = ' '.join(tags).split(' ') + [note] + argv = [x for x in list(set(argv)) if x != ''] + return argv + def __metadata(self, filename: str): obj = { 'file' : Path(), 'config' : self.CONFIG, 'schema' : self.CTX, 'context' : self.__context, + 'string' : self.get_argv } o = SimpleNamespace(**obj) for plugin_name in self.PLUGINS: @@ -457,7 +467,7 @@ class OrvaniTag: context_keys = list(CONTEXTS.keys()) for context_key in context_keys: if not context_key in context: - context[context_key] = None + context[context_key] = '' if context[context_key]: if CONTEXTS[context_key]: prefix = CONTEXTS[context_key] @@ -467,8 +477,6 @@ class OrvaniTag: elements.append(' '.join([prefix + tag for tag in context[context_key]])) else: elements.append(context[context_key]) - else: - elements.append(None) return elements def write(self, filename: str, method: str): @@ -477,7 +485,8 @@ class OrvaniTag: 'config' : self.CONFIG, 'schema' : self.CTX, 'context' : {}, - 'collect' : self.__collect_contexts + 'collect' : self.__collect_contexts, + 'string' : self.get_argv } o = SimpleNamespace(**obj) for plugin_name in self.PLUGINS: @@ -500,3 +509,23 @@ class OrvaniTag: if hasattr(plugin, 'writer'): plugin.writer(o) + def export(self, filename:str): + if filename == '' or filename == None: + filename = 'context.txt' + file = Path(filename) + index = self.list(file.parent, False, False, True, False, False, '') + items = index['index']['items'] + content = "" + for path in items: + string = ' '.join([x for x in self.__collect_contexts(items[path]) if x != None]) + content += f'{path} --- {string}\n' + file.write_text(content) + + def load(self, filename:str): + if filename == '' or filename == None: + filename = 'context.txt' + file = Path(filename) + imported = file.read_text() + known = self.list(file.parent, False, False, True, False, False, '')['index']['items'] + print(known) + diff --git a/orvani/plugins/hashdoubleslash.py b/orvani/plugins/hashdoubleslash.py index 1ca1f16..28e8130 100644 --- a/orvani/plugins/hashdoubleslash.py +++ b/orvani/plugins/hashdoubleslash.py @@ -37,19 +37,20 @@ def reader(o): if line_num > 5: break if line.strip().startswith("#// "): - tags = re.findall(REGEX, line.strip()[3:]) - note = re.sub(REGEX, '', line.strip()[3:]).lstrip() - argv = ' '.join(tags).split(' ') + [note] + argv = o.string(line.strip()[3:]) context = o.context(argv) return context def writer(o): - string = PREFIX + ' '.join([x for x in o.collect(o.context) if x != None]) + '\n' + string = PREFIX + ' '.join(o.collect(o.context)) + '\n' with o.file.open() as data: content = data.readlines() - for num, line in enumerate(content): - if line.strip().startswith("#// ") or num > 3: - break + num = 0 + line = '' + if len(content): + for num, line in enumerate(content): + if line.strip().startswith("#// ") or num > 3: + break if not num > 3 and line.strip().startswith("#// "): content[num] = string with o.file.open('w') as file: diff --git a/orvani/plugins/id3.py b/orvani/plugins/id3.py index 0bd95ca..34e9485 100644 --- a/orvani/plugins/id3.py +++ b/orvani/plugins/id3.py @@ -23,10 +23,7 @@ def reader(o): id3map[context_key] = metadata[ctag] if 'COMMENT' in metadata: comment = metadata['COMMENT'] - tags = re.findall(REGEX, comment[0].strip()) - note = re.sub(REGEX, '', comment[0].strip()).lstrip() - argv = ' '.join(tags).split(' ') + [note] - argv = [x for x in argv if x != ''] + argv = o.string(comment[0].strip()) context = o.context(argv) if not universal in context: context[universal] = [] @@ -39,6 +36,6 @@ def reader(o): return context def writer(o): - string = ' '.join([x for x in o.collect(o.context) if x != None]) + string = ' '.join(o.collect(o.context)) with taglib.File(o.file, save_on_exit=True) as song: song.tags['COMMENT'] = [string] \ No newline at end of file diff --git a/orvani/plugins/office.py b/orvani/plugins/office.py index 81fdcfa..3f1d255 100644 --- a/orvani/plugins/office.py +++ b/orvani/plugins/office.py @@ -7,9 +7,8 @@ from olefile import OleFileIO NAME = 'office' TYPES = ['.doc', '.xls', '.ppt', '.docx', '.xlsx', '.pptx', '.odt', '.ods', '.odp'] -RE_ODT_CTX = r"(?P
)(?P.*?)(?P<\/meta:keyword>)"
-RE_DOCX_CTX = r"(?P
)(?P.*?)(?P<\/cp:keywords>)"
-REGEX = r"(?P
)(?P.*?)(?P<\/dc:description>)"
+XML_REGEX = r"(?P
)(?P.*?)(?P<\/dc:description>)"
+REGEX = r'\s*([@+]\w+|#\w+)'
 
 # Office Document processing inspired by
 # https://github.com/kappapiana/anonymize/blob/master/anonymize.py
@@ -30,16 +29,12 @@ def unzip_file(orig_file, tmp_dir):
     else:
         return type[0]
 
-def get_context(meta_file, re_ctx):
-    tags = note = []
+def get_context_string(meta_file):
+    ctx = ''
     meta_content = meta_file.read_text()
-    tagsearch = re.search(re_ctx, meta_content)
-    if tagsearch:
-        tags = tagsearch.group('body').split(' ')
-    notesearch = re.search(REGEX, meta_content)
-    if notesearch:
-        note = [notesearch.group('body')]
-    return [x for x in ' '.join(tags).split(' ') + note if x != '']
+    csearch = re.search(XML_REGEX, meta_content)
+    if csearch:
+        return csearch.group('body')
     
 
 def reader(o):
@@ -50,7 +45,7 @@ def reader(o):
     # OpenOffice / LibreOffice / Open Document Format
     if file_type in ['odt', 'ods', 'odp']:
         meta_file = tmp_folder / 'meta.xml'
-        argv = get_context(meta_file, RE_ODT_CTX)
+        argv = o.string(get_context_string(meta_file))
         context = o.context(argv)
         shutil.rmtree(tmp_folder)
         return context
@@ -58,7 +53,7 @@ def reader(o):
     # Microsoft Office Format
     elif file_type in ['docx', 'xlsx', 'pptx']:
         meta_file = tmp_folder / 'docProps' / 'core.xml'
-        argv = get_context(meta_file, RE_DOCX_CTX)
+        argv = o.string(get_context_string(meta_file))
         context = o.context(argv)
         shutil.rmtree(tmp_folder)
         return context
@@ -68,20 +63,17 @@ def reader(o):
         ole = OleFileIO(o.file)
         meta = ole.get_metadata()
         ole.close()
-        if meta.keywords:
-            tags = meta.keywords.decode('UTF-8').split(' ')
         if meta.comments:
-            note = [meta.comments.decode('UTF-8')]
-        argv = [x for x in tags + note if x != '']
-        context = o.context(argv)
-        return context
+            argv = o.string(meta.comments.decode('UTF-8'))
+            context = o.context(argv)
+            return context
     
 
 def writer(o):
-    string = ' '.join([x for x in o.collect(o.context) if x != None])
+    string = ' '.join(o.collect(o.context))
     tmp_folder = Path('unzip__' + o.file.stem)
     file_type = unzip_file(o.file, tmp_folder)
-    regex = re.compile(REGEX)
+    regex = re.compile(XML_REGEX)
     string = f"\\g
{string}\\g"
 
     # OpenOffice / LibreOffice / Open Document Format
diff --git a/orvani/plugins/pdf.py b/orvani/plugins/pdf.py
index 3972601..1b30884 100644
--- a/orvani/plugins/pdf.py
+++ b/orvani/plugins/pdf.py
@@ -18,7 +18,7 @@ def reader(o):
         return context
     
 def writer(o):
-    string = ' '.join([x for x in o.collect(o.context) if x != None])
+    string = ' '.join(o.collect(o.context))
     pdf = PdfReader(o.file)
     pdf.Info.Keywords = string
     PdfWriter(o.file, trailer=pdf).write()
diff --git a/test/.context.yaml b/test/.context.yaml
index 77d5e61..40f4da9 100644
--- a/test/.context.yaml
+++ b/test/.context.yaml
@@ -6,6 +6,12 @@ config:
       projects: album
       tags: genre
 items:
+  folder with spaces:
+    contexts: []
+    projects:
+    - totally
+    tags:
+    - spaces
   office.odt:
     contexts: []
     projects:
diff --git a/test/folder with spaces/file with spaces.txt b/test/folder with spaces/file with spaces.txt
new file mode 100644
index 0000000..56aacb0
--- /dev/null
+++ b/test/folder with spaces/file with spaces.txt	
@@ -0,0 +1,3 @@
+---
+#// #spaces
+---
diff --git a/test/office.odt b/test/office.odt
index 350910bb9c18176ecc271a3ebc96f9bcec31589a..01eb5e983c307277a14eeac3e62b459b497a4a7a 100644
GIT binary patch
delta 6890
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
YlNl8xMOeWRE65McW%YM%{tx+G
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