vcs: Fix writing module structure

This commit is contained in:
Marty Oehme 2024-09-19 11:18:54 +02:00
parent fee876b87f
commit c14710aa98
Signed by: Marty
GPG key ID: EDBF2ED917B2EF6A
15 changed files with 7 additions and 7 deletions

View file

@ -0,0 +1,3 @@
# from papis import config
#
# config.set("ref-format", f"{doc[author].split()[0] if 'author' in doc else 'Unknown'}{doc[year] if 'year' in doc else '0000'}")

View file

@ -0,0 +1,3 @@
{doc.html_escape[title]}
<ansigreen>{doc.html_escape[author]}</ansigreen>
<ansiblue>({doc.html_escape[year]})</ansiblue> [<ansiyellow>{doc.html_escape[tags]}</ansiyellow>] [<ansired>{doc.html_escape[readstatus]}</ansired>]

View file

@ -0,0 +1,203 @@
# current issues/requests
# - [ ] call arbitrary shell command
# - [ ] non-blocking opening of files
# - [ ] distinguish in config between aliases for multiline/table
# - [ ] update view after more operations
# - if I call an 'update' command (e.g. set new tag or read status), it will not update in list until 'edit' command invoked and rebuilding the list
# - two selection additions:
# - [ ] select all in view
# - [ ] limit view to currently selected
#
# for papis itself:
# - [ ] I can not search custom numeric fields in whoosh? i.e. 'priority:1' will not give results. 'priority:high' will
base:
vimflavour: nvim
documentlist:
defaultstyle: multiline
marked-icon: "󰁔"
multilinestyle:
rows:
# TODO: show if already note attached; have info window display note content?
- "{' ' if 'priority' not in doc or doc['priority'] == 'low' else '<red>󱝇 </red>' if doc['priority'] == 'high' else '<yellow>󱝇 </yellow>'}<cyan>{doc.alias('type')} {doc['ref']}</cyan> {doc.alias('readstatus')} {' 󰘓 ' if doc['notes'] else ''}"
- "<white><bold>{doc.html_escape['title']}</bold></white>"
- "<blue>{doc.html_escape['author']}</blue>"
- "{doc.foreach('tags', '<lightgray>(</lightgray><gray><dim>{}</dim></gray><lightgray>)</lightgray>', split = ', ', sep = ' ')}"
tablestyle:
separator: " "
headerstyle: "underline|bold"
rowstyle: "white_bg"
cursorrowstyle: "black_white|bold"
columns:
- {
header: "󱝇",
content: "{'' if 'priority' not in doc or doc['priority'] == 'low' else '󰝥' if doc['priority'] == 'high' else '󰝦'}",
width: 1,
}
- {
header: "󰑇",
content: "{'󰗠' if doc['readstatus'] == 'read' else '󰗡' if doc['readstatus'] == 'skimmed' else ' '}",
width: 1,
}
- {
header: "",
content: "{'󰎚' if doc['notes'] else '󰎛' if doc['note'] else ' '}",
width: 1,
}
- {
header: "󰉺",
content: "{doc.alias('type')}",
width: 1,
}
- { header: "Authors", content: "{doc['author']}", width: 25 }
- { header: "Year", content: "{str(doc['year'])}", width: 4 }
- { header: "Title", content: "{doc['title']}", width: 125 }
- { header: "Reference", content: "{doc['ref']}", width: 15 }
- {
header: "󰘓",
content: "{str(len(doc.get_files()) if len(doc.get_files()) > 0 else '')}",
width: 1,
}
- { header: "Tags", content: "{doc['tags'] if isinstance(doc['tags'], str) else doc.foreach('tags', '{}', sep=', ')}", width: 35 }
aliases:
type:
{
article: "",
book: "",
inbook: "",
incollection: "󰪧",
software: "󰅩",
presentation: "󰐨",
thesis: "",
techreport: "󰁰",
_default_: "",
}
readstatus:
{
read: "<green>󰗠</green>",
skimmed: "<yellow>󰗡</yellow>"
}
keymappings:
q: quit
"?": help
T: toggle_style
S: cmd "sort "
/: search_mode
<key_down>: scroll_down
<key_up>: scroll_up
<ctrl-f>: page_down
<ctrl-b>: page_up
G: jump_to_bottom
gg: jump_to_top
j: scroll_down
k: scroll_up
o: open -r "pdf$"
O: open -d
b: browse
B: papis browse -k doi papis_id:{doc['papis_id']}
R: view_reset
<c-r>: reload
e:
- edit
- edit info
n:
- papis edit -n papis_id:{doc['papis_id']}
- edit notes
"'n":
- search "notes:.+"
- limit to entries with notes
"'u":
- search "readstatus:read OR readstatus:skimmed"
- limit to read entries
"'r":
- search "NOT readstatus:read AND NOT readstatus:skimmed"
- limit to unread entries
" ": mark_selected
mm: mark_selected
M: mark_down
J: mark_down
mu: unmark_all
mv: mark_view
",t": cmd "tag "
",r":
- papis update -s readstatus read papis_id:{doc['papis_id']}
- set readstatus read
",k":
- papis update -s readstatus skimmed papis_id:{doc['papis_id']}
- set readstatus skimmed
",u":
- papis update -s readstatus "" papis_id:{doc['papis_id']}
- set readstatus unread
",ph":
- papis update -s priority high papis_id:{doc['papis_id']}
- set priority high
",pm":
- papis update -s priority medium read papis_id:{doc['papis_id']}
- set priority medium
",pl":
- papis update -s priority low read papis_id:{doc['papis_id']}
- set priority low
i:
- info_toggle
- "Toggle info window"
I:
- info_cycle
- "Cycle info windows"
<ctrl-p>: info_scroll_up
<ctrl-n>: info_scroll_down
# all require 'clip' script to be available on PATH
yy:
- copy_to_clipboard "[@{doc['ref']}]"
- yank pandoc-styled reference
ss:
- vim_send "[@{doc['ref']}]"
- send vim pandoc-styled reference
yl:
- copy_to_clipboard "\\cite\{{doc['ref']}\}"
- yank latex-styled reference
sl:
- vim_send "\\cite\{{doc['ref']}\}"
- send vim latex-styled reference
yr:
- copy_to_clipboard "{format_reference(doc,style='apa')}"
- yank apa-styled reference
sr:
- vim_send "{format_reference(doc,style='apa')}"
- send vim apa-styled reference
yt:
- copy_to_clipboard "{doc['title']}"
- yank title
yu:
- copy_to_clipboard "{doc['url']}"
- yank url
yd:
- copy_to_clipboard "{doc['doi']}"
- yank doi
# TODO look into https://github.com/supersambo/papis-tui vim-send mappings
infowindow:
default_on: False
views:
doc:
content: "author: {doc['author'].strip()}\n title: {doc['title'].strip()}\n tags:{doc['tags'] if isinstance(doc['tags'], str) else doc.foreach('tags', '{}', sep=', ')}"
height: 8
apa:
content: "{format_reference(doc,style='apa')}"
abstract:
content: "{doc['abstract']}"
linewrap: True
height: 8
commandline:
search:
keyword_aliases: { a: "author:", t: "title:", y: "year:", k: "tags:" }
statusbar:
left:
default: "<black_green><bold> {info['mode_upper']} </black_green></bold><green_bg></green_bg>"
normal: "<black_green><bold> {info['mode_upper']} </black_green></bold><green_bg></green_bg>"
command: "<black_cyan><bold> {info['mode_upper']} </black_cyan></bold><cyan_bg></cyan_bg>"
select: "<black_red><bold> {info['mode_upper']} </black_red></bold><red_bg></red_bg>"
search: "<black_magenta><bold> {info['mode_upper']} </black_magenta></bold><magenta_bg></magenta_bg>"
right:
default: "<green>{info['sortkeys']} </green><cyan_bg></cyan_bg><black_cyan> {info['idx']} < {info['marked']} < {info['view']} < {info['items']} </black_cyan>"

View file

@ -0,0 +1,163 @@
#!/usr/bin/env python
# papis-short-help: Import iOS Marvin exported csv annotations
#
# This script can be used to import your highlights and notes from
# the iOS application 'Marvin Reader'. In the app, export your
# annotations as 'csv' format and then point the script to the
# resulting file.
# https://git.martyoeh.me/Marty/papis-marvin
import os
import sys
import re
import logging
from typing import Dict
import papis.api
import papis.pick
import papis.format
import papis.commands.edit
import papis.commands.list
import papis.commands.add
import papis.notes
import papis.config
import papis.database
import isbnlib
import papis.isbn
logger = logging.getLogger("marvin")
logger.setLevel(logging.DEBUG)
DEFAULT_CSV_PATH = "/home/marty/Nextcloud/Personal/Backups/Journal.csv"
def main(fpath, db):
with open(fpath) as f:
import csv
csv = csv.DictReader(f)
notes = get_all_annotations(db, csv)
write_to_files(notes)
def get_all_annotations(db, csv) -> Dict:
notes = {}
note_file = ""
for row in csv:
# switch to next book
if not is_same_book(row["Title"]):
doc = get_document(db, row["Author"], row["Title"])
if not doc:
continue
note_file = get_notefile(db, doc)
text = format_entry(row)
if note_file and text:
if note_file not in notes.keys():
notes[note_file] = []
notes[note_file].append(text)
return notes
def get_document(db, author, title):
res = query_document(db, author, title)
if not res:
add_to_database(author, title)
res = query_document(db, author, title)
if not res:
logger.warning(f"Nothing found for {author}: {title}.\nPlease create manually.")
return
return res
# TODO warn user/ let him pick with picker if multiple docs found
def query_document(db, author, title):
title = strip_string(title)
for query in [f"author:({author}) title:({title})"]:
print(f"query: {query}")
res = db.query(query)
if len(res) >= 1:
return res[0]
def add_to_database(author, title, confirm=True, edit=False):
logger.info(f"Searching - '{title} {author}'")
data = None
try:
data = papis.isbn.get_data(f"{title}")
except isbnlib.ISBNLibException as e:
logger.error(e)
else:
logger.warning(f"Found: {data}")
if data:
papis_data = papis.isbn.data_to_papis(data[0])
papis.commands.add.run([], data=papis_data, confirm=confirm, edit=edit)
def get_notefile(db, document) -> str | None:
if not document.has("notes"):
notes_name = papis.config.getstring("notes-name")
document["notes"] = papis.format.format(notes_name, document)
document.save()
db.update(document)
notes_path = os.path.join(str(document.get_main_folder()), document["notes"])
if not os.path.exists(notes_path):
# TODO reimplement logger: logger.debug("Creating '%s'", notes_path)
papis.notes.notes_path_ensured(document)
return notes_path
# TODO implement custom formatting (akin to pubs-extract)
def format_entry(row) -> str:
text = f"> {row['HighlightText']}"
if row["EntryText"]:
if text:
text += "\n"
else:
text = "> "
text += f"{row['EntryText']}"
return text
_old_title = ""
def is_same_book(title):
global _old_title
same = _old_title == title
_old_title = title
if same:
return True
return False
def write_to_files(notes: Dict):
# write to notes
for f, entries in notes.items():
if f:
with open(f, "a") as note:
logger.info(f"Editing {f}...")
num_added = 0
for entry in entries:
with open(f) as noteread:
if entry not in noteread.read():
note.write(f"{entry}\n\n")
num_added += 1
logger.info(f"Added {num_added} entries to it.")
strip_pattern = re.compile(r"([^\s\w]|_)+\w*")
def strip_string(title) -> str:
return strip_pattern.sub("", title)
if __name__ == "__main__":
# use argument passed to command as file or default file here
fpath = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_CSV_PATH
main(fpath, papis.database.get())

View file

@ -0,0 +1,28 @@
#!/usr/bin/env python
# papis-short-help: Manually rebuild cache for all libraries
#
# This tiny script updates all libraries by rebuilding their caches.
# Useful to invoke after manual edits in one of your library folders
# if you have many 'sub-libraries' (one library location with sub-
# directories).
# You don't have to think about which library you changed stuff in
# and just get everything updated. Might take a little time but
# should generally be a quick process ().
import papis.api
from argparse import ArgumentParser
parser = ArgumentParser()
_ = parser.add_argument(
"--all", "-a", help="reload all libraries not just current", action="store_true"
)
args = parser.parse_args()
if args.all:
libs = papis.api.get_libraries()
else:
libs = [papis.api.get_lib_name()]
for lib in libs:
papis.api.clear_lib_cache(lib)
_ = papis.api.get_all_documents_in_lib()

View file

@ -0,0 +1,116 @@
#!/usr/bin/env python
# papis-short-help: Display pretty human-readable document overview
#
# Takes a query and displays the metadata of the results. Uses
# python-rich to display pretty panels if it exists in the environment,
# otherwise displays regular text.
# Can be invoked with -s to display single-line results better for
# pasting into other documents.
import argparse
from importlib.util import find_spec
from papis import database
from papis.database.base import Database
from papis.document import Document
from dataclasses import dataclass, field
parser = argparse.ArgumentParser()
parser.add_argument(
"--short", "-s", help="only display single-line quick info", action="store_true"
)
parser.add_argument(
"--url",
dest="identifier",
help="display identifier (doi, isbn, url) or not",
action=argparse.BooleanOptionalAction,
default=True,
)
parser.add_argument("query", nargs="*", help="the query to search for")
args = parser.parse_args()
@dataclass
class DocInfo:
author: str = ""
title: str = ""
year: str = ""
journal: str = ""
volume: str = ""
issue: str = ""
pages: str = ""
publisher: str = ""
tags: list = field(default_factory=lambda: [])
dtype: str = ""
identifier: str = ""
doi: str = ""
@staticmethod
def from_Document(doc: Document):
t: list[str] | str = doc.get("tags", "")
tags = (
t.replace(";", ",").replace(" ", "").split(",") if isinstance(t, str) else t
)
return DocInfo(
author=doc.get("author", ""),
title=doc.get("title", ""),
year=doc.get("year", ""),
journal=doc.get("journal", ""),
volume=doc.get("volume", ""),
issue=doc.get("issue", ""),
pages=doc.get("pages", ""),
publisher=doc.get("publisher", ""),
tags=tags,
dtype=doc.get("type", ""),
identifier=doc.get("doi", doc.get("isbn", doc.get("url", ""))),
)
def main(db: Database, args) -> None:
query = " ".join(args.query)
docs: list[Document] = db.query(query)
for doc in docs:
info = DocInfo.from_Document(doc)
if args.short:
print_short(info, with_identifier=args.identifier)
else:
print_info(info, with_identifier=args.identifier)
def print_short(doc: DocInfo, with_identifier: bool = True) -> None:
print(
f"{doc.author} ({doc.year}). {doc.title}. "
f"{doc.identifier if with_identifier else ''}\n"
)
def print_info(doc: DocInfo, with_identifier: bool = True) -> None:
if find_spec("rich"):
from rich import print as richprint
from rich.panel import Panel
info: str = (
f"[red]{doc.author}[/red] ({doc.year}) "
f"[steel_blue]\\[{doc.dtype}][/steel_blue]\n"
f"[bold]{doc.title}[/bold]\n"
f"{doc.journal} ({doc.volume}/{doc.issue}){doc.pages} - {doc.publisher}\n"
f"[grey69]{[tag for tag in doc.tags]} "
)
if with_identifier:
info += f"[link={doc.identifier}]{doc.identifier}[/link][/grey69]"
richprint(Panel(info, expand=False))
else:
info: str = (
f"{doc.author} ({doc.year}) "
f"[{doc.dtype}]\n"
f"{doc.title}\n"
f"{doc.journal} ({doc.volume}/{doc.issue}){doc.pages} - {doc.publisher}\n"
f"{[tag for tag in doc.tags]} "
f"{doc.identifier}"
if with_identifier
else ""
)
print(f"{info}\n---")
if __name__ == "__main__":
main(database.get(), args)

View file

@ -0,0 +1,57 @@
#!/usr/bin/env python
# papis-short-help: List all tags occuring in query items
#
# Takes a query and spits out a sorted list of all tags contained therein,
# nothing more.
# Can be very useful for things like picking a tag or two and listing all
# items that contain it:
# $ papis tags "*" | fzf | xargs papis show "tags:{}"
import argparse
from papis import database
from papis.database.base import Database
from papis.document import Document
parser = argparse.ArgumentParser()
parser.add_argument(
"--count", "-c", help="the query to search for", action="store_true"
)
parser.add_argument("query", nargs="*", help="the query to search for", default="*")
args = parser.parse_args()
def main(db: Database, args) -> None:
query = " ".join(args.query)
docs: list[Document] = db.query(query)
all_tags: dict[str, int] = {}
for doc in docs:
t: list[str] | str = doc.get("tags", "")
tags = (
t.replace(";", ",").replace(" ", "").split(",") if isinstance(t, str) else t
)
for tag in tags:
if tag == '':
continue
all_tags[tag] = all_tags.get(tag, 0) + 1
if args.count:
print_tags_and_counts(all_tags)
else:
print_tags_only(all_tags)
def print_tags_only(all_tags):
for tag in sorted(all_tags):
print(tag)
def print_tags_and_counts(all_tags):
for tag, count in sorted(all_tags.items(), key=lambda d: d[1], reverse=True):
if args.count:
print(tag, count)
else:
print(tag)
if __name__ == "__main__":
main(database.get(), args)