conver 0.1.0__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.
- conver/__init__.py +25 -0
- conver/__version__.py +1 -0
- conver/_convert.py +193 -0
- conver/cli.py +90 -0
- conver/conver.py +154 -0
- conver/scripts/convert.jxa +229 -0
- conver/scripts/convert.ps1 +190 -0
- conver-0.1.0.dist-info/METADATA +353 -0
- conver-0.1.0.dist-info/RECORD +13 -0
- conver-0.1.0.dist-info/WHEEL +5 -0
- conver-0.1.0.dist-info/entry_points.txt +2 -0
- conver-0.1.0.dist-info/licenses/LICENSE +20 -0
- conver-0.1.0.dist-info/top_level.txt +1 -0
conver/__init__.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Convenient public API for the conver package.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .conver import conver
|
|
6
|
+
from .conver import ConverError
|
|
7
|
+
from .conver import InputFileNotFound
|
|
8
|
+
from .conver import UnsupportedFormat
|
|
9
|
+
from .conver import WordStartError
|
|
10
|
+
from .conver import SaveError
|
|
11
|
+
from .conver import IPCError
|
|
12
|
+
from .conver import PlatformNotSupported
|
|
13
|
+
from .__version__ import __version__
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"conver",
|
|
17
|
+
"ConverError",
|
|
18
|
+
"InputFileNotFound",
|
|
19
|
+
"UnsupportedFormat",
|
|
20
|
+
"WordStartError",
|
|
21
|
+
"SaveError",
|
|
22
|
+
"IPCError",
|
|
23
|
+
"PlatformNotSupported",
|
|
24
|
+
"__version__",
|
|
25
|
+
]
|
conver/__version__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
conver/_convert.py
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Author: Timur Ulyahin, https://github.com/ucomru
|
|
2
|
+
# License: MIT – provided "as-is" without any warranty or liability
|
|
3
|
+
# Copyright: (c) 2024 Timur Ulyahin
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
MODULE OVERVIEW:
|
|
7
|
+
This module implements the low-level IPC-based `convert` function used to perform
|
|
8
|
+
document format conversion by invoking platform-specific scripts for macOS (JXA)
|
|
9
|
+
and Windows (PowerShell).
|
|
10
|
+
|
|
11
|
+
It serves as the internal transport layer between Python and the operating-system
|
|
12
|
+
automation scripts, enabling the conversion of Microsoft Word documents (.doc, .docx)
|
|
13
|
+
and other supported formats into output formats such as PDF, RTF, TXT, HTML, and more.
|
|
14
|
+
|
|
15
|
+
The function returns structured information (status, message, error_code) that
|
|
16
|
+
is interpreted by the high-level `conver()` API, which converts these results
|
|
17
|
+
into Python exceptions on failure.
|
|
18
|
+
|
|
19
|
+
USAGE:
|
|
20
|
+
To use the `convert` function, specify the `input_path`, `output_path`, and optionally
|
|
21
|
+
`keep_open` to indicate whether the application should remain open post-conversion.
|
|
22
|
+
|
|
23
|
+
RECOMMENDED PATHS:
|
|
24
|
+
Use paths like "~/Downloads/" on macOS or "C:/Users/YourName/Downloads/" on Windows
|
|
25
|
+
for both `input_path` and `output_path` to ensure permission consistency.
|
|
26
|
+
|
|
27
|
+
SETUP (macOS):
|
|
28
|
+
1. Go to System Settings > Privacy & Security > Files and Folders.
|
|
29
|
+
2. Under Microsoft Word or Microsoft Excel, enable access to "Downloads/" if required.
|
|
30
|
+
|
|
31
|
+
SUPPORTED FORMATS:
|
|
32
|
+
The function supports conversion of the following file extensions:
|
|
33
|
+
- Microsoft Word (.docx) // Modern Word document format
|
|
34
|
+
- Word 97-2003 (.doc) // Legacy Word document format
|
|
35
|
+
- PDF (.pdf) // Export documents to PDF format
|
|
36
|
+
- Rich Text Format (.rtf) // Basic document format with limited formatting
|
|
37
|
+
- OpenDocument Text (.odt) // OpenOffice/LibreOffice document format
|
|
38
|
+
- Plain Text (.txt) // Plain text format without any formatting
|
|
39
|
+
- HTML (.html) // Web page format for viewing in browsers
|
|
40
|
+
|
|
41
|
+
STATUS SCHEMA (ConvertResult):
|
|
42
|
+
The function returns a structured dictionary as follows:
|
|
43
|
+
{
|
|
44
|
+
"status": "success" | "error", // Execution status
|
|
45
|
+
"input": "<path to input file>", // Path to the input file (may be null in error cases)
|
|
46
|
+
"output": "<path to output file>", // Path to the output file (may be null in error cases)
|
|
47
|
+
"message": "OK" | "<error message>", // "OK" on success or specific error message
|
|
48
|
+
"error_code": 0 | <error code> // 0 for success; specific code for different errors
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
ERROR CODES:
|
|
52
|
+
The `convert` function and associated scripts define a set of error codes for troubleshooting:
|
|
53
|
+
0 - Success
|
|
54
|
+
1 - Incorrect JSON format or missing required fields
|
|
55
|
+
2 - Input file format is unsupported
|
|
56
|
+
3 - Output file format is unsupported
|
|
57
|
+
11 - Input file not found
|
|
58
|
+
21 - Microsoft Word or Excel did not start within the expected time
|
|
59
|
+
31 - Error saving or converting file
|
|
60
|
+
98 - Invalid JSON output from script
|
|
61
|
+
99 - Unsupported platform
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
import subprocess
|
|
65
|
+
from json import loads, dumps, JSONDecodeError
|
|
66
|
+
import sys
|
|
67
|
+
from importlib.resources import files, as_file
|
|
68
|
+
from typing import TypedDict, Optional, List
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class ConvertResult(TypedDict):
|
|
72
|
+
status: str
|
|
73
|
+
input: Optional[str]
|
|
74
|
+
output: Optional[str]
|
|
75
|
+
message: str
|
|
76
|
+
error_code: int
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def convert(
|
|
80
|
+
input_path: str, output_path: str, keep_open: bool = False
|
|
81
|
+
) -> ConvertResult:
|
|
82
|
+
"""
|
|
83
|
+
Convert a document from one format to another using platform-specific scripts.
|
|
84
|
+
|
|
85
|
+
This function acts as an interface for inter-process communication (IPC) with macOS
|
|
86
|
+
or Windows scripts to perform document conversions. Depending on the operating system,
|
|
87
|
+
it triggers a JXA or PowerShell script, which performs the conversion and returns a
|
|
88
|
+
structured JSON result. The `convert` function processes the JSON result, ensuring
|
|
89
|
+
it matches the strict structure defined by `ConvertResult`.
|
|
90
|
+
|
|
91
|
+
Parameters:
|
|
92
|
+
input_path (str): The path to the source file.
|
|
93
|
+
output_path (str): The path where the converted file will be saved.
|
|
94
|
+
keep_open (bool): Whether to keep the application (Word/Excel) open after processing.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
ConvertResult: A dictionary with keys for `status`, `input`, `output`, `message`, and
|
|
98
|
+
`error_code`, providing details of the conversion process and any errors encountered.
|
|
99
|
+
|
|
100
|
+
Errors:
|
|
101
|
+
Returns structured error responses as defined by the ConvertResult type. Unexpected errors
|
|
102
|
+
that are not captured by the script will return error_code 98 for JSON parsing failures.
|
|
103
|
+
"""
|
|
104
|
+
if sys.platform == "darwin":
|
|
105
|
+
return _run_macos_script(input_path, output_path, keep_open)
|
|
106
|
+
elif sys.platform == "win32":
|
|
107
|
+
return _run_windows_script(input_path, output_path, keep_open)
|
|
108
|
+
else:
|
|
109
|
+
# Unsupported platform
|
|
110
|
+
return {
|
|
111
|
+
"status": "error",
|
|
112
|
+
"input": input_path,
|
|
113
|
+
"output": output_path,
|
|
114
|
+
"message": "Unsupported platform.",
|
|
115
|
+
"error_code": 99,
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _run_macos_script(
|
|
120
|
+
input_path: str, output_path: str, keep_open: bool
|
|
121
|
+
) -> ConvertResult:
|
|
122
|
+
"""Run macOS JXA script for conversion."""
|
|
123
|
+
with as_file(files("conver.scripts").joinpath("convert.jxa")) as script_path:
|
|
124
|
+
command = [
|
|
125
|
+
"osascript",
|
|
126
|
+
"-l",
|
|
127
|
+
"JavaScript",
|
|
128
|
+
str(script_path),
|
|
129
|
+
dumps({"input": input_path, "output": output_path, "keepOpen": keep_open}),
|
|
130
|
+
]
|
|
131
|
+
return _execute_command(command, input_path, output_path)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _run_windows_script(
|
|
135
|
+
input_path: str, output_path: str, keep_open: bool
|
|
136
|
+
) -> ConvertResult:
|
|
137
|
+
"""Run Windows PowerShell script for conversion."""
|
|
138
|
+
with as_file(files("conver.scripts").joinpath("convert.ps1")) as script_path:
|
|
139
|
+
command = [
|
|
140
|
+
"powershell",
|
|
141
|
+
"-ExecutionPolicy",
|
|
142
|
+
"Bypass",
|
|
143
|
+
"-File",
|
|
144
|
+
str(script_path),
|
|
145
|
+
"-jsonArgs",
|
|
146
|
+
dumps({"input": input_path, "output": output_path, "keepOpen": keep_open}),
|
|
147
|
+
]
|
|
148
|
+
return _execute_command(command, input_path, output_path)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _execute_command(
|
|
152
|
+
command: List[str], input_path: str, output_path: str
|
|
153
|
+
) -> ConvertResult:
|
|
154
|
+
"""
|
|
155
|
+
Executes the command for either macOS or Windows and parses the JSON output.
|
|
156
|
+
|
|
157
|
+
Attempts to run the provided script command and expects JSON output for successful completion.
|
|
158
|
+
In cases where the script exits with an error, the `error_code` from the script’s JSON response
|
|
159
|
+
is returned directly to help identify specific issues. If JSON parsing fails, a general error
|
|
160
|
+
code of 98 is returned.
|
|
161
|
+
|
|
162
|
+
Parameters:
|
|
163
|
+
command (List[str]): The command to be executed.
|
|
164
|
+
input_path (str): The path to the input file, passed for error context.
|
|
165
|
+
output_path (str): The path to the output file, passed for error context.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
ConvertResult: A structured dictionary with status, message, error code, input, and output.
|
|
169
|
+
"""
|
|
170
|
+
result = subprocess.run(command, capture_output=True, text=True)
|
|
171
|
+
|
|
172
|
+
# osascript places all output in stderr, including success
|
|
173
|
+
raw = result.stdout.strip() or result.stderr.strip()
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
data = loads(raw)
|
|
177
|
+
except JSONDecodeError:
|
|
178
|
+
return {
|
|
179
|
+
"status": "error",
|
|
180
|
+
"input": input_path,
|
|
181
|
+
"output": output_path,
|
|
182
|
+
"message": "Invalid JSON output from script.",
|
|
183
|
+
"error_code": 98,
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
# canonical normalized result
|
|
187
|
+
return {
|
|
188
|
+
"status": data.get("status", "error"),
|
|
189
|
+
"input": data.get("input", input_path),
|
|
190
|
+
"output": data.get("output", output_path),
|
|
191
|
+
"message": data.get("message", ""),
|
|
192
|
+
"error_code": data.get("error_code", result.returncode),
|
|
193
|
+
}
|
conver/cli.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from click import (
|
|
5
|
+
command,
|
|
6
|
+
argument,
|
|
7
|
+
option,
|
|
8
|
+
version_option,
|
|
9
|
+
echo,
|
|
10
|
+
Path as ClickPath,
|
|
11
|
+
UsageError,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from .conver import conver, ConverError
|
|
15
|
+
from .__version__ import __version__
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def fail(label: str, err: Exception, code: int = 1):
|
|
19
|
+
echo(f"{label} {err}", err=True)
|
|
20
|
+
sys.exit(code)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@command(help="Convert Word documents.")
|
|
24
|
+
@argument(
|
|
25
|
+
"inputs", type=ClickPath(exists=True, path_type=Path), nargs=-1, metavar="INPUT..."
|
|
26
|
+
)
|
|
27
|
+
@option(
|
|
28
|
+
"-o",
|
|
29
|
+
"--output",
|
|
30
|
+
"output",
|
|
31
|
+
type=ClickPath(path_type=Path),
|
|
32
|
+
required=False,
|
|
33
|
+
help="Output file or directory (for multiple inputs).",
|
|
34
|
+
)
|
|
35
|
+
@option("-p", "--pdf", "target", flag_value="pdf", help="Convert to PDF.")
|
|
36
|
+
@option("-d", "--docx", "target", flag_value="docx", help="Convert to DOCX.")
|
|
37
|
+
@option("-r", "--rtf", "target", flag_value="rtf", help="Convert to RTF.")
|
|
38
|
+
@option("-t", "--txt", "target", flag_value="txt", help="Convert to TXT.")
|
|
39
|
+
@option("-h", "--html", "target", flag_value="html", help="Convert to HTML.")
|
|
40
|
+
@option("-k", "--keep-open", is_flag=True, help="Keep Microsoft Word open.")
|
|
41
|
+
@version_option(__version__, "-V", "--version")
|
|
42
|
+
def cli(inputs, output, target, keep_open):
|
|
43
|
+
"""Main CLI entry point."""
|
|
44
|
+
if not inputs:
|
|
45
|
+
raise UsageError("No input files specified.")
|
|
46
|
+
|
|
47
|
+
if output is not None and target is not None:
|
|
48
|
+
raise UsageError("Cannot use both --output and format flags together.")
|
|
49
|
+
|
|
50
|
+
# --- MULTIPLE INPUTS ---
|
|
51
|
+
if len(inputs) > 1:
|
|
52
|
+
if output is None:
|
|
53
|
+
raise UsageError("Multiple inputs require --output DIRECTORY.")
|
|
54
|
+
|
|
55
|
+
if output.exists() and not output.is_dir():
|
|
56
|
+
raise UsageError("--output must be a directory for multiple inputs.")
|
|
57
|
+
|
|
58
|
+
output.mkdir(parents=True, exist_ok=True)
|
|
59
|
+
|
|
60
|
+
if target is None:
|
|
61
|
+
target = "pdf"
|
|
62
|
+
|
|
63
|
+
for inp in inputs:
|
|
64
|
+
out_file = output / (inp.stem + f".{target}")
|
|
65
|
+
try:
|
|
66
|
+
result = conver(inp, out_file, keep_open=keep_open)
|
|
67
|
+
echo(result)
|
|
68
|
+
except ConverError as err:
|
|
69
|
+
fail("Error:", err, err.error_code or 1)
|
|
70
|
+
|
|
71
|
+
# --- SINGLE INPUT ---
|
|
72
|
+
else:
|
|
73
|
+
inp = inputs[0]
|
|
74
|
+
|
|
75
|
+
if output is not None:
|
|
76
|
+
try:
|
|
77
|
+
result = conver(inp, output, keep_open=keep_open)
|
|
78
|
+
echo(result)
|
|
79
|
+
except ConverError as err:
|
|
80
|
+
fail("Error:", err, err.error_code or 1)
|
|
81
|
+
else:
|
|
82
|
+
if target is None:
|
|
83
|
+
target = "pdf"
|
|
84
|
+
|
|
85
|
+
out_file = inp.with_suffix("." + target)
|
|
86
|
+
try:
|
|
87
|
+
result = conver(inp, out_file, keep_open=keep_open)
|
|
88
|
+
echo(result)
|
|
89
|
+
except ConverError as err:
|
|
90
|
+
fail("Error:", err, err.error_code or 1)
|
conver/conver.py
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# Author: Timur Ulyahin, https://github.com/ucomru
|
|
2
|
+
# License: MIT – provided "as-is" without any warranty or liability
|
|
3
|
+
# Copyright: (c) 2024 Timur Ulyahin
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
High-level document conversion API.
|
|
7
|
+
|
|
8
|
+
This module exposes the `conver()` function — a Pythonic wrapper around the
|
|
9
|
+
low-level IPC-based converter. It normalizes paths, performs minimal validation,
|
|
10
|
+
invokes the underlying platform-specific script, and raises structured exceptions
|
|
11
|
+
on failure.
|
|
12
|
+
|
|
13
|
+
Successful conversion returns the resolved output path as a `Path` object.
|
|
14
|
+
Errors are represented as subclasses of `ConverError`.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from typing import Union, Tuple
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
from ._convert import convert
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ConverError(Exception):
|
|
24
|
+
def __init__(self, message, error_code=None):
|
|
25
|
+
super().__init__(message)
|
|
26
|
+
self.error_code = error_code
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class InputFileNotFound(ConverError):
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class UnsupportedFormat(ConverError):
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class WordStartError(ConverError):
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class SaveError(ConverError):
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class IPCError(ConverError):
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class PlatformNotSupported(ConverError):
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
_ERROR_MAP = {
|
|
54
|
+
1: IPCError,
|
|
55
|
+
2: UnsupportedFormat,
|
|
56
|
+
3: UnsupportedFormat,
|
|
57
|
+
11: InputFileNotFound,
|
|
58
|
+
21: WordStartError,
|
|
59
|
+
31: SaveError,
|
|
60
|
+
98: IPCError,
|
|
61
|
+
99: PlatformNotSupported,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _normalize_paths(
|
|
66
|
+
input_path: Union[str, Path],
|
|
67
|
+
output_path: Union[str, Path],
|
|
68
|
+
) -> Tuple[Path, Path]:
|
|
69
|
+
"""
|
|
70
|
+
Normalize and resolve input/output paths.
|
|
71
|
+
|
|
72
|
+
Rules:
|
|
73
|
+
- input_path is converted to absolute Path.
|
|
74
|
+
- output_path:
|
|
75
|
+
* if only a filename is provided, it is placed into the same directory
|
|
76
|
+
as input_path;
|
|
77
|
+
* otherwise, it is resolved relative to the filesystem.
|
|
78
|
+
- All returned paths are absolute.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
# Normalize input
|
|
82
|
+
in_path = Path(input_path).expanduser().absolute()
|
|
83
|
+
|
|
84
|
+
# Normalize output raw
|
|
85
|
+
out_raw = Path(output_path).expanduser()
|
|
86
|
+
|
|
87
|
+
# filename-only -> drop into input directory
|
|
88
|
+
if out_raw.parent == Path("."):
|
|
89
|
+
out_path = in_path.parent / out_raw
|
|
90
|
+
else:
|
|
91
|
+
out_path = out_raw
|
|
92
|
+
|
|
93
|
+
out_path = out_path.absolute()
|
|
94
|
+
|
|
95
|
+
return in_path, out_path
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def conver(
|
|
99
|
+
input_path: Union[str, Path],
|
|
100
|
+
output_path: Union[str, Path],
|
|
101
|
+
keep_open: bool = False,
|
|
102
|
+
) -> Path:
|
|
103
|
+
"""
|
|
104
|
+
Convert a document to another format via the platform-level converter.
|
|
105
|
+
|
|
106
|
+
Parameters
|
|
107
|
+
----------
|
|
108
|
+
input_path : str or pathlib.Path
|
|
109
|
+
Path to the source document.
|
|
110
|
+
output_path : str or pathlib.Path
|
|
111
|
+
Target output path with desired extension.
|
|
112
|
+
keep_open : bool, default=False
|
|
113
|
+
Whether to keep Microsoft Word open after conversion.
|
|
114
|
+
|
|
115
|
+
Returns
|
|
116
|
+
-------
|
|
117
|
+
Path
|
|
118
|
+
The resolved absolute output path.
|
|
119
|
+
|
|
120
|
+
Raises
|
|
121
|
+
------
|
|
122
|
+
InputFileNotFound
|
|
123
|
+
The input file does not exist.
|
|
124
|
+
UnsupportedFormat
|
|
125
|
+
The source or target format is not supported by the underlying converter.
|
|
126
|
+
WordStartError
|
|
127
|
+
Microsoft Word could not be started by the script.
|
|
128
|
+
SaveError
|
|
129
|
+
The document could not be saved in the target format.
|
|
130
|
+
IPCError
|
|
131
|
+
Communication with the script failed or produced invalid JSON.
|
|
132
|
+
PlatformNotSupported
|
|
133
|
+
The current OS is not supported.
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
in_path, out_path = _normalize_paths(input_path, output_path)
|
|
137
|
+
|
|
138
|
+
if not in_path.exists():
|
|
139
|
+
raise InputFileNotFound(f"Input file does not exist: {in_path}", 11)
|
|
140
|
+
|
|
141
|
+
result = convert(
|
|
142
|
+
input_path=str(in_path),
|
|
143
|
+
output_path=str(out_path),
|
|
144
|
+
keep_open=keep_open,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
code = result.get("error_code", None)
|
|
148
|
+
msg = result.get("message", "Unknown error")
|
|
149
|
+
|
|
150
|
+
if code and code != 0:
|
|
151
|
+
exc_class = _ERROR_MAP.get(code, ConverError)
|
|
152
|
+
raise exc_class(f"[{code}] {msg}", code)
|
|
153
|
+
|
|
154
|
+
return out_path
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
#!/usr/bin/osascript -l JavaScript
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Author: Timur Ulyahin, https://github.com/ucomru
|
|
5
|
+
License: MIT – provided "as-is" without any warranty or liability
|
|
6
|
+
Copyright: (c) 2024 Timur Ulyahin
|
|
7
|
+
|
|
8
|
+
USAGE:
|
|
9
|
+
osascript -l JavaScript convert.jxa \
|
|
10
|
+
'{"input": "<inputPath>", "output": "<outputPath>", "keepOpen": true | false}'
|
|
11
|
+
|
|
12
|
+
RECOMMENDED PATHS:
|
|
13
|
+
Use "~/Downloads/" for both <inputPath> and <outputPath>.
|
|
14
|
+
|
|
15
|
+
SETUP (macOS):
|
|
16
|
+
1. Go to System Settings > Privacy & Security > Files and Folders
|
|
17
|
+
2. Under Microsoft Word, enable access to "Downloads Folder".
|
|
18
|
+
|
|
19
|
+
SUPPORTED FORMATS:
|
|
20
|
+
The script supports the following file extensions for input and output:
|
|
21
|
+
- Microsoft Word (.docx) // Modern Word document format
|
|
22
|
+
- Word 97-2003 (.doc) // Legacy Word document format
|
|
23
|
+
- PDF (.pdf) // Export documents to PDF format
|
|
24
|
+
- Rich Text Format (.rtf) // Basic document format with limited formatting
|
|
25
|
+
- OpenDocument Text (.odt) // OpenOffice/LibreOffice document format
|
|
26
|
+
- Plain Text (.txt) // Plain text format without any formatting
|
|
27
|
+
- HTML (.html) // Web page format for viewing in browsers
|
|
28
|
+
|
|
29
|
+
PARAMETERS:
|
|
30
|
+
- input: "<path to input file>" // Path to the input file (required)
|
|
31
|
+
- output: "<path to output file>" // Path to the output file (required)
|
|
32
|
+
- keepOpen: true | false // Optional. Leave Word open after processing (default: false)
|
|
33
|
+
|
|
34
|
+
STATUS SCHEMA (JSON Output):
|
|
35
|
+
The script outputs a JSON object with the following fields:
|
|
36
|
+
{
|
|
37
|
+
"status": "success" | "error", // Execution status
|
|
38
|
+
"input": "<path to input file>", // Path to the input file
|
|
39
|
+
"output": "<path to output file>", // Path to the output file (null if error)
|
|
40
|
+
"message": "OK" | "<error message>", // "OK" on success or specific error message
|
|
41
|
+
"error_code": 0 | <error code> // 0 for success; specific code for different errors
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
ERROR CODES:
|
|
45
|
+
0 - Success
|
|
46
|
+
1 - Incorrect JSON format or missing required fields
|
|
47
|
+
2 - Input file format is unsupported
|
|
48
|
+
3 - Output file format is unsupported
|
|
49
|
+
11 - Input file not found
|
|
50
|
+
21 - Microsoft Word did not start within the expected time
|
|
51
|
+
31 - Error saving or converting file
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
ObjC.import("stdlib");
|
|
55
|
+
const SystemEvents = Application("System Events");
|
|
56
|
+
|
|
57
|
+
// Supported file formats with their corresponding codes in Word
|
|
58
|
+
const formatCodes = {
|
|
59
|
+
"docx": 16,
|
|
60
|
+
"doc": 0,
|
|
61
|
+
"pdf": 17,
|
|
62
|
+
"rtf": 6,
|
|
63
|
+
"odt": 19,
|
|
64
|
+
"txt": 7,
|
|
65
|
+
"html": 8
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// Retrieve the format code based on file extension, if supported
|
|
69
|
+
function getFormatCodeByExtension(extension) {
|
|
70
|
+
const ext = extension.toLowerCase();
|
|
71
|
+
return ext in formatCodes ? formatCodes[ext] : null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function run(argv) {
|
|
75
|
+
// Ensure there is only one JSON argument passed to the script
|
|
76
|
+
if (argv.length !== 1) {
|
|
77
|
+
console.log(JSON.stringify({
|
|
78
|
+
"status": "error",
|
|
79
|
+
"input": null,
|
|
80
|
+
"output": null,
|
|
81
|
+
"message": "A single JSON argument is required.",
|
|
82
|
+
"error_code": 1
|
|
83
|
+
}));
|
|
84
|
+
$.exit(1);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let params;
|
|
88
|
+
// Attempt to parse the JSON input
|
|
89
|
+
try {
|
|
90
|
+
params = JSON.parse(argv[0]);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.log(JSON.stringify({
|
|
93
|
+
"status": "error",
|
|
94
|
+
"input": null,
|
|
95
|
+
"output": null,
|
|
96
|
+
"message": "Invalid JSON format.",
|
|
97
|
+
"error_code": 1
|
|
98
|
+
}));
|
|
99
|
+
$.exit(1);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const inputPath = params.input;
|
|
103
|
+
let outputPath = params.output;
|
|
104
|
+
const keepOpen = params.keepOpen === true; // Flag to optionally keep Word open
|
|
105
|
+
|
|
106
|
+
// Validate the presence of required fields
|
|
107
|
+
if (!inputPath || !outputPath) {
|
|
108
|
+
console.log(JSON.stringify({
|
|
109
|
+
"status": "error",
|
|
110
|
+
"input": inputPath || null,
|
|
111
|
+
"output": outputPath || null,
|
|
112
|
+
"message": "Both 'input' and 'output' fields are required in JSON.",
|
|
113
|
+
"error_code": 1
|
|
114
|
+
}));
|
|
115
|
+
$.exit(1);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Check if input file format is supported
|
|
119
|
+
const inputExtension = inputPath.split('.').pop().toLowerCase();
|
|
120
|
+
const inputFormat = getFormatCodeByExtension(inputExtension);
|
|
121
|
+
|
|
122
|
+
if (inputFormat === null) {
|
|
123
|
+
console.log(JSON.stringify({
|
|
124
|
+
"status": "error",
|
|
125
|
+
"input": inputPath,
|
|
126
|
+
"output": null,
|
|
127
|
+
"message": `The input file format "${inputExtension}" is unsupported.`,
|
|
128
|
+
"error_code": 2
|
|
129
|
+
}));
|
|
130
|
+
$.exit(2);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Check if output file format is supported
|
|
134
|
+
const outputExtension = outputPath.split('.').pop().toLowerCase();
|
|
135
|
+
const outputFormat = getFormatCodeByExtension(outputExtension);
|
|
136
|
+
|
|
137
|
+
if (outputFormat === null) {
|
|
138
|
+
console.log(JSON.stringify({
|
|
139
|
+
"status": "error",
|
|
140
|
+
"input": inputPath,
|
|
141
|
+
"output": outputPath,
|
|
142
|
+
"message": `The output file format "${outputExtension}" is unsupported.`,
|
|
143
|
+
"error_code": 3
|
|
144
|
+
}));
|
|
145
|
+
$.exit(3);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const Word = Application("Microsoft Word");
|
|
149
|
+
Word.includeStandardAdditions = true;
|
|
150
|
+
|
|
151
|
+
let wordStartedByScript = false; // Flag to track if Word was started by this script
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
// Check if the input file exists
|
|
155
|
+
const fileManager = $.NSFileManager.defaultManager;
|
|
156
|
+
if (!fileManager.fileExistsAtPath($(inputPath))) {
|
|
157
|
+
console.log(JSON.stringify({
|
|
158
|
+
"status": "error",
|
|
159
|
+
"input": inputPath,
|
|
160
|
+
"output": null,
|
|
161
|
+
"message": `File "${inputPath}" not found.`,
|
|
162
|
+
"error_code": 11
|
|
163
|
+
}));
|
|
164
|
+
$.exit(11);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Activate or launch Word if not already running
|
|
168
|
+
if (!Word.running()) {
|
|
169
|
+
Word.activate();
|
|
170
|
+
wordStartedByScript = true; // Mark that Word was launched by this script
|
|
171
|
+
|
|
172
|
+
let attempt = 0;
|
|
173
|
+
// Wait for Word to start, with a maximum number of attempts
|
|
174
|
+
while (!Word.running() && attempt < 10) {
|
|
175
|
+
delay(0.1);
|
|
176
|
+
attempt++;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Check if Word started successfully
|
|
180
|
+
if (Word.running()) {
|
|
181
|
+
SystemEvents.processes["Microsoft Word"].visible = false;
|
|
182
|
+
} else {
|
|
183
|
+
console.log(JSON.stringify({
|
|
184
|
+
"status": "error",
|
|
185
|
+
"input": inputPath,
|
|
186
|
+
"output": outputPath,
|
|
187
|
+
"message": "Microsoft Word did not start within the expected time.",
|
|
188
|
+
"error_code": 21
|
|
189
|
+
}));
|
|
190
|
+
$.exit(21);
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
Word.launch();
|
|
194
|
+
SystemEvents.processes["Microsoft Word"].visible = false;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Open the document and save it in the desired format
|
|
198
|
+
Word.open(inputPath);
|
|
199
|
+
const doc = Word.documents[0];
|
|
200
|
+
doc.saveAs({
|
|
201
|
+
fileName: outputPath,
|
|
202
|
+
fileFormat: outputFormat
|
|
203
|
+
});
|
|
204
|
+
doc.close({ saving: "no" }); // Close the document without saving any changes
|
|
205
|
+
} catch (error) {
|
|
206
|
+
console.log(JSON.stringify({
|
|
207
|
+
"status": "error",
|
|
208
|
+
"input": inputPath,
|
|
209
|
+
"output": outputPath,
|
|
210
|
+
"message": error.toString(),
|
|
211
|
+
"error_code": 31
|
|
212
|
+
}));
|
|
213
|
+
$.exit(31);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Output success message in JSON format
|
|
217
|
+
console.log(JSON.stringify({
|
|
218
|
+
"status": "success",
|
|
219
|
+
"input": inputPath,
|
|
220
|
+
"output": outputPath,
|
|
221
|
+
"message": "OK",
|
|
222
|
+
"error_code": 0
|
|
223
|
+
}));
|
|
224
|
+
|
|
225
|
+
// Close Word if it was started by this script and keepOpen is false
|
|
226
|
+
if (wordStartedByScript && !keepOpen) {
|
|
227
|
+
Word.quit();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
<#
|
|
2
|
+
Author: Timur Ulyahin, https://github.com/ucomru
|
|
3
|
+
License: MIT – provided "as-is" without any warranty or liability
|
|
4
|
+
Copyright: (c) 2024 Timur Ulyahin
|
|
5
|
+
|
|
6
|
+
USAGE:
|
|
7
|
+
powershell -ExecutionPolicy Bypass -File convert.ps1 -jsonArgs "{\"input\": \"<inputPath>\", \"output\": \"<outputPath>\", \"keepOpen\": true | false}"
|
|
8
|
+
|
|
9
|
+
RECOMMENDED PATHS:
|
|
10
|
+
Use paths like "C:\Users\UserName\Downloads\" for both <inputPath> and <outputPath>.
|
|
11
|
+
|
|
12
|
+
SETUP (Windows):
|
|
13
|
+
1. Ensure Microsoft Word is installed on your system.
|
|
14
|
+
2. If prompted, allow PowerShell to automate Microsoft Word.
|
|
15
|
+
|
|
16
|
+
SUPPORTED FORMATS:
|
|
17
|
+
The script supports the following file extensions for input and output:
|
|
18
|
+
- Microsoft Word (.docx) # Modern Word document format
|
|
19
|
+
- Word 97-2003 (.doc) # Legacy Word document format
|
|
20
|
+
- PDF (.pdf) # Export documents to PDF format
|
|
21
|
+
- Rich Text Format (.rtf) # Basic document format with limited formatting
|
|
22
|
+
- OpenDocument Text (.odt) # OpenOffice/LibreOffice document format
|
|
23
|
+
- Plain Text (.txt) # Plain text format without any formatting
|
|
24
|
+
- HTML (.html) # Web page format for viewing in browsers
|
|
25
|
+
|
|
26
|
+
PARAMETERS:
|
|
27
|
+
- input: "<path to input file>" # Path to the input file (required)
|
|
28
|
+
- output: "<path to output file>" # Path to the output file (required)
|
|
29
|
+
- keepOpen: true | false # Optional. Leave Word open after processing (default: false)
|
|
30
|
+
|
|
31
|
+
STATUS SCHEMA (JSON Output):
|
|
32
|
+
The script outputs a JSON object with the following fields:
|
|
33
|
+
{
|
|
34
|
+
"status": "success" | "error", # Execution status
|
|
35
|
+
"input": "<path to input file>", # Path to the input file
|
|
36
|
+
"output": "<path to output file>", # Path to the output file (null if error)
|
|
37
|
+
"message": "OK" | "<error message>", # "OK" on success or specific error message
|
|
38
|
+
"error_code": 0 | <error code> # 0 for success; specific code for different errors
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
ERROR CODES:
|
|
42
|
+
0 - Success
|
|
43
|
+
1 - Incorrect JSON format or missing required fields
|
|
44
|
+
2 - Input file format is unsupported
|
|
45
|
+
3 - Output file format is unsupported
|
|
46
|
+
11 - Input file not found
|
|
47
|
+
21 - Microsoft Word did not start or is not installed
|
|
48
|
+
31 - Error saving or converting file
|
|
49
|
+
#>
|
|
50
|
+
|
|
51
|
+
param (
|
|
52
|
+
[string]$jsonArgs
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Supported file formats and corresponding codes for Microsoft Word
|
|
56
|
+
$formatCodes = @{
|
|
57
|
+
"docx" = 16
|
|
58
|
+
"doc" = 0
|
|
59
|
+
"pdf" = 17
|
|
60
|
+
"rtf" = 6
|
|
61
|
+
"odt" = 19
|
|
62
|
+
"txt" = 7
|
|
63
|
+
"html" = 8
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# Function to retrieve format code by extension
|
|
67
|
+
function Get-FormatCodeByExtension {
|
|
68
|
+
param ($extension)
|
|
69
|
+
$extension = $extension.ToLower()
|
|
70
|
+
return $formatCodes[$extension] -as [int]
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# Parse JSON input
|
|
74
|
+
try {
|
|
75
|
+
$params = $jsonArgs | ConvertFrom-Json
|
|
76
|
+
} catch {
|
|
77
|
+
Write-Output (ConvertTo-Json @{
|
|
78
|
+
status = "error"
|
|
79
|
+
input = $null
|
|
80
|
+
output = $null
|
|
81
|
+
message = "Invalid JSON format."
|
|
82
|
+
error_code = 1
|
|
83
|
+
})
|
|
84
|
+
exit 1
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
$inputPath = $params.input
|
|
88
|
+
$outputPath = $params.output
|
|
89
|
+
$keepOpen = $params.keepOpen -eq $true
|
|
90
|
+
|
|
91
|
+
# Validate required parameters
|
|
92
|
+
if (-not $inputPath -or -not $outputPath) {
|
|
93
|
+
Write-Output (ConvertTo-Json @{
|
|
94
|
+
status = "error"
|
|
95
|
+
input = $inputPath
|
|
96
|
+
output = $outputPath
|
|
97
|
+
message = "Both 'input' and 'output' fields are required in JSON."
|
|
98
|
+
error_code = 1
|
|
99
|
+
})
|
|
100
|
+
exit 1
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
# Check input file format support
|
|
104
|
+
$inputExtension = [System.IO.Path]::GetExtension($inputPath).TrimStart('.').ToLower()
|
|
105
|
+
$inputFormat = Get-FormatCodeByExtension -extension $inputExtension
|
|
106
|
+
|
|
107
|
+
if (-not $inputFormat) {
|
|
108
|
+
Write-Output (ConvertTo-Json @{
|
|
109
|
+
status = "error"
|
|
110
|
+
input = $inputPath
|
|
111
|
+
output = $null
|
|
112
|
+
message = "The input file format '$inputExtension' is unsupported."
|
|
113
|
+
error_code = 2
|
|
114
|
+
})
|
|
115
|
+
exit 2
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
# Check output file format support
|
|
119
|
+
$outputExtension = [System.IO.Path]::GetExtension($outputPath).TrimStart('.').ToLower()
|
|
120
|
+
$outputFormat = Get-FormatCodeByExtension -extension $outputExtension
|
|
121
|
+
|
|
122
|
+
if ($outputFormat -eq $null) {
|
|
123
|
+
Write-Output (ConvertTo-Json @{
|
|
124
|
+
status = "error"
|
|
125
|
+
input = $inputPath
|
|
126
|
+
output = $outputPath
|
|
127
|
+
message = "The output file format '$outputExtension' is unsupported."
|
|
128
|
+
error_code = 3
|
|
129
|
+
})
|
|
130
|
+
exit 3
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
# Initialize Word COM object with error handling
|
|
134
|
+
try {
|
|
135
|
+
$word = New-Object -ComObject Word.Application -ErrorAction Stop
|
|
136
|
+
} catch {
|
|
137
|
+
Write-Output (ConvertTo-Json @{
|
|
138
|
+
status = "error"
|
|
139
|
+
input = $inputPath
|
|
140
|
+
output = $outputPath
|
|
141
|
+
message = "Microsoft Word is not installed or cannot be started."
|
|
142
|
+
error_code = 21
|
|
143
|
+
})
|
|
144
|
+
exit 21
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
$word.Visible = $false
|
|
148
|
+
$wordStartedByScript = $false
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
# Check if input file exists
|
|
152
|
+
if (-not (Test-Path -Path $inputPath)) {
|
|
153
|
+
Write-Output (ConvertTo-Json @{
|
|
154
|
+
status = "error"
|
|
155
|
+
input = $inputPath
|
|
156
|
+
output = $null
|
|
157
|
+
message = "File '$inputPath' not found."
|
|
158
|
+
error_code = 11
|
|
159
|
+
})
|
|
160
|
+
exit 11
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
# Open and convert the document
|
|
164
|
+
$doc = $word.Documents.Open($inputPath)
|
|
165
|
+
$doc.SaveAs([ref]$outputPath, [ref]$outputFormat)
|
|
166
|
+
$doc.Close()
|
|
167
|
+
} catch {
|
|
168
|
+
Write-Output (ConvertTo-Json @{
|
|
169
|
+
status = "error"
|
|
170
|
+
input = $inputPath
|
|
171
|
+
output = $outputPath
|
|
172
|
+
message = $_.Exception.Message
|
|
173
|
+
error_code = 31
|
|
174
|
+
})
|
|
175
|
+
exit 31
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
# Success message
|
|
179
|
+
Write-Output (ConvertTo-Json @{
|
|
180
|
+
status = "success"
|
|
181
|
+
input = $inputPath
|
|
182
|
+
output = $outputPath
|
|
183
|
+
message = "OK"
|
|
184
|
+
error_code = 0
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
# Close Word if opened by the script and keepOpen is false
|
|
188
|
+
if (-not $keepOpen) {
|
|
189
|
+
$word.Quit()
|
|
190
|
+
}
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: conver
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A cross-platform document converter.
|
|
5
|
+
Author-email: Timur Ulyahin <ucomru@pm.me>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ucomru/python-conver
|
|
8
|
+
Project-URL: Repository, https://github.com/ucomru/python-conver
|
|
9
|
+
Project-URL: Documentation, https://github.com/ucomru/python-conver
|
|
10
|
+
Project-URL: Issues, https://github.com/ucomru/python-conver/issues
|
|
11
|
+
Keywords: conversion,documents,office,tools,cli
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Operating System :: MacOS
|
|
14
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
15
|
+
Requires-Python: >=3.9
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: click
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# conver — Cross-platform Word Document Converter
|
|
22
|
+
|
|
23
|
+
`conver` is a cross-platform Python package that converts Microsoft Word documents
|
|
24
|
+
using native system automation:
|
|
25
|
+
|
|
26
|
+
- macOS: JXA (JavaScript for Automation)
|
|
27
|
+
- Windows: PowerShell + Word COM automation
|
|
28
|
+
|
|
29
|
+
The package provides:
|
|
30
|
+
|
|
31
|
+
1. A high-level Python API: `conver.conver()`
|
|
32
|
+
2. A command-line interface: `conver`
|
|
33
|
+
3. A low-level IPC layer calling platform scripts
|
|
34
|
+
(`convert.jxa` for macOS, `convert.ps1` for Windows)
|
|
35
|
+
|
|
36
|
+
Conversion is performed via Microsoft Word itself, ensuring maximum compatibility
|
|
37
|
+
with `.docx`, `.doc`, `.rtf`, `.txt`, `.html`, `.odt`.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Features
|
|
42
|
+
|
|
43
|
+
- Cross-platform: macOS and Windows
|
|
44
|
+
- Uses the actual Microsoft Word engine
|
|
45
|
+
- Clean, minimal Python API
|
|
46
|
+
- CLI with direct input/output or format-selection flags
|
|
47
|
+
- Structured error handling via custom exceptions
|
|
48
|
+
- Filename-only outputs automatically placed next to the input file
|
|
49
|
+
- Safe low-level IPC layer between Python and native automation scripts
|
|
50
|
+
|
|
51
|
+
## Installation
|
|
52
|
+
|
|
53
|
+
### Using pip
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install conver
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Recommended for CLI use: pipx
|
|
60
|
+
|
|
61
|
+
If you primarily use `conver` as a command-line tool, it is best installed with **pipx**.
|
|
62
|
+
This isolates the package in its own virtual environment and avoids polluting your system Python:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pipx install conver
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
After installation:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
conver --help
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
will be available globally.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
### Requirements
|
|
79
|
+
|
|
80
|
+
- Python **3.9+**
|
|
81
|
+
- Microsoft Word must be installed on the system
|
|
82
|
+
- macOS (JXA) or Windows (PowerShell + Word COM automation)
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Python Usage
|
|
87
|
+
|
|
88
|
+
### Basic example
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from conver import conver
|
|
92
|
+
|
|
93
|
+
conver("document.docx", "document.pdf")
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
Path("/absolute/path/document.pdf")
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Filename-only output
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
conver("/Users/me/docs/a.docx", "a.pdf")
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Automatically produces:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
/Users/me/docs/a.pdf
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Error handling example
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from conver import conver, UnsupportedFormat
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
conver("a.doc", "a.xyz")
|
|
121
|
+
except UnsupportedFormat:
|
|
122
|
+
print("This output format is not supported.")
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Python API Reference
|
|
128
|
+
|
|
129
|
+
### `conver(input_path, output_path, keep_open=False) -> pathlib.Path`
|
|
130
|
+
|
|
131
|
+
High-level document conversion function.
|
|
132
|
+
|
|
133
|
+
#### Parameters
|
|
134
|
+
|
|
135
|
+
- `input_path` — `str` or `pathlib.Path`
|
|
136
|
+
Path to the source document.
|
|
137
|
+
- `output_path` — `str` or `pathlib.Path`
|
|
138
|
+
Target output filename or full path.
|
|
139
|
+
- `keep_open` — `bool`
|
|
140
|
+
Whether to keep Microsoft Word open after conversion.
|
|
141
|
+
|
|
142
|
+
#### Returns
|
|
143
|
+
- `pathlib.Path` — absolute path of the output file.
|
|
144
|
+
|
|
145
|
+
#### Raises
|
|
146
|
+
|
|
147
|
+
- `InputFileNotFound`
|
|
148
|
+
- `UnsupportedFormat`
|
|
149
|
+
- `WordStartError`
|
|
150
|
+
- `SaveError`
|
|
151
|
+
- `IPCError`
|
|
152
|
+
- `PlatformNotSupported`
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## CLI Usage
|
|
157
|
+
|
|
158
|
+
The CLI supports two modes: direct output path, or format flags.
|
|
159
|
+
|
|
160
|
+
### 1. Explicit input/output
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
conver input.docx output.pdf
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### 2. Format flags (output placed next to input)
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
conver input.docx --pdf
|
|
170
|
+
conver input.docx --rtf
|
|
171
|
+
conver input.docx --txt
|
|
172
|
+
conver input.docx --html
|
|
173
|
+
conver input.docx --docx
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
If only one input file and no explicit output is given,
|
|
177
|
+
the selected format determines the extension:
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
input.docx --pdf -> input.pdf
|
|
181
|
+
input.docx --rtf -> input.rtf
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Output Behavior
|
|
185
|
+
|
|
186
|
+
When OUTPUT is omitted, the resulting file is written next to the INPUT file.
|
|
187
|
+
Examples:
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
conver a.docx --pdf -> a.pdf
|
|
191
|
+
conver /path/x.docx -> /path/x.pdf
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
When multiple inputs are provided, --output must be a directory.
|
|
195
|
+
If the directory does not exist, it will be created automatically.
|
|
196
|
+
|
|
197
|
+
### Default Format
|
|
198
|
+
|
|
199
|
+
If no format flag is provided and no explicit output path is given,
|
|
200
|
+
the default output format is **PDF**:
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
conver input.docx # -> input.pdf
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### CLI Syntax
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
Usage:
|
|
210
|
+
conver <input> <output>
|
|
211
|
+
conver <input> [--pdf | --docx | --rtf | --txt | --html] [--keep-open]
|
|
212
|
+
|
|
213
|
+
Examples:
|
|
214
|
+
conver a.docx a.pdf
|
|
215
|
+
conver a.docx --pdf
|
|
216
|
+
conver /path/to/file.doc --html
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### CLI Arguments
|
|
220
|
+
|
|
221
|
+
`input`
|
|
222
|
+
One or more input files. Patterns like `*.docx` are expanded by the shell.
|
|
223
|
+
|
|
224
|
+
`output`
|
|
225
|
+
Optional.
|
|
226
|
+
For single input: output filename.
|
|
227
|
+
For multiple inputs: must be a directory.
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
### Multiple Inputs (Batch Mode)
|
|
232
|
+
|
|
233
|
+
The CLI can process multiple input files at once:
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
conver *.docx -o outdir
|
|
237
|
+
conver file1.docx file2.docx file3.docx -o converted/
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Rules:
|
|
241
|
+
|
|
242
|
+
- When multiple inputs are provided, `--output` **must** point to a directory.
|
|
243
|
+
- If the directory does not exist, it will be created automatically.
|
|
244
|
+
- If no format flag is provided, the default output format is **PDF**.
|
|
245
|
+
- Format flags (`--pdf`, `--rtf`, etc.) **cannot be used together with** `--output FILE`.
|
|
246
|
+
- Globbing (`*.docx`) is expanded by your shell before reaching the CLI.
|
|
247
|
+
|
|
248
|
+
Output filenames follow the pattern:
|
|
249
|
+
|
|
250
|
+
```
|
|
251
|
+
input.docx -> outdir/input.pdf
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Shell Globbing
|
|
255
|
+
|
|
256
|
+
Patterns like `*.docx` are expanded by your shell before the `conver` command is executed.
|
|
257
|
+
|
|
258
|
+
Examples:
|
|
259
|
+
`conver *.docx -o outdir`
|
|
260
|
+
`conver path/*.rtf --pdf`
|
|
261
|
+
|
|
262
|
+
This means the CLI receives the expanded list of files as separate arguments.
|
|
263
|
+
|
|
264
|
+
### Format-Flag Restrictions
|
|
265
|
+
|
|
266
|
+
Format-selection flags:
|
|
267
|
+
|
|
268
|
+
```
|
|
269
|
+
--pdf --docx --rtf --txt --html
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
work **only when OUTPUT is omitted**, i.e.:
|
|
273
|
+
|
|
274
|
+
*Allowed*:
|
|
275
|
+
```bash
|
|
276
|
+
conver input.docx --pdf
|
|
277
|
+
conver input.docx output.pdf
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
*Invalid:
|
|
281
|
+
```bash
|
|
282
|
+
conver input.docx output.pdf --pdf
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## Supported Formats
|
|
288
|
+
|
|
289
|
+
Conversion capabilities depend on Microsoft Word.
|
|
290
|
+
|
|
291
|
+
Input formats: `.docx`, `.doc`, `.pdf`, `.rtf`, `.odt`, `.txt`, `.html`
|
|
292
|
+
|
|
293
|
+
Output formats: `.pdf`, `.docx`, `.doc`, `.pdf`, `.rtf`, `.odt`, `.txt`, `.html`
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## Platform Notes
|
|
298
|
+
|
|
299
|
+
### macOS (JXA)
|
|
300
|
+
|
|
301
|
+
The script `convert.jxa` runs through:
|
|
302
|
+
|
|
303
|
+
```
|
|
304
|
+
osascript -l JavaScript
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
macOS may require granting Microsoft Word file-access permissions.
|
|
308
|
+
|
|
309
|
+
### Windows (PowerShell)
|
|
310
|
+
|
|
311
|
+
The script `convert.ps1` uses:
|
|
312
|
+
|
|
313
|
+
```
|
|
314
|
+
Word.Application COM automation
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
If Word prompts the user, automation must be allowed.
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## Low-Level Error Codes (Reference)
|
|
322
|
+
|
|
323
|
+
These codes come from platform scripts and are mapped to exceptions by `conver()`:
|
|
324
|
+
|
|
325
|
+
| Code | Meaning |
|
|
326
|
+
|------|--------------------------------------|
|
|
327
|
+
| 0 | Success |
|
|
328
|
+
| 1 | Invalid JSON from stdin |
|
|
329
|
+
| 2 | Unsupported input format |
|
|
330
|
+
| 3 | Unsupported output format |
|
|
331
|
+
| 11 | Input file not found |
|
|
332
|
+
| 21 | Word startup timeout |
|
|
333
|
+
| 31 | Word could not save the file |
|
|
334
|
+
| 98 | Script produced invalid JSON |
|
|
335
|
+
| 99 | Unsupported platform |
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## License
|
|
340
|
+
|
|
341
|
+
MIT License
|
|
342
|
+
(c) 2024 Timur Ulyahin
|
|
343
|
+
https://github.com/ucomru
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
## Project Homepage
|
|
348
|
+
|
|
349
|
+
GitHub:
|
|
350
|
+
https://github.com/ucomru/python-conver
|
|
351
|
+
|
|
352
|
+
PyPI:
|
|
353
|
+
https://pypi.org/project/conver/
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
conver/__init__.py,sha256=40DZmYJLiEt1HR6tZDm_ZjfhMvpHz57PAX5n8frB-9E,562
|
|
2
|
+
conver/__version__.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
|
|
3
|
+
conver/_convert.py,sha256=77EqKxxHBmNloK2HzR6zSKI28vxMWACzOJbrUHP1Vpg,7832
|
|
4
|
+
conver/cli.py,sha256=RJ33rmI2uqdYgLH9dKqsJGWc-sO-DKoQ4sGhLdYRAW8,2790
|
|
5
|
+
conver/conver.py,sha256=Ho6RiHVihQsk5AAJafte6fwCCS_5Sf5pyunouNHeMMQ,3780
|
|
6
|
+
conver/scripts/convert.jxa,sha256=7aePzEWbFlPRihPxdgEFfiteyxVxkVhCji4EVq7djNk,7715
|
|
7
|
+
conver/scripts/convert.ps1,sha256=qdm6lfoAUzD3HbyAMprWakQlbGMZW0Ow6KoLbmquOtE,5637
|
|
8
|
+
conver-0.1.0.dist-info/licenses/LICENSE,sha256=EZ2rjk_EQKv3xS71Wwf41DAGJ4gdkeGhM5CGvn6Mf0A,1095
|
|
9
|
+
conver-0.1.0.dist-info/METADATA,sha256=faEL7zeSRKrNj0dMkhYg-r7gxwomeh5_POMPDEFTeIs,7500
|
|
10
|
+
conver-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
11
|
+
conver-0.1.0.dist-info/entry_points.txt,sha256=xILIEXchvJsg2IJFeYlxRDBRxLLPUT--y0Lo1kB_QCo,42
|
|
12
|
+
conver-0.1.0.dist-info/top_level.txt,sha256=_pwrVeQgyeqp-yzROQqRFqK0S0tElmrsb8UXoDJCwiE,7
|
|
13
|
+
conver-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
© 2024 Timur Ulyahin, https://github.com/ucomru
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in
|
|
12
|
+
all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
20
|
+
THE SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
conver
|