Compare commits
2 Commits
96f21845dd
...
482d443314
Author | SHA1 | Date | |
---|---|---|---|
482d443314 | |||
5b8742f7c0 |
40
copy_ref
40
copy_ref
@ -1,12 +1,15 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
from pdf_data import get_metadata_pdf
|
from pdf_data import get_page_pdf
|
||||||
from datatypes import *
|
from datatypes import *
|
||||||
from enum import Enum, auto
|
from enum import Enum, auto
|
||||||
from os import environ
|
|
||||||
from urllib.parse import urlencode
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import pydbus
|
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):
|
class LinkFormat(Enum):
|
||||||
@ -20,33 +23,11 @@ def clip_copy(txt: str):
|
|||||||
raise Exception("Please install `xsel`.") from e
|
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:
|
def copy_ref(ref: Reference, format: LinkFormat) -> None:
|
||||||
"""Formats Reference and copies it to clipboard."""
|
"""Formats Reference and copies it to clipboard."""
|
||||||
match format:
|
match format:
|
||||||
case LinkFormat.TYPST:
|
case LinkFormat.TYPST:
|
||||||
link_txt = format_typst(ref)
|
link_txt = typst_fmt.ref(ref)
|
||||||
|
|
||||||
clip_copy(link_txt)
|
clip_copy(link_txt)
|
||||||
|
|
||||||
@ -59,7 +40,12 @@ def notify(title:str, txt: str) -> None:
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
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
|
format = LinkFormat.TYPST
|
||||||
copy_ref(ref, format)
|
copy_ref(ref, format)
|
||||||
|
58
datatypes.py
58
datatypes.py
@ -1,4 +1,4 @@
|
|||||||
from typing import NewType
|
from typing import NewType, Union
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
@ -6,12 +6,60 @@ from dataclasses import dataclass
|
|||||||
WindowId = NewType("WindowId", int)
|
WindowId = NewType("WindowId", int)
|
||||||
# PID int
|
# PID int
|
||||||
ProcessId = NewType("ProcessId", 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)
|
PageNumber = NewType("PageNumber", int)
|
||||||
|
|
||||||
|
|
||||||
# reference to a specific page in a specific pdf
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Reference:
|
class PDFPage(_PDFReference):
|
||||||
filepath: Path
|
"""Reference to a specific page in a PDF.
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
----------
|
||||||
|
page
|
||||||
|
Page number.
|
||||||
|
"""
|
||||||
|
|
||||||
page: PageNumber
|
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
0
formatter/__init__.py
Normal file
41
formatter/typst.py
Normal file
41
formatter/typst.py
Normal 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)
|
14
pdf_data.py
14
pdf_data.py
@ -4,12 +4,12 @@ import pydbus
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
def get_metadata_pdf() -> Reference:
|
def get_page_pdf() -> PDFPage:
|
||||||
"""Find current page of focused PDF reader window.
|
"""Find current page of focused PDF reader window.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
`Reference` to the current page, or None if not found.
|
`PDFPage` reference to the current page.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
res = subprocess.run(
|
res = subprocess.run(
|
||||||
@ -44,16 +44,16 @@ def get_metadata_pdf() -> Reference:
|
|||||||
|
|
||||||
match wm_class[0]:
|
match wm_class[0]:
|
||||||
case "Zathura":
|
case "Zathura":
|
||||||
return get_metadata_zathura(pid)
|
return get_page_zathura(pid)
|
||||||
case "org.pwmt.zathura":
|
case "org.pwmt.zathura":
|
||||||
return get_metadata_zathura(pid)
|
return get_page_zathura(pid)
|
||||||
case _:
|
case _:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
f"Can not retrieve pdf data from this type of window {wm_class}."
|
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.
|
"""Given the PID of a Zathura instance, find which page of which file it's on.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@ -63,7 +63,7 @@ def get_metadata_zathura(pid: ProcessId) -> Reference:
|
|||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
`Reference` that the Zathura instance is currently on
|
`PDFPage` that the Zathura instance is currently on.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
bus = pydbus.SessionBus()
|
bus = pydbus.SessionBus()
|
||||||
@ -73,4 +73,4 @@ def get_metadata_zathura(pid: ProcessId) -> Reference:
|
|||||||
# zathura returns 0-indexed pages
|
# zathura returns 0-indexed pages
|
||||||
pagenumber: PageNumber = obj.pagenumber + 1
|
pagenumber: PageNumber = obj.pagenumber + 1
|
||||||
|
|
||||||
return Reference(filepath=Path(filename), page=pagenumber)
|
return PDFPage(filepath=Path(filename), page=pagenumber)
|
||||||
|
58
util.py
Normal file
58
util.py
Normal 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
|
Loading…
x
Reference in New Issue
Block a user