Compare commits
No commits in common. "485dbca54e44d778b634dfd9baf713836b5f1bab" and "af24c991579986cec73695daa8318e7831049305" have entirely different histories.
485dbca54e
...
af24c99157
|
@ -20,13 +20,6 @@ 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`).
|
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
|
# 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
|
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.
|
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
59
flake.lock
|
@ -1,59 +0,0 @@
|
||||||
{
|
|
||||||
"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
115
flake.nix
|
@ -1,115 +0,0 @@
|
||||||
{
|
|
||||||
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";
|
|
||||||
};
|
|
||||||
})
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
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()
|
|
||||||
]
|
|
|
@ -6,19 +6,17 @@ import os
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import re
|
import re
|
||||||
from typing import List, Optional, Dict, Any
|
from typing import List, Optional
|
||||||
|
|
||||||
from org_tools import as_org, empty, DEFAULT_TEMPLATE, Config
|
from org_tools import as_org, empty, DEFAULT_TEMPLATE
|
||||||
|
|
||||||
CAPTURE_PATH_VAR = 'GRASP_CAPTURE_PATH'
|
CAPTURE_PATH_VAR = 'GRASP_CAPTURE_PATH'
|
||||||
CAPTURE_TEMPLATE_VAR = 'GRASP_CAPTURE_TEMPLATE'
|
CAPTURE_TEMPLATE_VAR = 'GRASP_CAPTURE_TEMPLATE'
|
||||||
CAPTURE_CONFIG_VAR = 'GRASP_CAPTURE_CONFIG'
|
|
||||||
|
|
||||||
|
|
||||||
def get_logger():
|
def get_logger():
|
||||||
return logging.getLogger('grasp-server')
|
return logging.getLogger('grasp-server')
|
||||||
|
|
||||||
|
|
||||||
def append_org(
|
def append_org(
|
||||||
path: Path,
|
path: Path,
|
||||||
org: str
|
org: str
|
||||||
|
@ -34,19 +32,6 @@ def append_org(
|
||||||
fo.write(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(
|
def capture(
|
||||||
url: str,
|
url: str,
|
||||||
title,
|
title,
|
||||||
|
@ -63,7 +48,6 @@ def capture(
|
||||||
return s
|
return s
|
||||||
capture_path = Path(os.environ[CAPTURE_PATH_VAR]).expanduser()
|
capture_path = Path(os.environ[CAPTURE_PATH_VAR]).expanduser()
|
||||||
org_template = os.environ[CAPTURE_TEMPLATE_VAR]
|
org_template = os.environ[CAPTURE_TEMPLATE_VAR]
|
||||||
config = capture_config()
|
|
||||||
logger.info('capturing %s to %s', (url, title, selection, comment, tag_str), capture_path)
|
logger.info('capturing %s to %s', (url, title, selection, comment, tag_str), capture_path)
|
||||||
|
|
||||||
url = safe(url)
|
url = safe(url)
|
||||||
|
@ -84,7 +68,6 @@ def capture(
|
||||||
comment=comment,
|
comment=comment,
|
||||||
tags=tags,
|
tags=tags,
|
||||||
org_template=org_template,
|
org_template=org_template,
|
||||||
config=config,
|
|
||||||
)
|
)
|
||||||
append_org(
|
append_org(
|
||||||
path=capture_path,
|
path=capture_path,
|
||||||
|
@ -128,15 +111,13 @@ class GraspRequestHandler(BaseHTTPRequestHandler):
|
||||||
self.respond_error(message=str(e))
|
self.respond_error(message=str(e))
|
||||||
|
|
||||||
|
|
||||||
def run(port: str, capture_path: str, template: str, config: Optional[Path]):
|
def run(port: str, capture_path: str, template: str):
|
||||||
logger = get_logger()
|
logger = get_logger()
|
||||||
logger.info("Using template %s", template)
|
logger.info("Using template %s", template)
|
||||||
|
|
||||||
# not sure if there is a simpler way to communicate with the server...
|
# not sure if there is a simpler way to communicate with the server...
|
||||||
os.environ[CAPTURE_PATH_VAR] = capture_path
|
os.environ[CAPTURE_PATH_VAR] = capture_path
|
||||||
os.environ[CAPTURE_TEMPLATE_VAR] = template
|
os.environ[CAPTURE_TEMPLATE_VAR] = template
|
||||||
if config is not None:
|
|
||||||
os.environ[CAPTURE_CONFIG_VAR] = str(config)
|
|
||||||
httpd = HTTPServer(('', int(port)), GraspRequestHandler)
|
httpd = HTTPServer(('', int(port)), GraspRequestHandler)
|
||||||
logger.info(f"Starting httpd on port {port}")
|
logger.info(f"Starting httpd on port {port}")
|
||||||
httpd.serve_forever()
|
httpd.serve_forever()
|
||||||
|
@ -148,8 +129,6 @@ def setup_parser(p):
|
||||||
p.add_argument('--template', type=str, default=DEFAULT_TEMPLATE, help=f"""
|
p.add_argument('--template', type=str, default=DEFAULT_TEMPLATE, help=f"""
|
||||||
{as_org.__doc__}
|
{as_org.__doc__}
|
||||||
""")
|
""")
|
||||||
abspath = lambda p: str(Path(p).absolute())
|
|
||||||
p.add_argument('--config', type=abspath, required=False, help='Optional dynamic config')
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
@ -158,7 +137,7 @@ def main():
|
||||||
p = argparse.ArgumentParser('grasp server', formatter_class=lambda prog: argparse.ArgumentDefaultsHelpFormatter(prog, width=100)) # type: ignore
|
p = argparse.ArgumentParser('grasp server', formatter_class=lambda prog: argparse.ArgumentDefaultsHelpFormatter(prog, width=100)) # type: ignore
|
||||||
setup_parser(p)
|
setup_parser(p)
|
||||||
args = p.parse_args()
|
args = p.parse_args()
|
||||||
run(args.port, args.path, args.template, args.config)
|
run(args.port, args.path, args.template)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
|
@ -2,9 +2,8 @@ from datetime import datetime
|
||||||
import re
|
import re
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
DEFAULT_TEMPLATE = "\\%title: [[%:link|%:description]]\n%:tags\n%U\n\n\n%:link\n%:initial\n"
|
DEFAULT_TEMPLATE = "* %U %:description %:tags\n%:link\n%:initial\n"
|
||||||
|
|
||||||
# TODO reuse inorganic/orgparse??
|
|
||||||
def date2org(t: datetime) -> str:
|
def date2org(t: datetime) -> str:
|
||||||
return t.strftime("%Y-%m-%d %a")
|
return t.strftime("%Y-%m-%d %a")
|
||||||
|
|
||||||
|
@ -19,35 +18,6 @@ def empty(s) -> bool:
|
||||||
return s is None or len(s.strip()) == 0
|
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(
|
def as_org(
|
||||||
url: str,
|
url: str,
|
||||||
title: str,
|
title: str,
|
||||||
|
@ -55,7 +25,6 @@ def as_org(
|
||||||
comment: str,
|
comment: str,
|
||||||
tags: List[str],
|
tags: List[str],
|
||||||
org_template: 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.
|
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.
|
||||||
|
@ -64,8 +33,7 @@ Additionally, supports :selection, :comment and :tags expansions.
|
||||||
|
|
||||||
You can look at `test_templates` for some specific examples.
|
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()
|
NOW = datetime.now()
|
||||||
org_date = date2org(NOW)
|
org_date = date2org(NOW)
|
||||||
|
@ -73,16 +41,19 @@ You can look at `test_templates` for some specific examples.
|
||||||
|
|
||||||
tags_s = '' if len(tags) == 0 else (':' + ':'.join(tags) + ':')
|
tags_s = '' if len(tags) == 0 else (':' + ':'.join(tags) + ':')
|
||||||
|
|
||||||
if config is None:
|
|
||||||
config = DefaultConfig()
|
|
||||||
|
|
||||||
# TODO tabulate selection and comments?
|
# TODO tabulate selection and comments?
|
||||||
# TODO not sure, maybe add as org quote?
|
# TODO not sure, maybe add as org quote?
|
||||||
parts = []
|
parts = []
|
||||||
if not empty(selection):
|
if not empty(selection):
|
||||||
parts.extend(config.format_selection(selection))
|
parts.extend([
|
||||||
|
'Selection:',
|
||||||
|
selection,
|
||||||
|
])
|
||||||
if not empty(comment):
|
if not empty(comment):
|
||||||
parts.extend(config.format_comment(comment))
|
parts.extend([
|
||||||
|
'Comment:',
|
||||||
|
comment
|
||||||
|
])
|
||||||
initial = '\n'.join(parts)
|
initial = '\n'.join(parts)
|
||||||
|
|
||||||
res = py_template.format(
|
res = py_template.format(
|
||||||
|
@ -146,3 +117,5 @@ def test_templates():
|
||||||
org_template=org_template
|
org_template=org_template
|
||||||
|
|
||||||
)
|
)
|
||||||
|
print()
|
||||||
|
print(res)
|
||||||
|
|
|
@ -37,13 +37,8 @@ def setup(args):
|
||||||
server_bin = Path(__file__).parent.joinpath('grasp_server.py').absolute()
|
server_bin = Path(__file__).parent.joinpath('grasp_server.py').absolute()
|
||||||
|
|
||||||
template = args.template.replace('%', '%%').replace('\n', r'\n') # escape for systemd...
|
template = args.template.replace('%', '%%').replace('\n', r'\n') # escape for systemd...
|
||||||
args = [
|
template = shlex.quote(template)
|
||||||
'--port', args.port,
|
extra_args = f'--port {args.port} --path {args.path} --template {template}'
|
||||||
'--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:
|
with out.open('w') as fo:
|
||||||
fo.write(SYSTEMD_CONFIG.format(
|
fo.write(SYSTEMD_CONFIG.format(
|
||||||
|
|
Loading…
Reference in a new issue