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 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 .util import goo, ask, confirm, randomsuffix, qw, split_host
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.3'
32
- __version_tuple__ = version_tuple = (0, 0, 3)
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 as keeper_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 .util import ask
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
- class _MinimalKeeperUI:
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 _extract_keeper_field(record, field: str) -> str | None:
69
- if isinstance(record, keeper_vault.TypedRecord):
70
- value = record.get_typed_field(field)
71
- if value is None:
72
- value = next((f for f in record.custom if f.label == field), None)
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
- if value and value.value:
75
- return value.value[0] if isinstance(value.value, list) else str(value.value)
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 _perform_keeper_login(params):
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=_MinimalKeeperUI())
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 get_keeper_password(record_uid: str, field_path: str) -> str:
107
- config_file = Path.home() / ".config" / "keeper" / "config.json"
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(config_file))
111
+ params = KeeperParams(config_filename=str(CONFIG_FILE))
111
112
 
112
- if config_file.exists():
113
+ if CONFIG_FILE.exists():
113
114
  try:
114
- params.config = json.loads(config_file.read_text())
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
- _perform_keeper_login(params)
120
+ perform_login(params)
120
121
  else:
121
- _perform_keeper_login(params)
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 = keeper_vault.KeeperRecord.load(params, record_uid)
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 = _extract_keeper_field(record, field_path)
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(["pass", "show", key], stdout=sp.PIPE, stderr=sp.PIPE, encoding="utf-8")
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 get_keeper_password
75
+ from .keeper import get_password as keeper_get_password
74
76
 
75
- return get_keeper_password(record_uid, field_path)
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]))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jwbmisc
3
- Version: 0.0.3
3
+ Version: 0.0.5
4
4
  Summary: Misc util functions of jwb
5
5
  Author-email: Joachim Bargsten <jw@bargsten.org>
6
6
  License: Apache License
@@ -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
@@ -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,,