Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for dynamic selection/comment formatting #30

Merged
merged 3 commits into from
Apr 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ or your target capture file is just not there, you can selfhost the server part

That's it! If you're using custom port make sure it's the same as in the extension settings (default is `12212`).

## Configuration

[Here](https://github.com/karlicoss/grasp/blob/af24c991579986cec73695daa8318e7831049305/server/org_tools.py#L91-L109) you can find some references for the `--template` syntax.

If you are looking for more flexible formatting that's not supported by template syntax, see [config.py.example](misc/config.py.example).
You can modify it to your liking and pass as `--config` to `grasp_server/setup` scripts.

# Motivation
Why use org-capture? Well, it's hard to explain, maybe some other time... However, if you do know you want to use it instead of/alongside your browser bookmarks, by default
you don't have much choice and have to copy everything manually. For an experienced enough org-mode user it's no less than a torture.
Expand Down
20 changes: 20 additions & 0 deletions misc/config.py.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from typing import List

class Config:
@staticmethod
def format_selection(selection: str) -> List[str]:
return [
'#+begin_quote',
selection,
'#+end_quote',
]

@staticmethod
def format_comment(comment: str) -> List[str]:
return [
'- comment:'
] + [
# colon is a quck way to define blocks of code/preformatted stuff: https://orgmode.org/manual/Literal-Examples.html
' : ' + s
for s in comment.splitlines()
]
31 changes: 26 additions & 5 deletions server/grasp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,19 @@
import logging
from pathlib import Path
import re
from typing import List, Optional
from typing import List, Optional, Dict, Any

from org_tools import as_org, empty, DEFAULT_TEMPLATE
from org_tools import as_org, empty, DEFAULT_TEMPLATE, Config

CAPTURE_PATH_VAR = 'GRASP_CAPTURE_PATH'
CAPTURE_PATH_VAR = 'GRASP_CAPTURE_PATH'
CAPTURE_TEMPLATE_VAR = 'GRASP_CAPTURE_TEMPLATE'
CAPTURE_CONFIG_VAR = 'GRASP_CAPTURE_CONFIG'


def get_logger():
return logging.getLogger('grasp-server')


def append_org(
path: Path,
org: str
Expand All @@ -32,6 +34,19 @@ def append_org(
fo.write(org)


from functools import lru_cache
@lru_cache(1)
def capture_config() -> Optional[Config]:
cvar = os.environ.get(CAPTURE_CONFIG_VAR)
if cvar is None:
return None

globs: Dict[str, Any] = {}
exec(Path(cvar).read_text(), globs)
ConfigClass = globs['Config']
return ConfigClass()


def capture(
url: str,
title,
Expand All @@ -48,6 +63,7 @@ def safe(s: Optional[str]) -> str:
return s
capture_path = Path(os.environ[CAPTURE_PATH_VAR]).expanduser()
org_template = os.environ[CAPTURE_TEMPLATE_VAR]
config = capture_config()
logger.info('capturing %s to %s', (url, title, selection, comment, tag_str), capture_path)

url = safe(url)
Expand All @@ -68,6 +84,7 @@ def safe(s: Optional[str]) -> str:
comment=comment,
tags=tags,
org_template=org_template,
config=config,
)
append_org(
path=capture_path,
Expand Down Expand Up @@ -111,13 +128,15 @@ def do_POST(self):
self.respond_error(message=str(e))


def run(port: str, capture_path: str, template: str):
def run(port: str, capture_path: str, template: str, config: Optional[Path]):
logger = get_logger()
logger.info("Using template %s", template)

# not sure if there is a simpler way to communicate with the server...
os.environ[CAPTURE_PATH_VAR] = capture_path
os.environ[CAPTURE_TEMPLATE_VAR] = template
if config is not None:
os.environ[CAPTURE_CONFIG_VAR] = str(config)
httpd = HTTPServer(('', int(port)), GraspRequestHandler)
logger.info(f"Starting httpd on port {port}")
httpd.serve_forever()
Expand All @@ -129,6 +148,8 @@ def setup_parser(p):
p.add_argument('--template', type=str, default=DEFAULT_TEMPLATE, help=f"""
{as_org.__doc__}
""")
abspath = lambda p: str(Path(p).absolute())
p.add_argument('--config', type=abspath, required=False, help='Optional dynamic config')


def main():
Expand All @@ -137,7 +158,7 @@ def main():
p = argparse.ArgumentParser('grasp server', formatter_class=lambda prog: argparse.ArgumentDefaultsHelpFormatter(prog, width=100)) # type: ignore
setup_parser(p)
args = p.parse_args()
run(args.port, args.path, args.template)
run(args.port, args.path, args.template, args.config)

if __name__ == '__main__':
main()
44 changes: 36 additions & 8 deletions server/org_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

DEFAULT_TEMPLATE = "* %U %:description %:tags\n%:link\n%:initial\n"

# TODO reuse inorganic/orgparse??
def date2org(t: datetime) -> str:
return t.strftime("%Y-%m-%d %a")

Expand All @@ -18,13 +19,43 @@ def empty(s) -> bool:
return s is None or len(s.strip()) == 0


# TODO protocol??
# TODO put template in config??
class Config:
@staticmethod
def format_selection(selection: str) -> List[str]:
...

@staticmethod
def format_comment(comment: str) -> List[str]:
...


class DefaultConfig(Config):
@staticmethod
def format_selection(selection: str) -> List[str]:
return [
'Selection:',
selection,
]


@staticmethod
def format_comment(comment: str) -> List[str]:
return [
'Comment:',
comment
]


def as_org(
url: str,
title: str,
selection: str,
comment: str,
tags: List[str],
org_template: str,
config: Optional[Config] = None,
):
"""
Formats captured results according to org template. Supports all sensible (e.g. non-interactive) template expansions from https://orgmode.org/manual/Template-expansion.html#Template-expansion.
Expand All @@ -41,19 +72,16 @@ def as_org(

tags_s = '' if len(tags) == 0 else (':' + ':'.join(tags) + ':')

if config is None:
config = DefaultConfig()

# TODO tabulate selection and comments?
# TODO not sure, maybe add as org quote?
parts = []
if not empty(selection):
parts.extend([
'Selection:',
selection,
])
parts.extend(config.format_selection(selection))
if not empty(comment):
parts.extend([
'Comment:',
comment
])
parts.extend(config.format_comment(comment))
initial = '\n'.join(parts)

res = py_template.format(
Expand Down
9 changes: 7 additions & 2 deletions server/setup
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,13 @@ def setup(args):
server_bin = Path(__file__).parent.joinpath('grasp_server.py').absolute()

template = args.template.replace('%', '%%').replace('\n', r'\n') # escape for systemd...
template = shlex.quote(template)
extra_args = f'--port {args.port} --path {args.path} --template {template}'
args = [
'--port', args.port,
'--path', args.path,
'--template', template,
*([] if args.config is None else ['--config' , args.config]),
]
extra_args = ' '.join(map(shlex.quote, args))

with out.open('w') as fo:
fo.write(SYSTEMD_CONFIG.format(
Expand Down