From 3327e40f09498ec07b016ec981ea26321e4afa96 Mon Sep 17 00:00:00 2001 From: Dima Gerasimov Date: Sun, 12 Apr 2020 15:56:24 +0100 Subject: [PATCH 1/3] more consisten quoting --- server/setup | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/server/setup b/server/setup index 1e7039e..8c5b84f 100755 --- a/server/setup +++ b/server/setup @@ -37,8 +37,12 @@ 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, + ] + extra_args = ' '.join(map(shlex.quote, args)) with out.open('w') as fo: fo.write(SYSTEMD_CONFIG.format( From 6b6020598bb7b34a8e327dcb107703b251eadd08 Mon Sep 17 00:00:00 2001 From: Dima Gerasimov Date: Sun, 12 Apr 2020 16:10:27 +0100 Subject: [PATCH 2/3] add Config to allow for more flexible formatting --- server/grasp_server.py | 2 +- server/org_tools.py | 44 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/server/grasp_server.py b/server/grasp_server.py index e681aa8..dec0dc5 100755 --- a/server/grasp_server.py +++ b/server/grasp_server.py @@ -8,7 +8,7 @@ from pathlib import Path import re from typing import List, Optional -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_TEMPLATE_VAR = 'GRASP_CAPTURE_TEMPLATE' diff --git a/server/org_tools.py b/server/org_tools.py index e62a6ee..708796e 100644 --- a/server/org_tools.py +++ b/server/org_tools.py @@ -4,6 +4,7 @@ from typing import List, Optional 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") @@ -18,6 +19,35 @@ 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, @@ -25,6 +55,7 @@ def as_org( 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. @@ -41,19 +72,16 @@ You can look at `test_templates` for some specific examples. 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( From c29408ae6c10b3593189c61b367b5b420aafa3b4 Mon Sep 17 00:00:00 2001 From: Dima Gerasimov Date: Sun, 12 Apr 2020 16:39:45 +0100 Subject: [PATCH 3/3] Support dynamic configuration --- README.md | 7 +++++++ misc/config.py.example | 20 ++++++++++++++++++++ server/grasp_server.py | 29 +++++++++++++++++++++++++---- server/setup | 1 + 4 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 misc/config.py.example diff --git a/README.md b/README.md index d3d9727..1f3ca15 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/misc/config.py.example b/misc/config.py.example new file mode 100644 index 0000000..e8fb823 --- /dev/null +++ b/misc/config.py.example @@ -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() + ] diff --git a/server/grasp_server.py b/server/grasp_server.py index dec0dc5..525956e 100755 --- a/server/grasp_server.py +++ b/server/grasp_server.py @@ -6,17 +6,19 @@ import os 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, 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 @@ -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, @@ -48,6 +63,7 @@ def capture( 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) @@ -68,6 +84,7 @@ def capture( comment=comment, tags=tags, org_template=org_template, + config=config, ) append_org( path=capture_path, @@ -111,13 +128,15 @@ class GraspRequestHandler(BaseHTTPRequestHandler): 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() @@ -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(): @@ -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() diff --git a/server/setup b/server/setup index 8c5b84f..50606e6 100755 --- a/server/setup +++ b/server/setup @@ -41,6 +41,7 @@ def setup(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))