s2k-cli 0.1.0__tar.gz → 0.2.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: s2k-cli
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Send local files to your Kindle from the command line.
5
5
  License: MIT
6
6
  Keywords: kindle,send-to-kindle,ebook,email,cli
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "s2k-cli"
3
- version = "0.1.0"
3
+ version = "0.2.0"
4
4
  description = "Send local files to your Kindle from the command line."
5
5
  authors = [{ name = "Marco Fura", email = "mjfura27@gmail.com" }]
6
6
  readme = "README.md"
@@ -33,8 +33,10 @@ def config_file_path() -> Path:
33
33
  override = os.environ.get(CONFIG_FILE_VARIABLE)
34
34
  if override:
35
35
  return Path(override).expanduser()
36
- base = os.environ.get("XDG_CONFIG_HOME") or "~/.config"
37
- return Path(base).expanduser() / "s2k" / "config.env"
36
+ xdg = os.environ.get("XDG_CONFIG_HOME", "")
37
+ # The XDG spec says relative values must be ignored.
38
+ base = Path(xdg) if xdg and Path(xdg).is_absolute() else Path("~/.config").expanduser()
39
+ return base / "s2k" / "config.env"
38
40
 
39
41
 
40
42
  class Settings(BaseSettings):
@@ -43,6 +43,14 @@ def _auth_failure_message(settings: Settings) -> str:
43
43
  return message
44
44
 
45
45
 
46
+ def _security_hint(settings: Settings) -> str:
47
+ if settings.smtp_port == 465 and settings.smtp_security == "starttls":
48
+ return " (port 465 normally needs S2K_SMTP_SECURITY=ssl)"
49
+ if settings.smtp_port == 587 and settings.smtp_security == "ssl":
50
+ return " (port 587 normally needs S2K_SMTP_SECURITY=starttls)"
51
+ return ""
52
+
53
+
46
54
  def _close_quietly(smtp: smtplib.SMTP | None) -> None:
47
55
  if smtp is not None:
48
56
  smtp.close()
@@ -112,6 +120,7 @@ class KindleMailer:
112
120
  _close_quietly(smtp)
113
121
  raise SmtpConnectionError(
114
122
  f"could not connect to {settings.smtp_host}:{settings.smtp_port}: {error}"
123
+ f"{_security_hint(settings)}"
115
124
  ) from error
116
125
  self._smtp = smtp
117
126
  return self
@@ -46,7 +46,11 @@ def _quote(value: str) -> str:
46
46
 
47
47
 
48
48
  def write_config(path: Path, values: dict[str, str]) -> None:
49
- """Atomically write ``values`` as KEY="value" lines to ``path`` (mode 600, new dirs 700)."""
49
+ """Atomically write ``values`` as KEY="value" lines to ``path`` (mode 600, new dirs 700).
50
+
51
+ A symlinked config file (e.g. managed by a dotfiles repo) is kept: its target is rewritten.
52
+ """
53
+ path = path.resolve() if path.is_symlink() else path
50
54
  path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
51
55
  content = HEADER + "".join(f"{key}={_quote(value)}\n" for key, value in values.items())
52
56
  fd, temp_name = tempfile.mkstemp(dir=path.parent, prefix=".config.env.")
@@ -79,16 +83,24 @@ def _ask(text: str, adapter: TypeAdapter[Any], default: str | None) -> str:
79
83
 
80
84
 
81
85
  def _ask_password(has_current: bool) -> str | None:
82
- """Return the new password, or None to keep the current one."""
83
- if has_current:
84
- answer = typer.prompt(
85
- "SMTP password (empty keeps the current one)",
86
- default="",
87
- hide_input=True,
88
- show_default=False,
89
- )
90
- return str(answer) or None
91
- return str(typer.prompt("SMTP password", hide_input=True))
86
+ """Return the new password (typed twice), or None to keep the current one."""
87
+ while True:
88
+ if has_current:
89
+ answer = str(
90
+ typer.prompt(
91
+ "SMTP password (empty keeps the current one)",
92
+ default="",
93
+ hide_input=True,
94
+ show_default=False,
95
+ )
96
+ )
97
+ if not answer:
98
+ return None
99
+ else:
100
+ answer = str(typer.prompt("SMTP password", hide_input=True))
101
+ if str(typer.prompt("Repeat the SMTP password", hide_input=True)) == answer:
102
+ return answer
103
+ typer.secho(" The passwords do not match; try again.", fg=typer.colors.YELLOW)
92
104
 
93
105
 
94
106
  def _collect(current: dict[str, str]) -> dict[str, str]:
File without changes
File without changes