jwbmisc 0.0.3__py3-none-any.whl → 0.0.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- jwbmisc/__init__.py +3 -2
- jwbmisc/_version.py +2 -2
- jwbmisc/collection.py +27 -0
- jwbmisc/interactive.py +13 -0
- jwbmisc/keeper.py +23 -22
- jwbmisc/passwd.py +5 -3
- jwbmisc/string.py +20 -0
- {jwbmisc-0.0.3.dist-info → jwbmisc-0.0.5.dist-info}/METADATA +1 -1
- jwbmisc-0.0.5.dist-info/RECORD +15 -0
- jwbmisc/util.py +0 -64
- jwbmisc-0.0.3.dist-info/RECORD +0 -14
- {jwbmisc-0.0.3.dist-info → jwbmisc-0.0.5.dist-info}/WHEEL +0 -0
- {jwbmisc-0.0.3.dist-info → jwbmisc-0.0.5.dist-info}/licenses/LICENSE +0 -0
- {jwbmisc-0.0.3.dist-info → jwbmisc-0.0.5.dist-info}/top_level.txt +0 -0
jwbmisc/__init__.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
from .passwd import get_pass
|
|
2
|
-
from .string import jinja_replace
|
|
2
|
+
from .string import jinja_replace, randomsuffix, qw, split_host
|
|
3
3
|
from .exec import run_cmd
|
|
4
4
|
from .json import jsonc_loads, jsonc_read, ndjson_read, ndjson_write, resilient_loads
|
|
5
5
|
from .fs import fzf, find_root
|
|
6
|
-
from .
|
|
6
|
+
from .collection import goo
|
|
7
|
+
from .interactive import ask, confirm
|
|
7
8
|
|
|
8
9
|
__all__ = [
|
|
9
10
|
"get_pass",
|
jwbmisc/_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.0.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 0,
|
|
31
|
+
__version__ = version = '0.0.5'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 0, 5)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
jwbmisc/collection.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def goo(
|
|
5
|
+
d: dict[str, Any],
|
|
6
|
+
*keys: str | int,
|
|
7
|
+
default: Any | None = None,
|
|
8
|
+
raise_on_default: bool = False,
|
|
9
|
+
):
|
|
10
|
+
path = ".".join(str(k) for k in keys)
|
|
11
|
+
parts = path.split(".")
|
|
12
|
+
|
|
13
|
+
res = d
|
|
14
|
+
for p in parts:
|
|
15
|
+
if res is None:
|
|
16
|
+
if raise_on_default:
|
|
17
|
+
raise ValueError(f"'{path}' does not exist")
|
|
18
|
+
return default
|
|
19
|
+
if isinstance(res, (list, set, tuple)):
|
|
20
|
+
res = res[int(p)]
|
|
21
|
+
else:
|
|
22
|
+
res = res.get(p)
|
|
23
|
+
if res is None:
|
|
24
|
+
if raise_on_default:
|
|
25
|
+
raise ValueError(f"'{path}' does not exist")
|
|
26
|
+
return default
|
|
27
|
+
return res
|
jwbmisc/interactive.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
def ask(question: str, default: None | str = None):
|
|
2
|
+
if default is not None:
|
|
3
|
+
question += f" [{default}]"
|
|
4
|
+
answer = input(question.strip() + " ").strip()
|
|
5
|
+
return answer if answer else default
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def confirm(question: str, default: str = "n") -> bool:
|
|
9
|
+
prompt = f"{question} (y/n)"
|
|
10
|
+
answer = ask(prompt, default=default)
|
|
11
|
+
if not answer:
|
|
12
|
+
return False
|
|
13
|
+
return answer.lower().startswith("y")
|
jwbmisc/keeper.py
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import json
|
|
2
|
-
from keepercommander import vault
|
|
2
|
+
from keepercommander import vault
|
|
3
3
|
from keepercommander import api
|
|
4
4
|
from time import sleep
|
|
5
5
|
from keepercommander.params import KeeperParams
|
|
6
6
|
from keepercommander.config_storage import loader
|
|
7
7
|
import os
|
|
8
|
-
from .
|
|
8
|
+
from .interactive import ask
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
import webbrowser
|
|
11
11
|
from keepercommander.auth import login_steps
|
|
@@ -13,8 +13,10 @@ from logging import getLogger
|
|
|
13
13
|
|
|
14
14
|
logger = getLogger(__name__)
|
|
15
15
|
|
|
16
|
+
CONFIG_FILE = Path.home() / ".config" / "jwbmisc" / "keeper.json"
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
|
|
19
|
+
class MinimalKeeperUI:
|
|
18
20
|
def __init__(self):
|
|
19
21
|
self.waiting_for_sso_data_key = False
|
|
20
22
|
|
|
@@ -65,19 +67,19 @@ class _MinimalKeeperUI:
|
|
|
65
67
|
step.resume()
|
|
66
68
|
|
|
67
69
|
|
|
68
|
-
def
|
|
69
|
-
if isinstance(record,
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
def extract_record_field(record, field: str) -> str | None:
|
|
71
|
+
if not isinstance(record, vault.TypedRecord):
|
|
72
|
+
raise TypeError("only TypedRecord is supported")
|
|
73
|
+
|
|
74
|
+
value = record.get_typed_field(field) or record.get_typed_field(None, field)
|
|
73
75
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
+
if value and value.value:
|
|
77
|
+
return value.value[0] if isinstance(value.value, list) else str(value.value)
|
|
76
78
|
|
|
77
79
|
return None
|
|
78
80
|
|
|
79
81
|
|
|
80
|
-
def
|
|
82
|
+
def perform_login(params):
|
|
81
83
|
if not params.user:
|
|
82
84
|
user = os.environ.get("KEEPER_USERNAME", None)
|
|
83
85
|
if user is None:
|
|
@@ -96,29 +98,28 @@ def _perform_keeper_login(params):
|
|
|
96
98
|
params.server = server
|
|
97
99
|
|
|
98
100
|
try:
|
|
99
|
-
api.login(params, login_ui=
|
|
101
|
+
api.login(params, login_ui=MinimalKeeperUI())
|
|
100
102
|
except KeyboardInterrupt:
|
|
101
103
|
raise KeyError("\nKeeper login cancelled by user.") from None
|
|
102
104
|
except Exception as e:
|
|
103
105
|
raise KeyError(f"Keeper login failed: {e}") from e
|
|
104
106
|
|
|
105
107
|
|
|
106
|
-
def
|
|
107
|
-
|
|
108
|
-
config_file.parent.mkdir(parents=True, exist_ok=True)
|
|
108
|
+
def get_password(record_uid: str, field_path: str) -> str:
|
|
109
|
+
CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
109
110
|
|
|
110
|
-
params = KeeperParams(config_filename=str(
|
|
111
|
+
params = KeeperParams(config_filename=str(CONFIG_FILE))
|
|
111
112
|
|
|
112
|
-
if
|
|
113
|
+
if CONFIG_FILE.exists():
|
|
113
114
|
try:
|
|
114
|
-
params.config = json.loads(
|
|
115
|
+
params.config = json.loads(CONFIG_FILE.read_text())
|
|
115
116
|
loader.load_config_properties(params)
|
|
116
117
|
if not params.session_token:
|
|
117
118
|
raise ValueError("No session token")
|
|
118
119
|
except Exception:
|
|
119
|
-
|
|
120
|
+
perform_login(params)
|
|
120
121
|
else:
|
|
121
|
-
|
|
122
|
+
perform_login(params)
|
|
122
123
|
|
|
123
124
|
try:
|
|
124
125
|
api.sync_down(params)
|
|
@@ -126,11 +127,11 @@ def get_keeper_password(record_uid: str, field_path: str) -> str:
|
|
|
126
127
|
raise KeyError(f"Failed to sync Keeper vault: {e}") from e
|
|
127
128
|
|
|
128
129
|
try:
|
|
129
|
-
record =
|
|
130
|
+
record = vault.KeeperRecord.load(params, record_uid)
|
|
130
131
|
except Exception as e:
|
|
131
132
|
raise KeyError(f"Record {record_uid} not found: {e}") from e
|
|
132
133
|
|
|
133
|
-
value =
|
|
134
|
+
value = extract_record_field(record, field_path)
|
|
134
135
|
if value is None:
|
|
135
136
|
raise KeyError(f"Field '{field_path}' not found in record {record_uid}")
|
|
136
137
|
|
jwbmisc/passwd.py
CHANGED
|
@@ -2,6 +2,8 @@ import subprocess as sp
|
|
|
2
2
|
import os
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
|
|
5
|
+
PASS_BIN = os.environ.get("JWBMISC_PASS_BIN", "pass")
|
|
6
|
+
|
|
5
7
|
|
|
6
8
|
def get_pass(*pass_keys: str):
|
|
7
9
|
if not pass_keys:
|
|
@@ -48,7 +50,7 @@ def get_pass(*pass_keys: str):
|
|
|
48
50
|
|
|
49
51
|
|
|
50
52
|
def _call_unix_pass(key, lnum=1):
|
|
51
|
-
proc = sp.Popen([
|
|
53
|
+
proc = sp.Popen([PASS_BIN, "show", key], stdout=sp.PIPE, stderr=sp.PIPE, encoding="utf-8")
|
|
52
54
|
value, stderr = proc.communicate()
|
|
53
55
|
|
|
54
56
|
if proc.returncode != 0:
|
|
@@ -70,6 +72,6 @@ def _call_unix_pass(key, lnum=1):
|
|
|
70
72
|
|
|
71
73
|
|
|
72
74
|
def _keeper_password(record_uid: str, field_path: str) -> str:
|
|
73
|
-
from .keeper import
|
|
75
|
+
from .keeper import get_password as keeper_get_password
|
|
74
76
|
|
|
75
|
-
return
|
|
77
|
+
return keeper_get_password(record_uid, field_path)
|
jwbmisc/string.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import re
|
|
2
|
+
import random
|
|
3
|
+
import string
|
|
2
4
|
|
|
3
5
|
|
|
4
6
|
def jinja_replace(s, config, relaxed: bool = False, delim: tuple[str, str] = ("{{", "}}")):
|
|
@@ -20,3 +22,21 @@ def jinja_replace(s, config, relaxed: bool = False, delim: tuple[str, str] = ("{
|
|
|
20
22
|
raise KeyError(f"{k} is not in the supplied replacement variables")
|
|
21
23
|
|
|
22
24
|
return re.sub(re.escape(delim[0]) + r"\s*(\w+)\s*" + re.escape(delim[1]), handle_match, s)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def randomsuffix(length: int):
|
|
28
|
+
letters = string.ascii_lowercase
|
|
29
|
+
return "".join(random.choice(letters) for _ in range(length))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def qw(s: str) -> list[str]:
|
|
33
|
+
return s.split()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def split_host(host: str) -> tuple[str | None, int | None]:
|
|
37
|
+
if not host:
|
|
38
|
+
return (None, None)
|
|
39
|
+
res = host.split(":", 1)
|
|
40
|
+
if len(res) == 1:
|
|
41
|
+
return (res[0], None)
|
|
42
|
+
return (res[0], int(res[1]))
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
jwbmisc/__init__.py,sha256=5VXxKCMejjLWaiECh64309WJ2LpHHSULxaGOvCAOzyU,581
|
|
2
|
+
jwbmisc/_version.py,sha256=YRV1ohn6CdKEhsUOmFFMmr5UTjMv4Ydw3WJGxF2BHBs,704
|
|
3
|
+
jwbmisc/collection.py,sha256=mICHxBFfk2XY8ajdWkLvmQF1R6oo6d0torpRAeDvd1c,663
|
|
4
|
+
jwbmisc/exec.py,sha256=9g1Jc7iDkBj1Y-dn5VhnwH1JqvWSbrFe6Mmvnf-iqag,1177
|
|
5
|
+
jwbmisc/fs.py,sha256=Vf28qbOnBeHEbXNMUZjOQXtMWBurjkzD2KmfV2gJQXM,599
|
|
6
|
+
jwbmisc/interactive.py,sha256=qMgpQNHvUg4AYw-aAAy2qdxri1aKr-mffoLfUgh8TWE,423
|
|
7
|
+
jwbmisc/json.py,sha256=h3CBDNNZjcTDxydViyydPsQufXQLuxqP24wBBAT2nDs,1198
|
|
8
|
+
jwbmisc/keeper.py,sha256=Fj9k8vWpfp2UMFiCfghWlnc5UxnkcR0oBQgY7NiJhsM,4241
|
|
9
|
+
jwbmisc/passwd.py,sha256=pTa0Lo7Qq10oo7PDlbq8lS3f5IaMFspKB27QI1NQB5k,2593
|
|
10
|
+
jwbmisc/string.py,sha256=0_AvtAyXUlyVL6yw1iMjs3vMunubTTTWS0PSu_qKgi8,1232
|
|
11
|
+
jwbmisc-0.0.5.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
12
|
+
jwbmisc-0.0.5.dist-info/METADATA,sha256=PEKl-YazmyJFlnl8uAYVHcpUhPbyUWp4L5ly1eEf_T4,14507
|
|
13
|
+
jwbmisc-0.0.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
14
|
+
jwbmisc-0.0.5.dist-info/top_level.txt,sha256=FqEYs8zdG3iGOJmC6cutDXfGQUNptzfYeKsaG43y1HE,8
|
|
15
|
+
jwbmisc-0.0.5.dist-info/RECORD,,
|
jwbmisc/util.py
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import random
|
|
2
|
-
import string
|
|
3
|
-
from typing import Any
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def ask(question, default=None):
|
|
7
|
-
if default is not None:
|
|
8
|
-
question += f" [{default}]"
|
|
9
|
-
answer = input(question.strip() + " ").strip()
|
|
10
|
-
return answer if answer else default
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def confirm(question, default="n"):
|
|
14
|
-
prompt = f"{question} (y/n)"
|
|
15
|
-
if default is not None:
|
|
16
|
-
prompt += f" [{default}]"
|
|
17
|
-
answer = input(prompt).strip().lower()
|
|
18
|
-
if not answer:
|
|
19
|
-
answer = default.lower() if default else "n"
|
|
20
|
-
return answer.startswith("y")
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def randomsuffix(length):
|
|
24
|
-
letters = string.ascii_lowercase
|
|
25
|
-
return "".join(random.choice(letters) for _ in range(length))
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def qw(s: str) -> list[str]:
|
|
29
|
-
return s.split()
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def split_host(host: str) -> tuple[str | None, int | None]:
|
|
33
|
-
if not host:
|
|
34
|
-
return (None, None)
|
|
35
|
-
res = host.split(":", 1)
|
|
36
|
-
if len(res) == 1:
|
|
37
|
-
return (res[0], None)
|
|
38
|
-
return (res[0], int(res[1]))
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def goo(
|
|
42
|
-
d: dict[str, Any],
|
|
43
|
-
*keys: str | int,
|
|
44
|
-
default: Any | None = None,
|
|
45
|
-
raise_on_default: bool = False,
|
|
46
|
-
):
|
|
47
|
-
path = ".".join(str(k) for k in keys)
|
|
48
|
-
parts = path.split(".")
|
|
49
|
-
|
|
50
|
-
res = d
|
|
51
|
-
for p in parts:
|
|
52
|
-
if res is None:
|
|
53
|
-
if raise_on_default:
|
|
54
|
-
raise ValueError(f"'{path}' does not exist")
|
|
55
|
-
return default
|
|
56
|
-
if isinstance(res, (list, set, tuple)):
|
|
57
|
-
res = res[int(p)]
|
|
58
|
-
else:
|
|
59
|
-
res = res.get(p)
|
|
60
|
-
if res is None:
|
|
61
|
-
if raise_on_default:
|
|
62
|
-
raise ValueError(f"'{path}' does not exist")
|
|
63
|
-
return default
|
|
64
|
-
return res
|
jwbmisc-0.0.3.dist-info/RECORD
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
jwbmisc/__init__.py,sha256=VT_f1aG3ufoT7mYV1Ho0ocNtydMTIPbmcvOANwQE-CQ,551
|
|
2
|
-
jwbmisc/_version.py,sha256=pBZsQt6tlL02W-ri--X_4JCubpAK7jjCSnOmUp_isjc,704
|
|
3
|
-
jwbmisc/exec.py,sha256=9g1Jc7iDkBj1Y-dn5VhnwH1JqvWSbrFe6Mmvnf-iqag,1177
|
|
4
|
-
jwbmisc/fs.py,sha256=Vf28qbOnBeHEbXNMUZjOQXtMWBurjkzD2KmfV2gJQXM,599
|
|
5
|
-
jwbmisc/json.py,sha256=h3CBDNNZjcTDxydViyydPsQufXQLuxqP24wBBAT2nDs,1198
|
|
6
|
-
jwbmisc/keeper.py,sha256=nOOfzoAPF7Uy3TuMPPdBdq9DvwNanyrQp6lVFI66LnU,4317
|
|
7
|
-
jwbmisc/passwd.py,sha256=ZcOIT_RrwDR3uZgrcWASRLz8D6gf6iAHOEdyfwTuX4I,2520
|
|
8
|
-
jwbmisc/string.py,sha256=pUNAMaP5531mBy3mrp9GCZ3QRPy6MQwegt5yzcWP1wQ,795
|
|
9
|
-
jwbmisc/util.py,sha256=XCF5LEOLjI4IsYP-FF2ytbzo4J0rlCNJzrdNrtNWSGM,1568
|
|
10
|
-
jwbmisc-0.0.3.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
11
|
-
jwbmisc-0.0.3.dist-info/METADATA,sha256=U6WTkWiesNELCpjGh0Myaj-Uzd6To1qw7K28--fAWkU,14507
|
|
12
|
-
jwbmisc-0.0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
13
|
-
jwbmisc-0.0.3.dist-info/top_level.txt,sha256=FqEYs8zdG3iGOJmC6cutDXfGQUNptzfYeKsaG43y1HE,8
|
|
14
|
-
jwbmisc-0.0.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|