convert-docs 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,8 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: convert-docs
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Recursively convert documents (pdf, docx, pptx, xlsx, ...) to Markdown via markitdown, mirroring the source folder structure into a destination folder.
5
- Requires-Python: >=3.9
5
+ Requires-Python: >=3.10
6
6
  Requires-Dist: markitdown[docx,outlook,pdf,pptx,xls,xlsx]>=0.1.5
7
7
  Description-Content-Type: text/markdown
8
8
 
@@ -14,11 +14,23 @@ structure into a destination folder.
14
14
 
15
15
  ## Install
16
16
 
17
+ Requires [uv](https://docs.astral.sh/uv/). If you don't have it yet:
18
+
19
+ ```bash
20
+ # macOS / Linux
21
+ curl -LsSf https://astral.sh/uv/install.sh | sh
22
+
23
+ # Windows (PowerShell)
24
+ powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
25
+ ```
26
+
27
+ Then install the tool:
28
+
17
29
  ```bash
18
30
  uv tool install convert-docs
19
31
  ```
20
32
 
21
- (Or, before it's published to PyPI: `uv tool install git+https://github.com/owenljy/convert-docs`)
33
+ No `uv`? Plain `pip` also works, as long as it's Python 3.10+: `pip install convert-docs`.
22
34
 
23
35
  ## Usage
24
36
 
@@ -34,6 +46,7 @@ Or pass flags to skip the prompts:
34
46
  convert-docs -s ~/Documents/Reports -o ~/Documents/Reports_MD
35
47
  convert-docs -e pdf,docx # restrict which extensions get converted
36
48
  convert-docs -f # force re-conversion, ignoring the up-to-date skip
49
+ convert-docs --last # re-run with the same source/destination/extensions as last time
37
50
  ```
38
51
 
39
52
  | Flag | Description |
@@ -42,10 +55,20 @@ convert-docs -f # force re-conversion, ignoring the up-to-date s
42
55
  | `-o, --output` | Output directory, mirrors source structure (skips the interactive prompt) |
43
56
  | `-e, --ext` | Comma-separated extensions to convert |
44
57
  | `-f, --force` | Force re-conversion even if output `.md` already exists and is up to date |
58
+ | `-l, --last` | Reuse the source, destination, and extensions from the last run (skips prompts; cannot be combined with `-s`/`-o`) |
45
59
 
46
60
  Files with unsupported extensions are listed at the end instead of being silently
47
61
  skipped, and any conversion failures are reported with the underlying error.
48
62
 
63
+ ### Re-running on new files
64
+
65
+ Every run saves its source/destination/extensions to `~/.config/convert-docs/last_run.json`.
66
+ Since conversion already skips files whose output is up to date, `convert-docs --last` is a
67
+ cheap way to pick up newly added files in a folder you've converted before — run it manually
68
+ whenever you want to sync, or wire it into a cron job / scheduled task on whatever interval
69
+ suits you (just point it at the tool's full path, e.g. `~/.local/bin/convert-docs --last`,
70
+ since scheduled jobs don't load your shell profile).
71
+
49
72
  ## Update
50
73
 
51
74
  ```bash
@@ -6,11 +6,23 @@ structure into a destination folder.
6
6
 
7
7
  ## Install
8
8
 
9
+ Requires [uv](https://docs.astral.sh/uv/). If you don't have it yet:
10
+
11
+ ```bash
12
+ # macOS / Linux
13
+ curl -LsSf https://astral.sh/uv/install.sh | sh
14
+
15
+ # Windows (PowerShell)
16
+ powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
17
+ ```
18
+
19
+ Then install the tool:
20
+
9
21
  ```bash
10
22
  uv tool install convert-docs
11
23
  ```
12
24
 
13
- (Or, before it's published to PyPI: `uv tool install git+https://github.com/owenljy/convert-docs`)
25
+ No `uv`? Plain `pip` also works, as long as it's Python 3.10+: `pip install convert-docs`.
14
26
 
15
27
  ## Usage
16
28
 
@@ -26,6 +38,7 @@ Or pass flags to skip the prompts:
26
38
  convert-docs -s ~/Documents/Reports -o ~/Documents/Reports_MD
27
39
  convert-docs -e pdf,docx # restrict which extensions get converted
28
40
  convert-docs -f # force re-conversion, ignoring the up-to-date skip
41
+ convert-docs --last # re-run with the same source/destination/extensions as last time
29
42
  ```
30
43
 
31
44
  | Flag | Description |
@@ -34,10 +47,20 @@ convert-docs -f # force re-conversion, ignoring the up-to-date s
34
47
  | `-o, --output` | Output directory, mirrors source structure (skips the interactive prompt) |
35
48
  | `-e, --ext` | Comma-separated extensions to convert |
36
49
  | `-f, --force` | Force re-conversion even if output `.md` already exists and is up to date |
50
+ | `-l, --last` | Reuse the source, destination, and extensions from the last run (skips prompts; cannot be combined with `-s`/`-o`) |
37
51
 
38
52
  Files with unsupported extensions are listed at the end instead of being silently
39
53
  skipped, and any conversion failures are reported with the underlying error.
40
54
 
55
+ ### Re-running on new files
56
+
57
+ Every run saves its source/destination/extensions to `~/.config/convert-docs/last_run.json`.
58
+ Since conversion already skips files whose output is up to date, `convert-docs --last` is a
59
+ cheap way to pick up newly added files in a folder you've converted before — run it manually
60
+ whenever you want to sync, or wire it into a cron job / scheduled task on whatever interval
61
+ suits you (just point it at the tool's full path, e.g. `~/.local/bin/convert-docs --last`,
62
+ since scheduled jobs don't load your shell profile).
63
+
41
64
  ## Update
42
65
 
43
66
  ```bash
@@ -1,9 +1,9 @@
1
1
  [project]
2
2
  name = "convert-docs"
3
- version = "0.1.0"
3
+ version = "0.2.0"
4
4
  description = "Recursively convert documents (pdf, docx, pptx, xlsx, ...) to Markdown via markitdown, mirroring the source folder structure into a destination folder."
5
5
  readme = "README.md"
6
- requires-python = ">=3.9"
6
+ requires-python = ">=3.10"
7
7
  dependencies = [
8
8
  "markitdown[pdf,docx,pptx,xlsx,xls,outlook]>=0.1.5",
9
9
  ]
@@ -1,6 +1,8 @@
1
1
  """Recursively convert documents to Markdown via markitdown, mirroring folder structure."""
2
2
 
3
3
  import argparse
4
+ import json
5
+ import os
4
6
  import sys
5
7
  from pathlib import Path
6
8
 
@@ -12,6 +14,45 @@ DEFAULT_EXTENSIONS = [
12
14
  SEPARATOR = "-" * 40
13
15
 
14
16
 
17
+ def config_dir() -> Path:
18
+ base = os.environ.get("XDG_CONFIG_HOME")
19
+ return (Path(base) if base else Path.home() / ".config") / "convert-docs"
20
+
21
+
22
+ def last_run_path() -> Path:
23
+ return config_dir() / "last_run.json"
24
+
25
+
26
+ def save_last_run(src_dir: Path, dest_dir: Path, extensions: list) -> None:
27
+ path = last_run_path()
28
+ path.parent.mkdir(parents=True, exist_ok=True)
29
+ path.write_text(json.dumps({
30
+ "source": str(src_dir),
31
+ "destination": str(dest_dir),
32
+ "extensions": extensions,
33
+ }, indent=2))
34
+
35
+
36
+ def load_last_run() -> dict:
37
+ path = last_run_path()
38
+ if not path.exists():
39
+ print(
40
+ "Error: no previous run found. Run convert-docs normally first "
41
+ "(with -s/-o or interactively) before using --last.",
42
+ file=sys.stderr,
43
+ )
44
+ sys.exit(1)
45
+ try:
46
+ return json.loads(path.read_text())
47
+ except (json.JSONDecodeError, OSError) as exc:
48
+ print(f"Error: could not read saved config at {path}: {exc}", file=sys.stderr)
49
+ sys.exit(1)
50
+
51
+
52
+ def parse_extensions(raw: str) -> list:
53
+ return [e.strip().lower().lstrip(".") for e in raw.split(",") if e.strip()]
54
+
55
+
15
56
  def strip_quotes(text: str) -> str:
16
57
  if len(text) >= 2 and text[0] == text[-1] and text[0] in ("'", '"'):
17
58
  return text[1:-1]
@@ -51,14 +92,20 @@ def parse_args(argv=None) -> argparse.Namespace:
51
92
  )
52
93
  parser.add_argument(
53
94
  "-e", "--ext",
54
- default=",".join(DEFAULT_EXTENSIONS),
55
- help="Comma-separated extensions to convert (default: %(default)s)",
95
+ default=None,
96
+ help=f"Comma-separated extensions to convert (default: {','.join(DEFAULT_EXTENSIONS)})",
56
97
  )
57
98
  parser.add_argument(
58
99
  "-f", "--force",
59
100
  action="store_true",
60
101
  help="Force re-conversion even if output .md already exists and is up to date",
61
102
  )
103
+ parser.add_argument(
104
+ "-l", "--last",
105
+ action="store_true",
106
+ help="Reuse the source, destination, and extensions from the last run "
107
+ "(skips prompts; cannot be combined with -s/-o)",
108
+ )
62
109
  return parser.parse_args(argv)
63
110
 
64
111
 
@@ -124,16 +171,32 @@ def convert_files(target_files: list, src_dir: Path, dest_dir: Path, force: bool
124
171
 
125
172
  def main(argv=None) -> int:
126
173
  args = parse_args(argv)
127
- extensions = [e.strip().lower().lstrip(".") for e in args.ext.split(",") if e.strip()]
128
174
 
129
- src_dir = resolve_source(args)
130
- dest_dir = resolve_destination(args, src_dir)
175
+ if args.last and (args.source or args.output):
176
+ print("Error: --last cannot be combined with -s/--source or -o/--output.", file=sys.stderr)
177
+ return 1
178
+
179
+ if args.last:
180
+ last = load_last_run()
181
+ src_dir = Path(last["source"])
182
+ if not src_dir.is_dir():
183
+ print(f"Error: last-used source directory no longer exists: {src_dir}", file=sys.stderr)
184
+ return 1
185
+ src_dir = src_dir.resolve()
186
+ dest_dir = Path(last["destination"])
187
+ extensions = parse_extensions(args.ext) if args.ext else last.get("extensions", DEFAULT_EXTENSIONS)
188
+ else:
189
+ extensions = parse_extensions(args.ext) if args.ext else DEFAULT_EXTENSIONS
190
+ src_dir = resolve_source(args)
191
+ dest_dir = resolve_destination(args, src_dir)
131
192
 
132
193
  if not dest_dir.exists():
133
194
  print(f"Destination does not exist, creating: {dest_dir}")
134
195
  dest_dir.mkdir(parents=True, exist_ok=True)
135
196
  dest_dir = dest_dir.resolve()
136
197
 
198
+ save_last_run(src_dir, dest_dir, extensions)
199
+
137
200
  print(SEPARATOR)
138
201
  print(f"Source folder: {src_dir}")
139
202
  print(f"Destination folder: {dest_dir}")
File without changes