Compare commits

...

2 Commits

Author SHA1 Message Date
482d443314
util.py: implemented rofi wrapper 2023-07-23 18:20:34 -04:00
5b8742f7c0
restructure things
preparing to add features
2023-07-22 14:13:46 -04:00
6 changed files with 172 additions and 39 deletions

View File

@ -1,12 +1,15 @@
#!/usr/bin/env python3
from pdf_data import get_metadata_pdf
from pdf_data import get_page_pdf
from datatypes import *
from enum import Enum, auto
from os import environ
from urllib.parse import urlencode
import subprocess
import pydbus
import argparse
import formatter.typst as typst_fmt
parser = argparse.ArgumentParser()
parser.add_argument("--section", help="Copy reference to the section title instead of the page number.", action="store_true")
class LinkFormat(Enum):
@ -20,33 +23,11 @@ def clip_copy(txt: str):
raise Exception("Please install `xsel`.") from e
def format_typst(ref: Reference) -> str:
path_str = environ.get("TYPST_ROOT", None)
if path_str is None:
raise KeyError("Please set TYPST_ROOT to format links with Typst.")
typst_root = Path(path_str)
relative: bool = ref.filepath.is_relative_to(typst_root)
format_path: str
if relative:
format_path = "/" + str(ref.filepath.relative_to(typst_root))
else:
format_path = str(ref.filepath.absolute())
params = dict(page=ref.page)
if relative:
return f'#lref("{format_path}?{urlencode(params)}", pdfref: true)[]'
else:
return f'#link("pdfref://{format_path}?{urlencode(params)}")[]'
def copy_ref(ref: Reference, format: LinkFormat) -> None:
"""Formats Reference and copies it to clipboard."""
match format:
case LinkFormat.TYPST:
link_txt = format_typst(ref)
link_txt = typst_fmt.ref(ref)
clip_copy(link_txt)
@ -59,7 +40,12 @@ def notify(title:str, txt: str) -> None:
if __name__ == "__main__":
ref = get_metadata_pdf()
args = parser.parse_args()
if args.section:
raise NotImplementedError("--section isn't implemented")
ref = get_page_pdf()
format = LinkFormat.TYPST
copy_ref(ref, format)

View File

@ -1,4 +1,4 @@
from typing import NewType
from typing import NewType, Union
from pathlib import Path
from dataclasses import dataclass
@ -6,12 +6,60 @@ from dataclasses import dataclass
WindowId = NewType("WindowId", int)
# PID int
ProcessId = NewType("ProcessId", int)
# page number
@dataclass
class _Reference:
"""Reference to a location within a file."""
pass
@dataclass
class _PDFReference(_Reference):
"""Reference to a location within a PDF file.
Attributes
----------
filepath
Path of the relevant PDF file.
"""
filepath: Path
PageNumber = NewType("PageNumber", int)
# reference to a specific page in a specific pdf
@dataclass
class Reference:
filepath: Path
class PDFPage(_PDFReference):
"""Reference to a specific page in a PDF.
Attributes
----------
page
Page number.
"""
page: PageNumber
SectionTitle = NewType("SectionTitle", str)
@dataclass
class PDFSection(_PDFReference):
"""Reference to a specific section title in a PDF.
Attributes
----------
title
Section title.
"""
title: SectionTitle
PDFReference = Union[PDFPage, PDFSection]
# for now no other format is implemented
# replace this with an union if that happens
Reference = PDFReference

0
formatter/__init__.py Normal file
View File

41
formatter/typst.py Normal file
View File

@ -0,0 +1,41 @@
from os import environ
from urllib.parse import urlencode
from datatypes import PDFPage, PDFSection, PDFReference, Reference
from typing import assert_never
from pathlib import Path
def format_pdf_link(ref: PDFReference) -> str:
path_str = environ.get("TYPST_ROOT", None)
if path_str is None:
raise KeyError("Please set TYPST_ROOT to format links with Typst.")
typst_root = Path(path_str)
relative: bool = ref.filepath.is_relative_to(typst_root)
format_path: str
if relative:
format_path = "/" + str(ref.filepath.relative_to(typst_root))
else:
format_path = str(ref.filepath.absolute())
params = {}
match ref:
case PDFPage():
params["page"] = ref.page
case PDFSection():
params["section"] = ref.title
case _ as obj:
assert_never(obj)
if relative:
return f'#lref("{format_path}?{urlencode(params)}", pdfref: true)[]'
else:
return f'#link("pdfref://{format_path}?{urlencode(params)}")[]'
def ref(ref: Reference) -> str:
"""Formats a Reference."""
# for now no other types are implemented
# replace this with a match/case when that happens
return format_pdf_link(ref)

View File

@ -4,12 +4,12 @@ import pydbus
import subprocess
def get_metadata_pdf() -> Reference:
def get_page_pdf() -> PDFPage:
"""Find current page of focused PDF reader window.
Returns
-------
`Reference` to the current page, or None if not found.
`PDFPage` reference to the current page.
"""
try:
res = subprocess.run(
@ -44,16 +44,16 @@ def get_metadata_pdf() -> Reference:
match wm_class[0]:
case "Zathura":
return get_metadata_zathura(pid)
return get_page_zathura(pid)
case "org.pwmt.zathura":
return get_metadata_zathura(pid)
return get_page_zathura(pid)
case _:
raise Exception(
f"Can not retrieve pdf data from this type of window {wm_class}."
)
def get_metadata_zathura(pid: ProcessId) -> Reference:
def get_page_zathura(pid: ProcessId) -> PDFPage:
"""Given the PID of a Zathura instance, find which page of which file it's on.
Parameters
@ -63,7 +63,7 @@ def get_metadata_zathura(pid: ProcessId) -> Reference:
Returns
-------
`Reference` that the Zathura instance is currently on
`PDFPage` that the Zathura instance is currently on.
"""
bus = pydbus.SessionBus()
@ -73,4 +73,4 @@ def get_metadata_zathura(pid: ProcessId) -> Reference:
# zathura returns 0-indexed pages
pagenumber: PageNumber = obj.pagenumber + 1
return Reference(filepath=Path(filename), page=pagenumber)
return PDFPage(filepath=Path(filename), page=pagenumber)

58
util.py Normal file
View File

@ -0,0 +1,58 @@
import subprocess
from dataclasses import dataclass
from typing import Optional
@dataclass
class RofiResult:
"""Data returned from Rofi.
Attributes
----------
index
Index within entries of the selected entry.
`None` if nothing was selected.
value
Selected entry's string value.
`None` if the value is not in the list or nothing was selected.
custom_bind
ID of custom bind used to select entry. None if no custom bind was used.
"""
index: Optional[int]
value: str
custom_bind: Optional[int]
def rofi(entries: list[str], prompt: str="> ", fuzzy=True, extra_args=[]) -> Optional[RofiResult]:
"""Start a Rofi prompt.
Returns
-------
None if the prompt was cancelled, or a `RofiResult`.
"""
args = ["rofi", "-dmenu", "-sep", "\\0"]
args += ["-p", prompt]
if fuzzy:
args += ["-matching", "fuzzy"]
args += extra_args
ret = RofiResult(None, "", None)
res = subprocess.run(args, input="\0".join(entries), stdout=subprocess.PIPE, text=True)
match res.returncode:
case 0:
pass
case 1:
return None
case x if x >= 10 and x <= 28:
ret.custom_bind = x - 9
case _ as retc:
raise RuntimeError(f"Rofi returned an unexpected return code `{retc}`.")
ret.value = res.stdout.strip()
try:
ret.index = entries.index(ret.value)
except ValueError:
pass
return ret