Compare commits

..

8 commits

Author SHA1 Message Date
Anish Lakhwara 485dbca54e custom template 2022-12-20 11:23:32 +10:00
Anish Lakhwara f784dff0ca flake.lock 2022-12-19 18:26:59 +10:00
Anish Lakhwara 299dbb7865 change regex string 2022-12-19 18:26:29 +10:00
Anish Lakhwara feb74fdf2d flake that i'd already made for this 2022-12-19 18:26:14 +10:00
karlicoss 4bc8f01122
Merge pull request #30 from karlicoss/updates
Support for dynamic selection/comment formatting
2020-04-12 16:42:48 +01:00
Dima Gerasimov c29408ae6c Support dynamic configuration 2020-04-12 16:39:45 +01:00
Dima Gerasimov 6b6020598b add Config to allow for more flexible formatting 2020-04-12 16:10:27 +01:00
Dima Gerasimov 3327e40f09 more consisten quoting 2020-04-12 15:56:24 +01:00
7 changed files with 273 additions and 19 deletions

View file

@ -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.

59
flake.lock Normal file
View file

@ -0,0 +1,59 @@
{
"nodes": {
"flake-utils": {
"locked": {
"lastModified": 1667395993,
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"grasp": {
"flake": false,
"locked": {
"lastModified": 1586706168,
"narHash": "sha256-xLkxE24xtTghQpAfAqAsOvsGpz0coACXjNT8bHZhcxM=",
"owner": "karlicoss",
"repo": "grasp",
"rev": "4bc8f011227d1baa18b4e98543561534065490d8",
"type": "github"
},
"original": {
"owner": "karlicoss",
"repo": "grasp",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1671435975,
"narHash": "sha256-pEZT/KR0Vh874GHA2f6H1/m6Ls7su47dHaCQlXjbXNM=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "c4ae266de959fb5a48a210bfc7ee16030a558638",
"type": "github"
},
"original": {
"owner": "nixos",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"grasp": "grasp",
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

115
flake.nix Normal file
View file

@ -0,0 +1,115 @@
{
description = "Reproducible Environment for Grasp server";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs";
flake-utils.url = "github:numtide/flake-utils";
grasp.url = "github:karlicoss/grasp";
grasp.flake = false;
};
outputs = { self, nixpkgs, flake-utils, grasp }:
flake-utils.lib.eachDefaultSystem
(system:
let
pkgs = nixpkgs.legacyPackages.${system};
in
{
packages.grasp = pkgs.stdenv.mkDerivation rec {
pname = "grasp";
version = "v0.6.6";
src = grasp;
installPhase = ''
mkdir -p $out/bin
cp -r server/* $out/bin/
'';
};
devShell = pkgs.mkShell {
buildInputs = [ pkgs.python3 ];
};
}) // {
nixosModule = { options, lib, config, pkgs, ... }:
let
graspConfig = config.services."grasp";
options = {
enable = lib.mkEnableOption "Grasp service";
path = lib.mkOption rec {
type = lib.types.path;
default = "/var/lib/grasp/";
example = "/var/lib/grasp";
description = "Where to run Grasp from";
};
#port = lib.mkOption rec {
# type = lib.types.int;
# default = 12212;
# example = 12212;
# description = "Port to run the server on";
#};
user = lib.mkOption rec {
type = lib.types.str;
default = "";
example = "nixos";
description = "User to run the service as";
};
#template = mkOption rec {
# type = types.str;
# default = "12212";
# example = "12212";
# description = "Template to use";
#};
};
in
{
options.services.grasp-server = options;
config = lib.mkIf graspConfig.enable {
systemd.services.grasp-server = {
description = ''
A grasp server module
'';
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
serviceConfig = {
#DynamicUser = true; # Probably should default to this if no user supplied
User = "${graspConfig.user}";
RuntimeDirectory = "${graspConfig.path}";
RuntimeDirectoryMode = "0755";
StateDirectory = "${graspConfig.path}";
StateDirectoryMode = "0700";
};
path = with pkgs; [ nix python3 ] ++ [ ];
script = ''
${self.packages.${pkgs.system}.grasp}/bin/grasp_server.py --path ${graspConfig.path}
'';
};
# Allow nginx through the firewall
networking.firewall.allowedTCPPorts = [ 12212 ];
};
};
# Test container
nixosConfigurations."test" = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
self.nixosModule
({ pkgs, ... }: {
# Only allow this to boot as a container
boot.isContainer = true;
networking.hostName = "grasp";
services."grasp".enable = true;
services."grasp".user = "anish";
services."grasp".path = "/home/anish/grasp/capture.org";
# I think I actually created the above folder as well in the container
# I figured dynamic user with their own text file seems less useful
# I didn't know how to default to a
users.users.anish = {
isNormalUser = true;
password = "password";
};
})
];
};
};
}

20
misc/config.py.example Normal file
View file

@ -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()
]

View file

@ -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
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()

View file

@ -2,8 +2,9 @@ from datetime import datetime
import re
from typing import List, Optional
DEFAULT_TEMPLATE = "* %U %:description %:tags\n%:link\n%:initial\n"
DEFAULT_TEMPLATE = "\\%title: [[%:link|%:description]]\n%:tags\n%U\n\n\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.
@ -33,7 +64,8 @@ Additionally, supports :selection, :comment and :tags expansions.
You can look at `test_templates` for some specific examples.
"""
py_template = re.sub(r'%[:]?([?\w]+)', r'{\1}', org_template)
# py_template = re.sub(r'%[:]?([?\w]+)', r'{\1}', org_template)
py_template = re.sub(r'[^\\]%[:]?([?\w]+)', r'{\1}', org_template)
NOW = datetime.now()
org_date = date2org(NOW)
@ -41,19 +73,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(
@ -117,5 +146,3 @@ def test_templates():
org_template=org_template
)
print()
print(res)

View file

@ -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(