convashun 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.
- convashun/__init__.py +0 -0
- convashun/cli.py +28 -0
- convashun/document.py +514 -0
- convashun/image.py +123 -0
- convashun-0.1.0.dist-info/METADATA +139 -0
- convashun-0.1.0.dist-info/RECORD +9 -0
- convashun-0.1.0.dist-info/WHEEL +4 -0
- convashun-0.1.0.dist-info/entry_points.txt +2 -0
- convashun-0.1.0.dist-info/licenses/LICENSE +21 -0
convashun/__init__.py
ADDED
|
File without changes
|
convashun/cli.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from rich.console import Console
|
|
3
|
+
from convashun import image, document
|
|
4
|
+
|
|
5
|
+
# Create the main Typer application
|
|
6
|
+
app = typer.Typer(
|
|
7
|
+
name="Convashun",
|
|
8
|
+
help="⚡ Ultra-fast cross-platform file conversion utility ⚡",
|
|
9
|
+
add_completion=True,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
# Initialize rich console for error and status printing
|
|
13
|
+
console = Console()
|
|
14
|
+
|
|
15
|
+
# Attach sub-modules as command groups
|
|
16
|
+
app.add_typer(document.app, name="document", help="Convert document formats (DOCX, PDF, TXT, etc.)")
|
|
17
|
+
app.add_typer(image.app, name="image", help="Convert image formats (PNG, JPG, WEBP, etc.)")
|
|
18
|
+
|
|
19
|
+
@app.callback()
|
|
20
|
+
def main():
|
|
21
|
+
"""
|
|
22
|
+
Global settings or configurations can go here.
|
|
23
|
+
This executes before any subcommand.
|
|
24
|
+
"""
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
if __name__ == "__main__":
|
|
28
|
+
app()
|
convashun/document.py
ADDED
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
import subprocess
|
|
3
|
+
import sys
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import shutil
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from pypdf import PdfReader
|
|
9
|
+
from docx import document
|
|
10
|
+
|
|
11
|
+
app = typer.Typer()
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@app.command("to-pdf")
|
|
16
|
+
def to_pdf(
|
|
17
|
+
input_path: Path = typer.Argument(
|
|
18
|
+
...,
|
|
19
|
+
exists=True,
|
|
20
|
+
file_okay=True,
|
|
21
|
+
dir_okay=False,
|
|
22
|
+
readable=True,
|
|
23
|
+
help="Path to the source document file (e.g., .docx, .doc, .odt, .rtf)"
|
|
24
|
+
),
|
|
25
|
+
):
|
|
26
|
+
"""
|
|
27
|
+
Convert an incoming document file (DOCX/DOC/ODT) to PDF format.
|
|
28
|
+
"""
|
|
29
|
+
console.print(f"[yellow]Processing:[/yellow] {input_path.name}...")
|
|
30
|
+
|
|
31
|
+
# Cross-platform safe path modification
|
|
32
|
+
output_path = input_path.with_suffix(".pdf")
|
|
33
|
+
|
|
34
|
+
# 1. Locate LibreOffice on the user's system path dynamically
|
|
35
|
+
# Windows uses 'soffice', Linux/macOS usually map to 'libreoffice' or 'soffice'
|
|
36
|
+
cmd = shutil.which("libreoffice") or shutil.which("soffice")
|
|
37
|
+
|
|
38
|
+
if not cmd:
|
|
39
|
+
console.print("[bold red]Missing Dependency Error:[/bold red]")
|
|
40
|
+
console.print("LibreOffice is required to convert office documents on your machine.")
|
|
41
|
+
console.print("\n[yellow]How to install it:[/yellow]")
|
|
42
|
+
console.print("• [bold]macOS:[/bold] `brew install libreoffice`")
|
|
43
|
+
console.print("• [bold]Linux (Ubuntu/Debian):[/bold] `sudo apt install libreoffice`")
|
|
44
|
+
console.print("• [bold]Windows:[/bold] Install via winget: `winget install LibreOffice.LibreOffice` or download manually.")
|
|
45
|
+
raise typer.Exit(code=1)
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
# 2. Run the cross-platform headless conversion command
|
|
49
|
+
# This reads the document, runs in the background, and generates a clean PDF
|
|
50
|
+
subprocess.run([
|
|
51
|
+
cmd,
|
|
52
|
+
"--headless",
|
|
53
|
+
"--convert-to", "pdf",
|
|
54
|
+
str(input_path),
|
|
55
|
+
"--outdir", str(input_path.parent)
|
|
56
|
+
], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
57
|
+
|
|
58
|
+
console.print(f"[bold green]Success![/bold green] Saved to {output_path}")
|
|
59
|
+
except subprocess.CalledProcessError as e:
|
|
60
|
+
console.print(f"[bold red]LibreOffice engine failed to process the file:[/bold red] {e}")
|
|
61
|
+
raise typer.Exit(code=1)
|
|
62
|
+
except Exception as e:
|
|
63
|
+
console.print(f"[bold red]Error converting file:[/bold red] {e}")
|
|
64
|
+
raise typer.Exit(code=1)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@app.command("pptx-to-pdf")
|
|
68
|
+
def pptx_to_pdf(
|
|
69
|
+
input_path: Path = typer.Argument(
|
|
70
|
+
...,
|
|
71
|
+
exists=True,
|
|
72
|
+
file_okay=True,
|
|
73
|
+
dir_okay=False,
|
|
74
|
+
readable=True,
|
|
75
|
+
help="Path to the source PowerPoint (.pptx or .ppt) file"
|
|
76
|
+
),
|
|
77
|
+
):
|
|
78
|
+
"""
|
|
79
|
+
Convert an incoming PowerPoint presentation (PPTX/PPT) to PDF format.
|
|
80
|
+
"""
|
|
81
|
+
# Defensive check: Ensure they are actually passing a presentation file
|
|
82
|
+
if input_path.suffix.lower() not in (".pptx", ".ppt"):
|
|
83
|
+
console.print("[bold red]Format Error:[/bold red] This command only supports converting .pptx or .ppt files to .pdf.")
|
|
84
|
+
raise typer.Exit(code=1)
|
|
85
|
+
|
|
86
|
+
console.print(f"[yellow]Processing Presentation:[/yellow] {input_path.name}...")
|
|
87
|
+
|
|
88
|
+
# Cross-platform safe path modification
|
|
89
|
+
output_path = input_path.with_suffix(".pdf")
|
|
90
|
+
|
|
91
|
+
# 1. Locate LibreOffice dynamically on the user's system path
|
|
92
|
+
cmd = shutil.which("libreoffice") or shutil.which("soffice")
|
|
93
|
+
|
|
94
|
+
if not cmd:
|
|
95
|
+
console.print("[bold red]Missing Dependency Error:[/bold red]")
|
|
96
|
+
console.print("LibreOffice is required to convert presentation files on your machine.")
|
|
97
|
+
console.print("\n[yellow]How to install it:[/yellow]")
|
|
98
|
+
console.print("• [bold]macOS:[/bold] `brew install libreoffice`")
|
|
99
|
+
console.print("• [bold]Linux (Ubuntu/Debian):[/bold] `sudo apt install libreoffice`")
|
|
100
|
+
console.print("• [bold]Windows:[/bold] Install via winget: `winget install LibreOffice.LibreOffice` or download manually.")
|
|
101
|
+
raise typer.Exit(code=1)
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
# 2. Run the cross-platform headless conversion command
|
|
105
|
+
# LibreOffice reads presentation slide structures natively and flattens them cleanly
|
|
106
|
+
subprocess.run([
|
|
107
|
+
cmd,
|
|
108
|
+
"--headless",
|
|
109
|
+
"--convert-to", "pdf",
|
|
110
|
+
str(input_path),
|
|
111
|
+
"--outdir", str(input_path.parent)
|
|
112
|
+
], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
113
|
+
|
|
114
|
+
console.print(f"[bold green]Success![/bold green] Saved presentation to {output_path}")
|
|
115
|
+
except subprocess.CalledProcessError as e:
|
|
116
|
+
console.print(f"[bold red]LibreOffice engine failed to process the presentation:[/bold red] {e}")
|
|
117
|
+
raise typer.Exit(code=1)
|
|
118
|
+
except Exception as e:
|
|
119
|
+
console.print(f"[bold red]Error converting presentation file:[/bold red] {e}")
|
|
120
|
+
raise typer.Exit(code=1)
|
|
121
|
+
|
|
122
|
+
import pandas as pd
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@app.command("csv-to-xlsx")
|
|
126
|
+
def csv_to_xlsx(input_path: Path = typer.Argument(..., exists=True)):
|
|
127
|
+
"""Convert a flat CSV log file into an Excel Spreadsheet (.xlsx)."""
|
|
128
|
+
if input_path.suffix.lower() != ".csv":
|
|
129
|
+
console.print("[bold red]Format Error:[/bold red] This command only supports converting .csv files.")
|
|
130
|
+
raise typer.Exit(code=1)
|
|
131
|
+
output_path = input_path.with_suffix(".xlsx")
|
|
132
|
+
try:
|
|
133
|
+
df = pd.read_csv(input_path)
|
|
134
|
+
df.to_excel(output_path, index=False, engine='openpyxl')
|
|
135
|
+
console.print(f"[bold green]Success![/bold green] Spreadsheet saved to {output_path}")
|
|
136
|
+
except Exception as e:
|
|
137
|
+
console.print(f"[bold red]Data processing failed:[/bold red] {e}")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@app.command("xlsx-to-csv")
|
|
141
|
+
def xlsx_to_csv(
|
|
142
|
+
input_path: Path = typer.Argument(
|
|
143
|
+
...,
|
|
144
|
+
exists=True,
|
|
145
|
+
file_okay=True,
|
|
146
|
+
dir_okay=False,
|
|
147
|
+
readable=True,
|
|
148
|
+
help="Path to the source Excel (.xlsx) spreadsheet"
|
|
149
|
+
),
|
|
150
|
+
sheet_name: str = typer.Option(
|
|
151
|
+
"0",
|
|
152
|
+
"--sheet", "-s",
|
|
153
|
+
help="Name or index of the spreadsheet tab to extract"
|
|
154
|
+
),
|
|
155
|
+
):
|
|
156
|
+
"""
|
|
157
|
+
Flatten a structured Excel Spreadsheet (.xlsx) sheet into a raw data CSV file.
|
|
158
|
+
"""
|
|
159
|
+
if input_path.suffix.lower() != ".xlsx":
|
|
160
|
+
console.print("[bold red]Format Error:[/bold red] This command only supports converting .xlsx files.")
|
|
161
|
+
raise typer.Exit(code=1)
|
|
162
|
+
|
|
163
|
+
console.print(f"[yellow]Flattening spreadsheet records:[/yellow] {input_path.name}...")
|
|
164
|
+
output_path = input_path.with_suffix(".csv")
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
# Convert numeric string token to raw integer index dynamically
|
|
168
|
+
parsed_sheet = int(sheet_name) if sheet_name.isdigit() else sheet_name
|
|
169
|
+
|
|
170
|
+
# Pull data layout using openpyxl engine underneath pandas
|
|
171
|
+
df = pd.read_excel(input_path, sheet_name=parsed_sheet, engine="openpyxl")
|
|
172
|
+
|
|
173
|
+
# Save output records with standard comma-separation formatting
|
|
174
|
+
df.to_csv(output_path, index=False)
|
|
175
|
+
|
|
176
|
+
console.print(f"[bold green]Success![/bold green] Extracted data saved to {output_path}")
|
|
177
|
+
except Exception as e:
|
|
178
|
+
console.print(f"[bold red]Flattening procedure failed:[/bold red] {e}")
|
|
179
|
+
raise typer.Exit(code=1)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@app.command("to-csv")
|
|
183
|
+
def to_csv(
|
|
184
|
+
input_path: Path = typer.Argument(
|
|
185
|
+
...,
|
|
186
|
+
exists=True,
|
|
187
|
+
file_okay=True,
|
|
188
|
+
dir_okay=False,
|
|
189
|
+
readable=True,
|
|
190
|
+
help="Path to the source Excel (.xlsx) file"
|
|
191
|
+
),
|
|
192
|
+
sheet_name: str = typer.Option(
|
|
193
|
+
"0",
|
|
194
|
+
"--sheet", "-s",
|
|
195
|
+
help="Name or 0-based index of the Excel sheet to convert"
|
|
196
|
+
),
|
|
197
|
+
):
|
|
198
|
+
"""
|
|
199
|
+
Convert an Excel Spreadsheet (.xlsx) sheet into a raw data CSV file.
|
|
200
|
+
"""
|
|
201
|
+
if input_path.suffix.lower() != ".xlsx":
|
|
202
|
+
console.print("[bold red]Format Error:[/bold red] This command only supports converting .xlsx files.")
|
|
203
|
+
raise typer.Exit(code=1)
|
|
204
|
+
|
|
205
|
+
console.print(f"[yellow]Extracting spreadsheet data:[/yellow] {input_path.name}...")
|
|
206
|
+
output_path = input_path.with_suffix(".csv")
|
|
207
|
+
|
|
208
|
+
try:
|
|
209
|
+
# Convert numeric string index to int if applicable
|
|
210
|
+
parsed_sheet = int(sheet_name) if sheet_name.isdigit() else sheet_name
|
|
211
|
+
|
|
212
|
+
# Read the Excel file via openpyxl engine
|
|
213
|
+
df = pd.read_excel(input_path, sheet_name=parsed_sheet, engine="openpyxl")
|
|
214
|
+
|
|
215
|
+
# Save to CSV without the arbitrary row index numbers
|
|
216
|
+
df.to_csv(output_path, index=False)
|
|
217
|
+
|
|
218
|
+
console.print(f"[bold green]Success![/bold green] Extracted data saved to {output_path}")
|
|
219
|
+
except Exception as e:
|
|
220
|
+
console.print(f"[bold red]Data extraction failed:[/bold red] {e}")
|
|
221
|
+
raise typer.Exit(code=1)
|
|
222
|
+
|
|
223
|
+
@app.command("to-xlsx")
|
|
224
|
+
def to_xlsx(
|
|
225
|
+
input_path: Path = typer.Argument(
|
|
226
|
+
...,
|
|
227
|
+
exists=True,
|
|
228
|
+
file_okay=True,
|
|
229
|
+
dir_okay=False,
|
|
230
|
+
readable=True,
|
|
231
|
+
help="Path to the source CSV file"
|
|
232
|
+
),
|
|
233
|
+
):
|
|
234
|
+
"""
|
|
235
|
+
Convert flat CSV raw rows into a structured Excel Spreadsheet (.xlsx).
|
|
236
|
+
"""
|
|
237
|
+
if input_path.suffix.lower() != ".csv":
|
|
238
|
+
console.print("[bold red]Format Error:[/bold red] This command only supports converting .csv files.")
|
|
239
|
+
raise typer.Exit(code=1)
|
|
240
|
+
|
|
241
|
+
console.print(f"[yellow]Structuring spreadsheet layout:[/yellow] {input_path.name}...")
|
|
242
|
+
output_path = input_path.with_suffix(".xlsx")
|
|
243
|
+
|
|
244
|
+
try:
|
|
245
|
+
# Read raw tabular rows
|
|
246
|
+
df = pd.read_csv(input_path)
|
|
247
|
+
|
|
248
|
+
# Stream down directly into openpyxl formatting engine
|
|
249
|
+
df.to_excel(output_path, index=False, engine="openpyxl")
|
|
250
|
+
|
|
251
|
+
console.print(f"[bold green]Success![/bold green] Spreadsheet saved to {output_path}")
|
|
252
|
+
except Exception as e:
|
|
253
|
+
console.print(f"[bold red]Spreadsheet composition failed:[/bold red] {e}")
|
|
254
|
+
raise typer.Exit(code=1)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def install_tex_engine_automatically():
|
|
258
|
+
"""Triggers the operating system's native package manager to install a lightweight CLI TeX engine."""
|
|
259
|
+
console.print("\n[yellow]Attempting to bootstrap a lightweight TeX layout engine for you...[/yellow]")
|
|
260
|
+
|
|
261
|
+
try:
|
|
262
|
+
if sys.platform == "win32":
|
|
263
|
+
console.print("[blue]Running Windows Package Manager (winget) for MiKTeX...[/blue]")
|
|
264
|
+
# Triggers a clean, silent install of the core minimal Windows TeX compilation engine
|
|
265
|
+
subprocess.run(["winget", "install", "MiKTeX.MiKTeX", "--silent"], check=True)
|
|
266
|
+
|
|
267
|
+
elif sys.platform == "darwin":
|
|
268
|
+
console.print("[blue]Running macOS Homebrew for BasicTeX (Lightweight distribution)...[/blue]")
|
|
269
|
+
# Installs a minimal ~100MB CLI-only distribution instead of the multi-gigabyte full MacTeX package
|
|
270
|
+
subprocess.run(["brew", "install", "--cask", "basictex"], check=True)
|
|
271
|
+
|
|
272
|
+
else: # Linux (Ubuntu / Debian target check)
|
|
273
|
+
if shutil.which("apt-get"):
|
|
274
|
+
console.print("[blue]Running Linux Advanced Package Tool (apt) for lightweight texlive-base...[/blue]")
|
|
275
|
+
subprocess.run(["sudo", "apt-get", "update"], check=True)
|
|
276
|
+
subprocess.run(["sudo", "apt-get", "install", "-y", "texlive-latex-base"], check=True)
|
|
277
|
+
else:
|
|
278
|
+
raise RuntimeError("Unsupported Linux package distribution manager.")
|
|
279
|
+
|
|
280
|
+
console.print("[bold green]Success![/bold green] TeX compilation engine installation complete.")
|
|
281
|
+
console.print("[yellow]Note: Please restart your terminal window for the system path variables to take effect.[/yellow]")
|
|
282
|
+
|
|
283
|
+
except Exception as e:
|
|
284
|
+
console.print(f"[bold red]Auto-installation failed:[/bold red] {e}")
|
|
285
|
+
console.print("[yellow]Please configure a compiler (like MiKTeX or TeX Live) manually on your path.[/yellow]")
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
@app.command("tex-to-pdf")
|
|
289
|
+
def tex_to_pdf(
|
|
290
|
+
input_path: Path = typer.Argument(
|
|
291
|
+
..., exists=True, file_okay=True, dir_okay=False, readable=True,
|
|
292
|
+
help="Path to the source LaTeX (.tex) typesetting file"
|
|
293
|
+
),
|
|
294
|
+
):
|
|
295
|
+
"""
|
|
296
|
+
Compile a LaTeX (.tex) typesetting file into a high-fidelity PDF document.
|
|
297
|
+
"""
|
|
298
|
+
if input_path.suffix.lower() != ".tex":
|
|
299
|
+
console.print("[bold red]Format Error:[/bold red] This command only supports .tex files.")
|
|
300
|
+
raise typer.Exit(code=1)
|
|
301
|
+
|
|
302
|
+
console.print(f"[yellow]Typesetting document layout:[/yellow] {input_path.name}...")
|
|
303
|
+
output_path = input_path.with_suffix(".pdf")
|
|
304
|
+
|
|
305
|
+
# Locate native cross-platform latex binary mapping paths dynamically
|
|
306
|
+
cmd = shutil.which("pdflatex") or shutil.which("latexmk")
|
|
307
|
+
|
|
308
|
+
if not cmd:
|
|
309
|
+
console.print("[bold red]Missing Dependency Error:[/bold red] A native TeX compiler layout engine was not found.")
|
|
310
|
+
|
|
311
|
+
# Interactive Helper Selector
|
|
312
|
+
confirm = typer.confirm("Would you like convashun to automatically install a lightweight TeX engine via your system package manager?")
|
|
313
|
+
if confirm:
|
|
314
|
+
install_tex_engine_automatically()
|
|
315
|
+
raise typer.Exit(code=0)
|
|
316
|
+
else:
|
|
317
|
+
console.print("[red]Compilation aborted because system dependencies are missing.[/red]")
|
|
318
|
+
raise typer.Exit(code=1)
|
|
319
|
+
|
|
320
|
+
try:
|
|
321
|
+
# Run the native compiler safely in non-interactive batch mode
|
|
322
|
+
# It suppresses hanging user confirmation alerts if errors exist in the raw document layout
|
|
323
|
+
subprocess.run([
|
|
324
|
+
cmd,
|
|
325
|
+
"-interaction=nonstopmode",
|
|
326
|
+
f"-output-directory={str(input_path.parent)}",
|
|
327
|
+
str(input_path)
|
|
328
|
+
], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
329
|
+
|
|
330
|
+
# Clean up ancillary layout compilation log files generated by native TeX tools (.log, .aux)
|
|
331
|
+
for ext in (".aux", ".log", ".out"):
|
|
332
|
+
temp_file = input_path.with_suffix(ext)
|
|
333
|
+
if temp_file.exists():
|
|
334
|
+
temp_file.unlink()
|
|
335
|
+
|
|
336
|
+
console.print(f"[bold green]Success![/bold green] PDF compiled cleanly to {output_path}")
|
|
337
|
+
except subprocess.CalledProcessError as e:
|
|
338
|
+
console.print(f"[bold red]LaTeX engine failed to process the document structures:[/bold red] {e}")
|
|
339
|
+
raise typer.Exit(code=1)
|
|
340
|
+
except Exception as e:
|
|
341
|
+
console.print(f"[bold red]Error converting file:[/bold red] {e}")
|
|
342
|
+
raise typer.Exit(code=1)
|
|
343
|
+
|
|
344
|
+
@app.command("tex-to-pdf-native")
|
|
345
|
+
def tex_to_pdf_native(input_path: Path = typer.Argument(..., exists=True)):
|
|
346
|
+
"""Compile LaTeX to PDF using the local system engine."""
|
|
347
|
+
if input_path.suffix.lower() != ".tex":
|
|
348
|
+
console.print("[bold red]Format Error:[/bold red] Target must be a LaTex file.")
|
|
349
|
+
raise typer.Exit(code=1)
|
|
350
|
+
cmd = shutil.which("pdflatex")
|
|
351
|
+
|
|
352
|
+
if not cmd:
|
|
353
|
+
console.print("[bold red]Missing Dependency Error:[/bold red]")
|
|
354
|
+
console.print("A native TeX compiler (`pdflatex`) is required for this command.")
|
|
355
|
+
console.print("\n[yellow]How to install it:[/yellow]")
|
|
356
|
+
console.print("• [bold]macOS:[/bold] `brew install --cask mactex-no-gui`")
|
|
357
|
+
console.print("• [bold]Linux (Ubuntu):[/bold] `sudo apt install texlive-latex-base`")
|
|
358
|
+
console.print("• [bold]Windows:[/bold] Install MikTeX from `miktex.org`")
|
|
359
|
+
raise typer.Exit(code=1)
|
|
360
|
+
|
|
361
|
+
try:
|
|
362
|
+
# Execute the compiler tool inside the directory where the target file exists
|
|
363
|
+
subprocess.run([
|
|
364
|
+
cmd,
|
|
365
|
+
"-interaction=nonstopmode",
|
|
366
|
+
"-output-directory", str(input_path.parent),
|
|
367
|
+
str(input_path)
|
|
368
|
+
], check=True, stdout=subprocess.DEVNULL)
|
|
369
|
+
console.print(f"[bold green]Success![/bold green] Compiled via system engine.")
|
|
370
|
+
except Exception as e:
|
|
371
|
+
console.print(f"[bold red]Compilation crashed:[/bold red] {e}")
|
|
372
|
+
|
|
373
|
+
def install_libreoffice_automatically():
|
|
374
|
+
"""Triggers the operating system's native package manager to install LibreOffice."""
|
|
375
|
+
console.print("\n[yellow]Attempting to bootstrap LibreOffice for you...[/yellow]")
|
|
376
|
+
|
|
377
|
+
try:
|
|
378
|
+
if sys.platform == "win32":
|
|
379
|
+
console.print("[blue]Running Windows Package Manager (winget)...[/blue]")
|
|
380
|
+
# Executes Windows' official built-in app installer
|
|
381
|
+
subprocess.run(["winget", "install", "LibreOffice.LibreOffice", "--silent"], check=True)
|
|
382
|
+
|
|
383
|
+
elif sys.platform == "darwin":
|
|
384
|
+
console.print("[blue]Running macOS Homebrew...[/blue]")
|
|
385
|
+
subprocess.run(["brew", "install", "--cask", "libreoffice"], check=True)
|
|
386
|
+
|
|
387
|
+
else: # Linux (Ubuntu / Debian target check)
|
|
388
|
+
if shutil.which("apt-get"):
|
|
389
|
+
console.print("[blue]Running Linux Advanced Package Tool (apt)...[/blue]")
|
|
390
|
+
subprocess.run(["sudo", "apt-get", "update"], check=True)
|
|
391
|
+
subprocess.run(["sudo", "apt-get", "install", "-y", "libreoffice"], check=True)
|
|
392
|
+
else:
|
|
393
|
+
raise RuntimeError("Unsupported Linux package distribution manager.")
|
|
394
|
+
|
|
395
|
+
console.print("[bold green]Success![/bold green] LibreOffice installation completed.")
|
|
396
|
+
console.print("[yellow]Note: Please restart your terminal window for the system path changes to take effect.[/yellow]")
|
|
397
|
+
|
|
398
|
+
except Exception as e:
|
|
399
|
+
console.print(f"[bold red]Auto-installation failed:[/bold red] {e}")
|
|
400
|
+
console.print("[yellow]Please download LibreOffice manually from https://libreoffice.org[/yellow]")
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
@app.command("pptx-to-pdf")
|
|
405
|
+
def pptx_to_pdf(
|
|
406
|
+
input_path: Path = typer.Argument(
|
|
407
|
+
...,
|
|
408
|
+
exists=True,
|
|
409
|
+
file_okay=True,
|
|
410
|
+
dir_okay=False,
|
|
411
|
+
readable=True,
|
|
412
|
+
help="Path to the source PowerPoint (.pptx or .ppt) file"
|
|
413
|
+
),
|
|
414
|
+
):
|
|
415
|
+
"""
|
|
416
|
+
Convert a PowerPoint presentation (PPTX/PPT) to PDF format cleanly.
|
|
417
|
+
"""
|
|
418
|
+
if input_path.suffix.lower() not in (".pptx", ".ppt"):
|
|
419
|
+
console.print("[bold red]Format Error:[/bold red] This command only supports .pptx or .ppt files.")
|
|
420
|
+
raise typer.Exit(code=1)
|
|
421
|
+
|
|
422
|
+
console.print(f"[yellow]Processing Presentation:[/yellow] {input_path.name}...")
|
|
423
|
+
output_path = input_path.with_suffix(".pdf")
|
|
424
|
+
|
|
425
|
+
# Locate LibreOffice dynamically on the user's system path
|
|
426
|
+
cmd = shutil.which("libreoffice") or shutil.which("soffice")
|
|
427
|
+
if not cmd:
|
|
428
|
+
console.print("[bold red]Missing Dependency Error:[/bold red] LibreOffice is required to handle Office file layouts.")
|
|
429
|
+
|
|
430
|
+
# Interactive Prompt to make life easier for the user
|
|
431
|
+
confirm = typer.confirm("Would you like convashun to automatically install LibreOffice via your system package manager?")
|
|
432
|
+
if confirm:
|
|
433
|
+
install_libreoffice_automatically()
|
|
434
|
+
raise typer.Exit(code=0)
|
|
435
|
+
else:
|
|
436
|
+
console.print("[red]Conversion aborted because system dependencies are missing.[/red]")
|
|
437
|
+
raise typer.Exit(code=1)
|
|
438
|
+
|
|
439
|
+
try:
|
|
440
|
+
subprocess.run([
|
|
441
|
+
cmd, "--headless", "--convert-to", "pdf",
|
|
442
|
+
str(input_path), "--outdir", str(input_path.parent)
|
|
443
|
+
], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
444
|
+
console.print(f"[bold green]Success![/bold green] Saved presentation to {output_path}")
|
|
445
|
+
except Exception as e:
|
|
446
|
+
console.print(f"[bold red]Conversion failed:[/bold red] {e}")
|
|
447
|
+
raise typer.Exit(code=1)
|
|
448
|
+
|
|
449
|
+
@app.command("to-markdown")
|
|
450
|
+
def to_markdown(
|
|
451
|
+
input_path: Path = typer.Argument(..., exists=True, file_okay=True, dir_okay=False)
|
|
452
|
+
):
|
|
453
|
+
"""
|
|
454
|
+
Extract text contents from a PDF file into clean Markdown formatting (.md).
|
|
455
|
+
"""
|
|
456
|
+
if input_path.suffix.lower() != ".pdf":
|
|
457
|
+
console.print("[bold red]Format Error:[/bold red] Target must be a PDF file.")
|
|
458
|
+
raise typer.Exit(code=1)
|
|
459
|
+
|
|
460
|
+
console.print(f"[yellow]Extracting text layouts:[/yellow] {input_path.name}...")
|
|
461
|
+
output_path = input_path.with_suffix(".md")
|
|
462
|
+
|
|
463
|
+
try:
|
|
464
|
+
# pypdf is 100% pure Python, no C++ compilation or DLL required
|
|
465
|
+
reader = PdfReader(input_path)
|
|
466
|
+
markdown_lines = [f"# Document: {input_path.stem}\n"]
|
|
467
|
+
|
|
468
|
+
for i, page in enumerate(reader.pages, 1):
|
|
469
|
+
text = page.extract_text()
|
|
470
|
+
if text.strip():
|
|
471
|
+
markdown_lines.append(f"## Page {i}\n{text}\n")
|
|
472
|
+
|
|
473
|
+
output_path.write_text("\n".join(markdown_lines), encoding="utf-8")
|
|
474
|
+
console.print(f"[bold green]Success![/bold green] Extracted text layout to {output_path}")
|
|
475
|
+
except Exception as e:
|
|
476
|
+
console.print(f"[bold red]Markdown parsing failed:[/bold red] {e}")
|
|
477
|
+
raise typer.Exit(code=1)
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
@app.command("to-docx")
|
|
481
|
+
def to_docx(
|
|
482
|
+
input_path: Path = typer.Argument(..., exists=True, file_okay=True, dir_okay=False)
|
|
483
|
+
):
|
|
484
|
+
"""
|
|
485
|
+
Convert flat PDF text segments directly into an editable Word Document (.docx).
|
|
486
|
+
"""
|
|
487
|
+
if input_path.suffix.lower() != ".pdf":
|
|
488
|
+
console.print("[bold red]Format Error:[/bold red] Target file must be a PDF.")
|
|
489
|
+
raise typer.Exit(code=1)
|
|
490
|
+
|
|
491
|
+
console.print(f"[yellow]Building document tables:[/yellow] {input_path.name}...")
|
|
492
|
+
output_path = input_path.with_suffix(".docx")
|
|
493
|
+
|
|
494
|
+
try:
|
|
495
|
+
reader = PdfReader(input_path)
|
|
496
|
+
doc = Document()
|
|
497
|
+
doc.add_heading(input_path.stem, level=0)
|
|
498
|
+
|
|
499
|
+
has_content = False
|
|
500
|
+
for page in reader.pages:
|
|
501
|
+
text = page.extract_text()
|
|
502
|
+
if text and text.strip():
|
|
503
|
+
doc.add_paragraph(text)
|
|
504
|
+
has_content = True
|
|
505
|
+
else:
|
|
506
|
+
# Safe fallback option: ensures python-docx always has a structural
|
|
507
|
+
# paragraph element layer to bind to when saving the file data
|
|
508
|
+
doc.add_paragraph("[Empty Page or Unextractable Layout Text Element]")
|
|
509
|
+
|
|
510
|
+
doc.save(output_path)
|
|
511
|
+
console.print(f"[bold green]Success![/bold green] Document compiled cleanly to {output_path}")
|
|
512
|
+
except Exception as e:
|
|
513
|
+
console.print(f"[bold red]DOCX composition failed:[/bold red] {e}")
|
|
514
|
+
raise typer.Exit(code=1)
|
convashun/image.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
import subprocess
|
|
3
|
+
import shutil
|
|
4
|
+
from PIL import Image
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
|
|
8
|
+
app = typer.Typer()
|
|
9
|
+
console = Console()
|
|
10
|
+
|
|
11
|
+
@app.command("to-png")
|
|
12
|
+
def to_png(
|
|
13
|
+
input_path: Path = typer.Argument(
|
|
14
|
+
...,
|
|
15
|
+
exists=True,
|
|
16
|
+
file_okay=True,
|
|
17
|
+
dir_okay=False,
|
|
18
|
+
readable=True,
|
|
19
|
+
help="Path to the source image file"
|
|
20
|
+
),
|
|
21
|
+
optimize: bool = typer.Option(
|
|
22
|
+
False,
|
|
23
|
+
"--optimize", "-o",
|
|
24
|
+
help="Optimize the PNG file size (takes slightly longer to save)"
|
|
25
|
+
),
|
|
26
|
+
):
|
|
27
|
+
"""
|
|
28
|
+
Convert any incoming image file to PNG format.
|
|
29
|
+
"""
|
|
30
|
+
console.print(f"[yellow]Processing:[/yellow] {input_path.name}...")
|
|
31
|
+
|
|
32
|
+
# Cross-platform safe path modification
|
|
33
|
+
output_path = input_path.with_suffix(".png")
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
with Image.open(input_path) as img:
|
|
37
|
+
if img.mode == "CMYK":
|
|
38
|
+
img = img.convert("RGB")
|
|
39
|
+
|
|
40
|
+
img.save(output_path, format="PNG", optimize=optimize)
|
|
41
|
+
console.print(f"[bold green]Success![/bold green] Saved to {output_path}")
|
|
42
|
+
except Exception as e:
|
|
43
|
+
console.print(f"[bold red]Error converting file:[/bold red] {e}")
|
|
44
|
+
raise typer.Exit(code=1)
|
|
45
|
+
|
|
46
|
+
@app.command("to-webp")
|
|
47
|
+
def to_png(
|
|
48
|
+
input_path: Path = typer.Argument(
|
|
49
|
+
...,
|
|
50
|
+
exists=True,
|
|
51
|
+
file_okay=True,
|
|
52
|
+
dir_okay=False,
|
|
53
|
+
readable=True,
|
|
54
|
+
help="Path to the source image file"
|
|
55
|
+
),
|
|
56
|
+
quality: int = typer.Option(
|
|
57
|
+
90,
|
|
58
|
+
"--quality", "-q",
|
|
59
|
+
min=1, max=100,
|
|
60
|
+
help="Compression quality (1-100). 100 means lossless."
|
|
61
|
+
),
|
|
62
|
+
):
|
|
63
|
+
"""
|
|
64
|
+
Convert any incoming image file to WEBP format.
|
|
65
|
+
"""
|
|
66
|
+
console.print(f"[yellow]Processing:[/yellow] {input_path.name}...")
|
|
67
|
+
|
|
68
|
+
# Cross-platform safe path modification
|
|
69
|
+
output_path = input_path.with_suffix(".webp")
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
with Image.open(input_path) as img:
|
|
73
|
+
if img.mode == "CMYK":
|
|
74
|
+
img = img.convert("RGB")
|
|
75
|
+
|
|
76
|
+
img.save(output_path, format="WEBP", quality=quality)
|
|
77
|
+
|
|
78
|
+
console.print(f"[bold green]Success![/bold green] Saved to {output_path} (Quality: {quality})")
|
|
79
|
+
except Exception as e:
|
|
80
|
+
console.print(f"[bold red]Error converting file:[/bold red] {e}")
|
|
81
|
+
raise typer.Exit(code=1)
|
|
82
|
+
|
|
83
|
+
@app.command("to-jpg")
|
|
84
|
+
def to_jpg(
|
|
85
|
+
input_path: Path = typer.Argument(
|
|
86
|
+
...,
|
|
87
|
+
exists=True,
|
|
88
|
+
file_okay=True,
|
|
89
|
+
dir_okay=False,
|
|
90
|
+
readable=True,
|
|
91
|
+
help="Path to the source image file"
|
|
92
|
+
),
|
|
93
|
+
quality: int = typer.Option(
|
|
94
|
+
85,
|
|
95
|
+
"--quality", "-q",
|
|
96
|
+
min=1, max=100,
|
|
97
|
+
help="JPEG compression quality (1-100)"
|
|
98
|
+
),
|
|
99
|
+
):
|
|
100
|
+
"""
|
|
101
|
+
Convert any incoming image file to JPG format.
|
|
102
|
+
"""
|
|
103
|
+
console.print(f"[yellow]Processing:[/yellow] {input_path.name}...")
|
|
104
|
+
output_path = input_path.with_suffix(".jpg")
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
with Image.open(input_path) as img:
|
|
108
|
+
# Crucial: If the input has transparency (RGBA or P),
|
|
109
|
+
# we must flatten it onto a solid background color (white)
|
|
110
|
+
if img.mode in ("RGBA", "P"):
|
|
111
|
+
background = Image.new("RGB", img.size, (255, 255, 255))
|
|
112
|
+
# Paste the transparent image on top of the white background
|
|
113
|
+
background.paste(img, mask=img.convert("RGBA").split()[3])
|
|
114
|
+
img = background
|
|
115
|
+
elif img.mode == "CMYK":
|
|
116
|
+
img = img.convert("RGB")
|
|
117
|
+
|
|
118
|
+
img.save(output_path, format="JPEG", quality=quality)
|
|
119
|
+
|
|
120
|
+
console.print(f"[bold green]Success![/bold green] Saved to {output_path} (Quality: {quality})")
|
|
121
|
+
except Exception as e:
|
|
122
|
+
console.print(f"[bold red]Error converting file:[/bold red] {e}")
|
|
123
|
+
raise typer.Exit(code=1)
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: convashun
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Ultra-fast, zero-config, cross-platform file conversion utility optimized for developers, documentation teams, and automated robotics data pipelines.
|
|
5
|
+
Author-email: Emmanuel Omoiya <emmanuelomoiya6@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Classifier: Environment :: Console
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Requires-Python: <3.13,>=3.12
|
|
13
|
+
Requires-Dist: click<8.2.0,>=8.1.7
|
|
14
|
+
Requires-Dist: openpyxl>=3.1.5
|
|
15
|
+
Requires-Dist: pandas>=3.0.3
|
|
16
|
+
Requires-Dist: pillow<11.0.0
|
|
17
|
+
Requires-Dist: pypdf>=6.12.1
|
|
18
|
+
Requires-Dist: python-docx>=1.2.0
|
|
19
|
+
Requires-Dist: reportlab>=4.5.1
|
|
20
|
+
Requires-Dist: rich>=13.7.0
|
|
21
|
+
Requires-Dist: ruff>=0.15.14
|
|
22
|
+
Requires-Dist: typer<0.10.0,>=0.9.0
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# convashun 🟢
|
|
26
|
+
|
|
27
|
+
[](https://github.com)
|
|
28
|
+
[](https://pypi.org)
|
|
29
|
+
[](https://opensource.org)
|
|
30
|
+
[](https://pypi.org)
|
|
31
|
+
|
|
32
|
+
> ⚡ Ultra-fast, zero-config, cross-platform file conversion utility optimized for developers, documentation teams, and automated robotics data pipelines.
|
|
33
|
+
|
|
34
|
+
Built entirely on top of modern Python type hints (`Typer`), blazingly fast package architecture (`uv`), and robust data utilities. Developed with love for open-source engineering workflows and engineered to seamlessly support the data tracking, documentation, and asset conversion pipelines of communities like **Aurora Robotics**.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 📊 Conversion Matrix
|
|
39
|
+
|
|
40
|
+
`convashun` maps target conversions dynamically using deep binary validation rather than flimsy extension strings.
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
| Category | Input Formats | Targeted Output Command | Pure Python? | System Dependencies |
|
|
44
|
+
| :--- | :--- | :--- | :--- | :--- |
|
|
45
|
+
| **Images** | JPG, PNG, WEBP, BMP, GIF, TIFF | `convert image to-png` | Yes | None |
|
|
46
|
+
| **Images** | JPG, PNG, WEBP, BMP, GIF, TIFF | `convert image to-webp` | Yes | None |
|
|
47
|
+
| **Images** | JPG, PNG, WEBP, BMP, GIF, TIFF | `convert image to-jpg` | Yes | None |
|
|
48
|
+
| **Spreadsheets** | CSV (Comma Separated Log files) | `convert document to-xlsx` | Yes | None |
|
|
49
|
+
| **Spreadsheets** | XLSX (Multi-sheet spreadsheet layouts)| `convert document xlsx-to-csv` | Yes | None |
|
|
50
|
+
| **Documentation** | PDF Documents | `convert document to-docx` | Yes | None |
|
|
51
|
+
| **Documentation** | PDF Documents | `convert document to-markdown`| Yes | None |
|
|
52
|
+
| **Documentation** | Markdown Markup File (`.md`) | `convert document markdown-to-pdf`| Yes | None |
|
|
53
|
+
| **Office/Academics**| DOCX, DOC, ODT, PPTX | `convert document to-pdf` | No | LibreOffice |
|
|
54
|
+
| **Office/Academics**| PowerPoint Presentations (`.pptx`) | `convert document pptx-to-pdf`| No | LibreOffice |
|
|
55
|
+
| **Office/Academics**| LaTeX Documents (`.tex`) | `convert document tex-to-pdf` | Yes | None |
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 🚀 Quick Start & Installation
|
|
60
|
+
|
|
61
|
+
`convashun` works identically across Windows, macOS, and Linux distributions.
|
|
62
|
+
|
|
63
|
+
### Method 1: Using `uv` (Highly Recommended)
|
|
64
|
+
Install `convashun` instantly into an isolated, globally accessible environment managed entirely by `uv`:
|
|
65
|
+
```bash
|
|
66
|
+
uv tool install convashun
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Method 2: Standard Installation via PyPI
|
|
70
|
+
```bash
|
|
71
|
+
pip install convashun
|
|
72
|
+
# Or use pipx to isolate system paths cleanly
|
|
73
|
+
pipx install convashun
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 🛠️ Usage Examples
|
|
79
|
+
|
|
80
|
+
Once installed, use the global entry command `convert` directly in your terminal interface:
|
|
81
|
+
|
|
82
|
+
### 🖼️ Image Operations
|
|
83
|
+
```bash
|
|
84
|
+
# Convert an image into WebP format with custom compression quality settings
|
|
85
|
+
convert image to-webp input_graphic.png --quality 85
|
|
86
|
+
|
|
87
|
+
# Flatten a complex transparent asset safely into a JPG with a solid white backing
|
|
88
|
+
convert image to-jpg transparent_blueprint.png
|
|
89
|
+
|
|
90
|
+
# Optimize a standard PNG file size automatically without changing resolution parameters
|
|
91
|
+
convert image to-png photographic_capture.jpg --optimize
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 📄 Document Layouts & Spreadsheet Processing
|
|
95
|
+
```bash
|
|
96
|
+
# Compile a Markdown README specification file directly into a portable PDF manual
|
|
97
|
+
convert document markdown-to-pdf ARCHITECTURE.md
|
|
98
|
+
|
|
99
|
+
# Extract telemetry or configuration records from an Excel document directly down to a CSV log
|
|
100
|
+
convert document xlsx-to-csv metrics_workbook.xlsx --sheet "TelemetryLogs"
|
|
101
|
+
|
|
102
|
+
# Convert an uneditable PDF report layout back into a semantic Markdown string file
|
|
103
|
+
convert document to-markdown datasheet_report.pdf
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 📦 System Prerequisites (For Office Documents Only)
|
|
109
|
+
|
|
110
|
+
Commands such as `to-pdf` (for `.docx` and `.pptx`) use automated background processes to render layout definitions precisely. If a command prompts you with a missing dependency, install LibreOffice on your path using your platform package engine:
|
|
111
|
+
|
|
112
|
+
* **macOS:** `brew install libreoffice`
|
|
113
|
+
* **Linux (Ubuntu/Debian):** `sudo apt install libreoffice`
|
|
114
|
+
* **Windows (PowerShell WinGet):** `winget install LibreOffice.LibreOffice`
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## 🟢 Open Source Contributions
|
|
119
|
+
|
|
120
|
+
We value input from engineering collaborators and the robotics community!
|
|
121
|
+
|
|
122
|
+
To add optimization modules or extend format parsers:
|
|
123
|
+
1. Review our structured guidelines inside [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
124
|
+
2. Set up your local sandboxed tracking workspace via `uv`:
|
|
125
|
+
```bash
|
|
126
|
+
git clone https://github.com
|
|
127
|
+
cd convashun
|
|
128
|
+
uv sync --dev
|
|
129
|
+
```
|
|
130
|
+
3. Run the complete automated multi-OS testing validation matrix locally before pushing changes:
|
|
131
|
+
```bash
|
|
132
|
+
uv run pytest
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## 📄 License
|
|
138
|
+
|
|
139
|
+
Distributed safely under the terms of the open-source **MIT License**. Check out [LICENSE](LICENSE) for exact operational data guidelines.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
convashun/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
convashun/cli.py,sha256=70O-LqwGPxdou_J-ldoN7AUfWmraYsJtqraTs7x9WyQ,753
|
|
3
|
+
convashun/document.py,sha256=S7Drl0_zYnVsn3T3KmIj-rohXjlFIXBGWTcLu6qN54E,21826
|
|
4
|
+
convashun/image.py,sha256=-TTirZxpPKgACbrSmowo3Re3uulqqgSZ-0LvsMLjCpw,3831
|
|
5
|
+
convashun-0.1.0.dist-info/METADATA,sha256=1UAdOrlZPg8gaEBvjwGNN7O0k8wWfqg9KTHA4B78T6s,5652
|
|
6
|
+
convashun-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
7
|
+
convashun-0.1.0.dist-info/entry_points.txt,sha256=W94Q7BFnXOqRywszW8xU4aVOCQjmkrQEXLkTtfTqjgg,46
|
|
8
|
+
convashun-0.1.0.dist-info/licenses/LICENSE,sha256=VLGELCbLgyTym14JsR_0iY2m-rnwaAlCpoTFtgYW9nA,1067
|
|
9
|
+
convashun-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Emmanuel Omoiya
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|