d
This commit is contained in:
214
docs/tutorial.md
214
docs/tutorial.md
@@ -1,214 +0,0 @@
|
||||
# set up config.conf
|
||||
the config file is meant to be modular, you only need to put in what you want. technically you do not need a file store, but you will miss out on a bunch features. For this tutorial we are going to make an example folderstore. Add the below to your config.conf, pick a path for you that is a blank folder non-system folder.
|
||||
|
||||
|
||||
<figure>
|
||||
<figcaption>config.conf</figcaption>
|
||||
<pre><code class="language-powershell">[store=folder]
|
||||
name="tutorial"
|
||||
path="C:\Users\Admin\Downloads\tutorial"
|
||||
</code></pre>
|
||||
</figure>
|
||||
|
||||
|
||||
after your done save the file and restart the cli.py
|
||||
|
||||
# Downloading from youtube
|
||||
### cookies.txt required - [cookies.txt guide](cookies.md)
|
||||
start up the cli and enter this at prompt, you can copy and paste
|
||||
|
||||
<figure>
|
||||
<figcaption><🜂🜄|🜁🜃></figcaption>
|
||||
<pre><code class="language-powershell">download-media "https://www.youtube.com/watch?v=_23dFb50Z2Y" | add-file -store tutorial
|
||||
</code></pre>
|
||||
</figure>
|
||||
|
||||

|
||||
this shows the available formats you can download, the audio is audio only, and the video will automatically merge audio so you only need to pick the video if you want video and audio. it is ordered best quality at the lowest/highest number down.
|
||||
|
||||
the # column is how you select what you want to pick, run this next
|
||||
|
||||
<figure>
|
||||
<figcaption><🜂🜄|🜁🜃></figcaption>
|
||||
<pre><code class="language-powershell">@8
|
||||
</code></pre>
|
||||
</figure>
|
||||
|
||||

|
||||
|
||||
files inside of stores are renamed as their hash, you will only be able to find your file by searching for it, the title: tag acts as a psuedo filename, if you search "terry" then the file will be brought up, if you search "archeroflusitania" the file will not come up, you need to prepend the namespace first, "channel:archeroflusitania"; you can also use wild card "channel:arch*" and that will pull it up. lets see what tags the file has, run this.
|
||||
|
||||
<figure>
|
||||
<figcaption><🜂🜄|🜁🜃></figcaption>
|
||||
<pre><code class="language-powershell">@1 | get-tag
|
||||
</code></pre>
|
||||
</figure>
|
||||
|
||||

|
||||
these tags are scraped from the youtube video using yt-dlp, they are stored in your store's database, this is how you will actually find your files so make sure it has either a title: tag that you can look up or you add your custom tags
|
||||
now lets add more tags, run this
|
||||
|
||||
<figure>
|
||||
<figcaption><🜂🜄|🜁🜃></figcaption>
|
||||
<pre><code class="language-powershell">@ | add-tag "cli,ubuntu,desktop"
|
||||
</code></pre>
|
||||
</figure>
|
||||
|
||||

|
||||
|
||||
we added freeform tags, freeform tags are tags that dont have colons in them, these tags show up in searches without any special prepends. run the following below to search for our new tags added to the file.
|
||||
|
||||
<figure>
|
||||
<figcaption><🜂🜄|🜁🜃></figcaption>
|
||||
<pre><code class="language-powershell">search-file "ubuntu"
|
||||
</code></pre>
|
||||
</figure>
|
||||
|
||||

|
||||
|
||||
to access your file and view it, you can run either
|
||||
|
||||
@1 | get-file
|
||||
|
||||
or if you have mpv installed (the preferred way for video files)
|
||||
|
||||
(Tip: the bootstrap/setup scripts will try to install `mpv` for you if it's not found on PATH. If it isn't available, install `mpv` manually and ensure `mpv` is on your PATH.)
|
||||
|
||||
@1 | .pipe
|
||||
|
||||
# Bandcamp downloading (provider method)
|
||||
<figure>
|
||||
<figcaption><🜂🜄|🜁🜃></figcaption>
|
||||
<pre><code class="language-powershell">search-file -provider bandcamp -query "artist:altrusian grace media"
|
||||
</code></pre>
|
||||
</figure>
|
||||
|
||||

|
||||
this brings up special scraper for bandcamp on the artist page, the query syntax of "artist:" is how we parse args for some cmdlets. next run the following
|
||||
|
||||
<figure>
|
||||
<figcaption><🜂🜄|🜁🜃></figcaption>
|
||||
<pre><code class="language-powershell">@1
|
||||
</code></pre>
|
||||
</figure>
|
||||
|
||||

|
||||
this shows a list of bandcamp album links, go ahead and enter this below
|
||||
|
||||
<figure>
|
||||
<figcaption><🜂🜄|🜁🜃></figcaption>
|
||||
<pre><code class="language-powershell">@8
|
||||
</code></pre>
|
||||
</figure>
|
||||
|
||||

|
||||
this shows a list of bandcamp album link, if you look at the title of the table, download-media -url "https://altrusiangrace.bandcamp.com/album/zetetic-astronomy-earth-not-a-globe-full-audiobook" , you could just copy the url from webpage and paste it as this command to get the table without going through the provider search. In this table, all these are playlist items, each item is its own file but in this case its connected and they are in an album. if you wanted to only download a couple you could do @1,5,7 and that would only download those items, you can also do a range @1-6 and that will download 1 through 6. For this tutorial, run this below.
|
||||
|
||||
<figure>
|
||||
<figcaption><🜂🜄|🜁🜃></figcaption>
|
||||
<pre><code class="language-powershell">@* | merge-file | add-file -store tutorial
|
||||
</code></pre>
|
||||
</figure>
|
||||
|
||||
this will download the entire playlist, merge them into one file while putting chapter markers for the file merge points.
|
||||
|
||||
# screenshot
|
||||
|
||||
medios can take screenshots of pages and output them as pdf, jpg, png, and webp. the default is webp as its the smallest format. the screenshot is fullscreen and scrolls. run this below
|
||||
|
||||
<figure>
|
||||
<figcaption><🜂🜄|🜁🜃></figcaption>
|
||||
<pre><code class="language-powershell">screen-shot "https://www.timecube.net/" | add-tag "title:timecube homepage" | add-file -store tutorial
|
||||
</code></pre>
|
||||
</figure>
|
||||
|
||||

|
||||
|
||||
<figure>
|
||||
<figcaption><🜂🜄|🜁🜃></figcaption>
|
||||
<pre><code class="language-powershell">@1 | get-file
|
||||
</code></pre>
|
||||
</figure>
|
||||
|
||||
this opens up a webrowser with your media file embedded, you can copy and paste or if you want to share on a public hoster, you can do this
|
||||
|
||||
<figure>
|
||||
<figcaption><🜂🜄|🜁🜃></figcaption>
|
||||
<pre><code class="language-powershell">@1 | add-file -provider 0x0
|
||||
</code></pre>
|
||||
</figure>
|
||||
|
||||
this will upload your media file to 0x0.com and at the end provide you a link, you dont need to worry about saving it, you can always retrieve the link where you downloaded the file by running
|
||||
|
||||
<figure>
|
||||
<figcaption><🜂🜄|🜁🜃></figcaption>
|
||||
<pre><code class="language-powershell">@1 | get-url
|
||||
</code></pre>
|
||||
</figure>
|
||||
|
||||

|
||||
|
||||
# soul seek
|
||||
## soul-seek requires a psuedo account
|
||||
to make an account in soulseek, simple enter in random username and password and put in your config.conf. you do not need to create an account on the website just put in random name that isint taken with random password, dont user special symbols or spaces.
|
||||
|
||||
<figure>
|
||||
<figcaption>config.conf</figcaption>
|
||||
<pre><code class="language-powershell">[provider=soulseek]
|
||||
username="putinrandomusername"
|
||||
password="putinrandompassword"
|
||||
</code></pre>
|
||||
</figure>
|
||||
|
||||
restart the cli and check the startup table, if soulseek says ENABLED then you are good to go, run this.
|
||||
|
||||
<figure>
|
||||
<figcaption><🜂🜄|🜁🜃></figcaption>
|
||||
<pre><code class="language-powershell">search-file -provider soulseek "erika herms niel"
|
||||
</code></pre>
|
||||
</figure>
|
||||
|
||||

|
||||
|
||||
next run
|
||||
|
||||
<figure>
|
||||
<figcaption><🜂🜄|🜁🜃></figcaption>
|
||||
<pre><code class="language-powershell">@13 | add-file -store tutorial
|
||||
</code></pre>
|
||||
</figure>
|
||||
|
||||
this will download and inject into your tutorial store, keep in mind that soulseek is p2p, so download speeds will vary and it may take a minute to connect.
|
||||
|
||||
# Open Library
|
||||
## OPENLIBRARY/ARCHIVE.ORG ACCOUNT REQUIRED
|
||||
[Open Library](https://archive.org/account/signup)
|
||||
throw away account is fine to create.
|
||||
|
||||
<figure>
|
||||
<figcaption>config.conf</figcaption>
|
||||
<pre><code class="language-powershell">[provider=OpenLibrary]
|
||||
email=""
|
||||
password=""
|
||||
</code></pre>
|
||||
</figure>
|
||||
|
||||
|
||||
openlibrary allows us to borrow books, merge them into a permement pdf, then return the book. run this below
|
||||
|
||||
<figure>
|
||||
<figcaption><🜂🜄|🜁🜃></figcaption>
|
||||
<pre><code class="language-powershell">download-file "https://archive.org/details/powerofsatiremag0000elli_h4h9" | add-file -store tutorial
|
||||
</code></pre>
|
||||
</figure>
|
||||
|
||||
we could have use the search-file -provider openlibrary, but to show the versatile of the app, we able to use download-file and medios will be able to intelligently direct it the correct provider (with exception of download-media, download-media is just the frontend for yt-dlp).
|
||||
|
||||
# Libgen
|
||||
libgen is self-explanatory,
|
||||
|
||||
<figure>
|
||||
<figcaption><🜂🜄|🜁🜃></figcaption>
|
||||
<pre><code class="language-powershell">download-file "https://libgen.gl/ads.php?md5=5c258cc177c1d735c7acfb60fbdb14bf&downloadname=10.1515/9781400870080-009" | add-file -store tutorial
|
||||
</code></pre>
|
||||
</figure>
|
||||
@@ -1,61 +0,0 @@
|
||||
"""Entry point wrapper for Medeia-Macina CLI.
|
||||
|
||||
This file is intentionally backwards-compatible. When installed from the
|
||||
packaged distribution the preferred entry is `medeia_macina.cli_entry.main`.
|
||||
When running from the repository (or in legacy installs) the module will
|
||||
attempt to import `MedeiaCLI` from the top-level `CLI` module.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def _run_packaged_entry(argv=None) -> int:
|
||||
"""Try to delegate to the packaged entry (`medeia_macina.cli_entry:main`)."""
|
||||
try:
|
||||
from medeia_macina.cli_entry import main as _main
|
||||
|
||||
return int(_main(argv) or 0)
|
||||
except Exception:
|
||||
return -1
|
||||
|
||||
|
||||
def _run_legacy_entry() -> None:
|
||||
"""Legacy behaviour: make repo root importable and run CLI.
|
||||
|
||||
This supports running directly from the source tree where `CLI.py` is
|
||||
available as a top-level module.
|
||||
"""
|
||||
root_dir = Path(__file__).resolve().parent
|
||||
if str(root_dir) not in sys.path:
|
||||
sys.path.insert(0, str(root_dir))
|
||||
|
||||
try:
|
||||
from CLI import MedeiaCLI
|
||||
except Exception as exc: # pragma: no cover - user environment issues
|
||||
raise ImportError(
|
||||
"Could not import 'MedeiaCLI' from top-level 'CLI'. "
|
||||
"If you installed the package into a virtualenv, activate it and run: \n"
|
||||
" pip install -e .\n"
|
||||
"or re-run the project bootstrap to ensure an up-to-date install."
|
||||
) from exc
|
||||
|
||||
if __name__ == "__main__":
|
||||
MedeiaCLI().run()
|
||||
|
||||
|
||||
# Backward-compatibility: try to expose `MedeiaCLI` at import-time when the
|
||||
# project is being used from a development checkout (so modules that import
|
||||
# the top-level `medeia_entry` can still access the CLI class).
|
||||
try:
|
||||
from CLI import MedeiaCLI as MedeiaCLI # type: ignore
|
||||
except Exception:
|
||||
# It's okay if the legacy top-level CLI isn't importable in installed packages.
|
||||
pass
|
||||
|
||||
if __name__ == "__main__":
|
||||
rc = _run_packaged_entry(sys.argv[1:])
|
||||
if rc >= 0:
|
||||
raise SystemExit(rc)
|
||||
# Fall back to legacy import when packaged entry couldn't be invoked.
|
||||
_run_legacy_entry()
|
||||
@@ -1,7 +1,9 @@
|
||||
"""CLI entrypoint module compatible with console scripts.
|
||||
"""Packaged CLI entrypoint used by installers and console scripts.
|
||||
|
||||
This wraps the existing `medeia_entry.py` runner so installers can set
|
||||
entry points to `medeia_macina.cli_entry:main`.
|
||||
This module provides the `main` entrypoint for `mm`/`medeia` and supports
|
||||
running from a development checkout (by importing the top-level
|
||||
`CLI.MedeiaCLI`) or when running tests that inject a legacy
|
||||
`medeia_entry` shim into `sys.modules`.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -128,86 +130,44 @@ def _parse_mode_and_strip_args(args: List[str]) -> Tuple[Optional[str], List[str
|
||||
return mode, out
|
||||
|
||||
|
||||
def _import_medeia_entry_module():
|
||||
"""Import and return the top-level 'medeia_entry' module.
|
||||
|
||||
This attempts a regular import first. If that fails with ImportError it will
|
||||
try a few fallbacks useful for editable installs and running directly from
|
||||
the repository (searching for .egg-link, walking parents, or checking CWD).
|
||||
"""
|
||||
try:
|
||||
_ensure_repo_root_on_sys_path()
|
||||
return importlib.import_module("medeia_entry")
|
||||
except ImportError:
|
||||
# Try to find the project root next to this installed package
|
||||
pkg_dir = Path(__file__).resolve().parent
|
||||
|
||||
# 1) Look for an .egg-link that points to the project root
|
||||
try:
|
||||
for egg in pkg_dir.glob("*.egg-link"):
|
||||
try:
|
||||
project_root = egg.read_text().splitlines()[0].strip()
|
||||
if project_root:
|
||||
candidate = Path(project_root) / "medeia_entry.py"
|
||||
if candidate.exists():
|
||||
if str(Path(project_root)) not in sys.path:
|
||||
sys.path.insert(0, str(Path(project_root)))
|
||||
return importlib.import_module("medeia_entry")
|
||||
except Exception:
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 2) Walk upwards looking for a top-level 'medeia_entry.py'
|
||||
for parent in pkg_dir.parents:
|
||||
candidate = parent / "medeia_entry.py"
|
||||
if candidate.exists():
|
||||
if str(parent) not in sys.path:
|
||||
sys.path.insert(0, str(parent))
|
||||
return importlib.import_module("medeia_entry")
|
||||
|
||||
# 3) Check current working directory
|
||||
candidate = Path.cwd() / "medeia_entry.py"
|
||||
if candidate.exists():
|
||||
if str(Path.cwd()) not in sys.path:
|
||||
sys.path.insert(0, str(Path.cwd()))
|
||||
return importlib.import_module("medeia_entry")
|
||||
|
||||
raise ImportError(
|
||||
"Could not import 'medeia_entry'. This often means the package is not installed into the active virtualenv or is an outdated install.\n"
|
||||
"Remedy: activate your venv and run: pip install -e . (or re-run the bootstrap script).\n"
|
||||
"If problems persist, recreate the venv and reinstall the project."
|
||||
)
|
||||
|
||||
|
||||
def _run_cli(clean_args: List[str]) -> int:
|
||||
"""Run the CLI runner (MedeiaCLI) with cleaned argv list."""
|
||||
"""Run the CLI runner (MedeiaCLI) with cleaned argv list.
|
||||
|
||||
The function supports three modes (in order):
|
||||
1) If a `medeia_entry` module is present in sys.modules (for tests/legacy
|
||||
scenarios), use it as the source of `MedeiaCLI`.
|
||||
2) If running from a development checkout, import the top-level `CLI` and
|
||||
use `MedeiaCLI` from there.
|
||||
3) Otherwise fail with a helpful message suggesting an editable install.
|
||||
"""
|
||||
try:
|
||||
sys.argv[1:] = list(clean_args)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
mod = _import_medeia_entry_module()
|
||||
|
||||
# Backwards compatibility: the imported module may not expose `MedeiaCLI` as
|
||||
# an attribute (for example, the installed `medeia_entry` delegates to the
|
||||
# packaged entrypoint instead of importing the top-level `CLI` module at
|
||||
# import-time). Try a few strategies to obtain or invoke the CLI:
|
||||
# 1) Check for an in-memory 'medeia_entry' module (tests/legacy installs)
|
||||
MedeiaCLI = None
|
||||
if "medeia_entry" in sys.modules:
|
||||
mod = sys.modules.get("medeia_entry")
|
||||
if hasattr(mod, "MedeiaCLI"):
|
||||
MedeiaCLI = getattr(mod, "MedeiaCLI")
|
||||
else:
|
||||
# Try importing the top-level `CLI` module directly (editable/repo mode).
|
||||
# Preserve the existing error message used by tests that inject a
|
||||
# dummy 'medeia_entry' without a `MedeiaCLI` attribute.
|
||||
raise ImportError(
|
||||
"Imported module 'medeia_entry' does not define 'MedeiaCLI' and direct import of top-level 'CLI' failed.\n"
|
||||
"Remedy: ensure the top-level 'medeia_entry' module exports 'MedeiaCLI' or run from the project root/debug the checkout."
|
||||
)
|
||||
|
||||
# 2) If no in-memory module provided the class, try importing the repo-root CLI
|
||||
if MedeiaCLI is None:
|
||||
try:
|
||||
_ensure_repo_root_on_sys_path()
|
||||
from CLI import MedeiaCLI as _M # type: ignore
|
||||
|
||||
MedeiaCLI = _M
|
||||
except Exception:
|
||||
raise ImportError(
|
||||
"Imported module 'medeia_entry' does not define 'MedeiaCLI' and direct import of top-level 'CLI' failed.\n"
|
||||
"Remedy: activate your venv and run: pip install -e . (or re-run the bootstrap script).\n"
|
||||
"If problems persist, recreate the venv and reinstall the project."
|
||||
"Could not import 'MedeiaCLI'. This often means the project is not available on sys.path (run 'pip install -e .' or re-run the bootstrap script)."
|
||||
)
|
||||
|
||||
try:
|
||||
|
||||
@@ -7,7 +7,7 @@ name = "medeia-macina"
|
||||
version = "0.1.0"
|
||||
description = "Comprehensive media management and search platform with support for local files, Hydrus database, torrents, books, and P2P networks"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.9,<3.12"
|
||||
requires-python = ">=3.9,<3.14"
|
||||
license = {text = "MIT"}
|
||||
authors = [
|
||||
{name = "Your Name", email = "your.email@example.com"}
|
||||
@@ -24,6 +24,7 @@ classifiers = [
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
"Topic :: Multimedia",
|
||||
"Topic :: Internet",
|
||||
]
|
||||
@@ -111,7 +112,6 @@ Repository = "https://github.com/yourusername/medeia-macina.git"
|
||||
Issues = "https://github.com/yourusername/medeia-macina/issues"
|
||||
|
||||
[tool.setuptools]
|
||||
py-modules = ["medeia_entry"]
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["."]
|
||||
|
||||
@@ -15,6 +15,8 @@ GIT CLONE https://code.glowers.club/goyimnose/Medios-Macina
|
||||
|
||||
1. run python scripts\bootstrap.py
|
||||
|
||||
- When run interactively (a normal terminal), `bootstrap.py` will show a short menu to Install or Uninstall the project. For non-interactive runs use flags such as `--no-playwright`, `--uninstall`, or `-y` to assume yes for confirmations.
|
||||
|
||||
2. rename config.conf.remove to config.conf the store=folder path should be empty folder with no other files in it.
|
||||
|
||||
```ini
|
||||
|
||||
@@ -580,11 +580,44 @@ if (Test-Path (Join-Path $repo 'CLI.py')) { & python (Join-Path $repo 'CLI.py')
|
||||
# fallback
|
||||
python -m medeia_macina.cli_entry @args
|
||||
'@
|
||||
# Inject the actual repo path safely (escape embedded double-quotes if any)
|
||||
# Thin wrapper: prefer the canonical Python bootstrap installer
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$repo = (Resolve-Path (Join-Path $scriptDir "..")).Path
|
||||
$venvPy = Join-Path $repo '.venv\Scripts\python.exe'
|
||||
|
||||
# Normalize incoming quiet flag (-Quiet) into -q for the Python script
|
||||
$forwardArgs = @()
|
||||
foreach ($a in $args) {
|
||||
if ($a -eq '-Quiet') { $forwardArgs += '-q' } else { $forwardArgs += $a }
|
||||
}
|
||||
|
||||
# Debug helper
|
||||
if ($env:MM_DEBUG) { Write-Host "MM_DEBUG: invoking python installer with args: $forwardArgs" -ForegroundColor Yellow }
|
||||
|
||||
if (Test-Path $venvPy) {
|
||||
& $venvPy (Join-Path $repo 'scripts\bootstrap.py') --no-delegate @forwardArgs
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
|
||||
# Fall back to any system Python (py -3 -> python3 -> python)
|
||||
if (Get-Command -Name py -ErrorAction SilentlyContinue) {
|
||||
& py -3 (Join-Path $repo 'scripts\bootstrap.py') --no-delegate @forwardArgs
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
if (Get-Command -Name python3 -ErrorAction SilentlyContinue) {
|
||||
& python3 (Join-Path $repo 'scripts\bootstrap.py') --no-delegate @forwardArgs
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
if (Get-Command -Name python -ErrorAction SilentlyContinue) {
|
||||
& python (Join-Path $repo 'scripts\bootstrap.py') --no-delegate @forwardArgs
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
|
||||
Write-Host 'Error: no suitable Python 3 interpreter found. Please install Python 3 or use the venv.' -ForegroundColor Red
|
||||
exit 127 # Inject the actual repo path safely (escape embedded double-quotes if any)
|
||||
$ps1Text = $ps1Text.Replace('__REPO__', $repo.Replace('"', '""'))
|
||||
# Ensure the PowerShell shim falls back to the correct module when the venv isn't present
|
||||
$ps1Text = $ps1Text.Replace(' -m medeia_entry ', ' -m medeia_macina.cli_entry ')
|
||||
$ps1Text = $ps1Text.Replace('python -m medeia_entry', 'python -m medeia_macina.cli_entry')
|
||||
# (No legacy 'medeia_entry' shim - use the packaged entry 'medeia_macina.cli_entry')
|
||||
if (Test-Path $mmPs1) {
|
||||
$bak = "$mmPs1.bak$(Get-Date -UFormat %s)"
|
||||
Move-Item -Path $mmPs1 -Destination $bak -Force
|
||||
|
||||
@@ -8,6 +8,11 @@ downloads Playwright browser binaries by running `python -m playwright install`.
|
||||
By default this script installs **Chromium** only to conserve space; pass
|
||||
`--browsers all` to install all supported engines (chromium, firefox, webkit).
|
||||
|
||||
Note: This Python script is the canonical installer for the project — prefer
|
||||
running `python ./scripts/bootstrap.py` locally. The platform scripts
|
||||
(`scripts/bootstrap.ps1` and `scripts/bootstrap.sh`) are now thin wrappers
|
||||
that delegate to this script (they call it with `--no-delegate -q`).
|
||||
|
||||
When invoked without any arguments, `bootstrap.py` will automatically select and
|
||||
run the platform-specific bootstrap helper (`scripts/bootstrap.ps1` on Windows
|
||||
or `scripts/bootstrap.sh` on POSIX) in **non-interactive (quiet)** mode so a
|
||||
@@ -230,6 +235,17 @@ def main() -> int:
|
||||
action="store_true",
|
||||
help="Only run 'playwright install' (skips dependency installation)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-delegate",
|
||||
action="store_true",
|
||||
help="Do not delegate to platform bootstrap scripts; run the Python bootstrap directly.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-q",
|
||||
"--quiet",
|
||||
action="store_true",
|
||||
help="Quiet mode: minimize informational output (useful when called from platform wrappers)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--browsers",
|
||||
type=str,
|
||||
@@ -264,18 +280,167 @@ def main() -> int:
|
||||
action="store_true",
|
||||
help="Upgrade pip/setuptools/wheel before installing requirements",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--uninstall",
|
||||
action="store_true",
|
||||
help="Uninstall local .venv and user shims (non-interactive)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-y",
|
||||
"--yes",
|
||||
action="store_true",
|
||||
help="Assume yes for confirmation prompts during uninstall",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
repo_root = Path(__file__).resolve().parent.parent
|
||||
|
||||
# If invoked without any arguments, prefer to delegate to the platform
|
||||
# bootstrap script (if present). The bootstrap scripts support a quiet/
|
||||
# non-interactive mode, which we use so "python ./scripts/bootstrap.py" just
|
||||
# does the right thing on Windows and *nix without extra flags.
|
||||
if len(sys.argv) == 1:
|
||||
# Helpers for interactive menu and uninstall detection
|
||||
def _venv_python_path(p: Path) -> Path | None:
|
||||
"""Return the path to a python executable inside a venv directory if present."""
|
||||
if (p / "Scripts" / "python.exe").exists():
|
||||
return p / "Scripts" / "python.exe"
|
||||
if (p / "bin" / "python").exists():
|
||||
return p / "bin" / "python"
|
||||
return None
|
||||
|
||||
def _is_installed() -> bool:
|
||||
"""Return True if the project appears installed into the local .venv."""
|
||||
vdir = repo_root / ".venv"
|
||||
py = _venv_python_path(vdir)
|
||||
if py is None:
|
||||
return False
|
||||
try:
|
||||
rc = subprocess.run([str(py), "-m", "pip", "show", "medeia-macina"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False)
|
||||
return rc.returncode == 0
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def _do_uninstall() -> int:
|
||||
"""Attempt to remove the local venv and any shims written to the user's bin."""
|
||||
vdir = repo_root / ".venv"
|
||||
if not vdir.exists():
|
||||
if not args.quiet:
|
||||
print("No local .venv found; nothing to uninstall.")
|
||||
return 0
|
||||
if not args.yes:
|
||||
try:
|
||||
prompt = input(f"Remove local virtualenv at {vdir} and installed user shims? [y/N]: ")
|
||||
except EOFError:
|
||||
print("Non-interactive environment; pass --uninstall --yes to uninstall without prompts.", file=sys.stderr)
|
||||
return 2
|
||||
if prompt.strip().lower() not in ("y", "yes"):
|
||||
print("Uninstall aborted.")
|
||||
return 1
|
||||
|
||||
# Remove repo-local launchers
|
||||
for name in ("mm", "mm.ps1", "mm.bat"):
|
||||
p = repo_root / name
|
||||
if p.exists():
|
||||
try:
|
||||
p.unlink()
|
||||
if not args.quiet:
|
||||
print(f"Removed local launcher: {p}")
|
||||
except Exception as exc:
|
||||
print(f"Warning: failed to remove {p}: {exc}", file=sys.stderr)
|
||||
|
||||
# Remove user shims that the installer may have written
|
||||
try:
|
||||
system = platform.system().lower()
|
||||
if system == "windows":
|
||||
user_bin = Path(os.environ.get("USERPROFILE", str(Path.home()))) / "bin"
|
||||
if user_bin.exists():
|
||||
for name in ("mm.cmd", "mm.ps1"):
|
||||
p = user_bin / name
|
||||
if p.exists():
|
||||
p.unlink()
|
||||
if not args.quiet:
|
||||
print(f"Removed user shim: {p}")
|
||||
else:
|
||||
user_bin = Path(os.environ.get("XDG_BIN_HOME", str(Path.home() / ".local/bin")))
|
||||
if user_bin.exists():
|
||||
p = user_bin / "mm"
|
||||
if p.exists():
|
||||
p.unlink()
|
||||
if not args.quiet:
|
||||
print(f"Removed user shim: {p}")
|
||||
except Exception as exc:
|
||||
print(f"Warning: failed to remove user shims: {exc}", file=sys.stderr)
|
||||
|
||||
# Remove .venv directory
|
||||
try:
|
||||
shutil.rmtree(vdir)
|
||||
if not args.quiet:
|
||||
print(f"Removed local virtualenv: {vdir}")
|
||||
except Exception as exc:
|
||||
print(f"Failed to remove venv: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
def _interactive_menu() -> str | int:
|
||||
"""Show a simple interactive menu to choose install/uninstall or delegate."""
|
||||
try:
|
||||
installed = _is_installed()
|
||||
while True:
|
||||
print("\nMedeia-Macina bootstrap - interactive menu")
|
||||
if installed:
|
||||
print("1) Install / Reinstall")
|
||||
print("2) Uninstall")
|
||||
print("3) Status")
|
||||
print("q) Quit")
|
||||
choice = input("Choose an option: ").strip().lower()
|
||||
if not choice or choice in ("1", "install", "reinstall"):
|
||||
return "install"
|
||||
if choice in ("2", "uninstall"):
|
||||
return "uninstall"
|
||||
if choice in ("3", "status"):
|
||||
print("Installation detected." if installed else "Not installed.")
|
||||
continue
|
||||
if choice in ("q", "quit", "exit"):
|
||||
return 0
|
||||
else:
|
||||
print("1) Install")
|
||||
print("q) Quit")
|
||||
choice = input("Choose an option: ").strip().lower()
|
||||
if not choice or choice in ("1", "install"):
|
||||
return "install"
|
||||
if choice in ("q", "quit", "exit"):
|
||||
return 0
|
||||
except EOFError:
|
||||
# Non-interactive, fall back to delegating to platform helper
|
||||
return "delegate"
|
||||
|
||||
# If the user passed --uninstall explicitly, perform non-interactive uninstall and exit
|
||||
if args.uninstall:
|
||||
return _do_uninstall()
|
||||
|
||||
# If invoked without any arguments and not asked to skip delegation, prefer
|
||||
# the interactive menu when running in a TTY; otherwise delegate to the
|
||||
# platform-specific bootstrap helper (non-interactive).
|
||||
if len(sys.argv) == 1 and not args.no_delegate:
|
||||
if sys.stdin.isatty() and not args.quiet:
|
||||
sel = _interactive_menu()
|
||||
if sel == "install":
|
||||
# user chose to install/reinstall; set defaults and continue
|
||||
args.skip_deps = False
|
||||
args.install_editable = True
|
||||
args.no_playwright = False
|
||||
elif sel == "uninstall":
|
||||
return _do_uninstall()
|
||||
elif sel == "delegate":
|
||||
rc = run_platform_bootstrap(repo_root)
|
||||
if rc != 0:
|
||||
return rc
|
||||
if not args.quiet:
|
||||
print("Platform bootstrap completed successfully.")
|
||||
return 0
|
||||
else:
|
||||
return int(sel or 0)
|
||||
else:
|
||||
rc = run_platform_bootstrap(repo_root)
|
||||
if rc != 0:
|
||||
return rc
|
||||
if not args.quiet:
|
||||
print("Platform bootstrap completed successfully.")
|
||||
return 0
|
||||
|
||||
@@ -295,14 +460,17 @@ def main() -> int:
|
||||
|
||||
try:
|
||||
if not venv_dir.exists():
|
||||
if not args.quiet:
|
||||
print(f"Creating local virtualenv at: {venv_dir}")
|
||||
run([sys.executable, "-m", "venv", str(venv_dir)])
|
||||
else:
|
||||
if not args.quiet:
|
||||
print(f"Using existing virtualenv at: {venv_dir}")
|
||||
|
||||
py = _venv_python(venv_dir)
|
||||
if not py.exists():
|
||||
# Try recreating venv if python is missing
|
||||
if not args.quiet:
|
||||
print(f"Local venv python not found at {py}; recreating venv")
|
||||
run([sys.executable, "-m", "venv", str(venv_dir)])
|
||||
py = _venv_python(venv_dir)
|
||||
@@ -315,6 +483,7 @@ def main() -> int:
|
||||
|
||||
# Ensure a local venv is present and use it for subsequent installs.
|
||||
venv_python = _ensure_local_venv()
|
||||
if not args.quiet:
|
||||
print(f"Using venv python: {venv_python}")
|
||||
|
||||
# Enforce opinionated behavior: install deps, playwright, deno, and install project in editable mode.
|
||||
@@ -326,9 +495,11 @@ def main() -> int:
|
||||
try:
|
||||
if args.playwright_only:
|
||||
if not playwright_package_installed():
|
||||
if not args.quiet:
|
||||
print("'playwright' package not found; installing it via pip...")
|
||||
run([sys.executable, "-m", "pip", "install", "playwright"])
|
||||
|
||||
if not args.quiet:
|
||||
print(
|
||||
"Installing Playwright browsers (this may download several hundred MB)..."
|
||||
)
|
||||
@@ -339,10 +510,12 @@ def main() -> int:
|
||||
return 2
|
||||
|
||||
run(cmd)
|
||||
if not args.quiet:
|
||||
print("Playwright browsers installed successfully.")
|
||||
return 0
|
||||
|
||||
if args.upgrade_pip:
|
||||
if not args.quiet:
|
||||
print("Upgrading pip, setuptools, and wheel in local venv...")
|
||||
run(
|
||||
[
|
||||
@@ -365,6 +538,7 @@ def main() -> int:
|
||||
file=sys.stderr,
|
||||
)
|
||||
else:
|
||||
if not args.quiet:
|
||||
print(
|
||||
f"Installing Python dependencies into local venv from {req_file}..."
|
||||
)
|
||||
@@ -372,9 +546,11 @@ def main() -> int:
|
||||
|
||||
if not args.no_playwright:
|
||||
if not playwright_package_installed():
|
||||
if not args.quiet:
|
||||
print("'playwright' package not installed in venv; installing it...")
|
||||
run([str(venv_python), "-m", "pip", "install", "playwright"])
|
||||
|
||||
if not args.quiet:
|
||||
print(
|
||||
"Installing Playwright browsers (this may download several hundred MB)..."
|
||||
)
|
||||
@@ -389,10 +565,12 @@ def main() -> int:
|
||||
run(cmd)
|
||||
|
||||
# Install the project into the local venv (editable mode is the default, opinionated)
|
||||
if not args.quiet:
|
||||
print("Installing project into local venv (editable mode)")
|
||||
run([str(venv_python), "-m", "pip", "install", "-e", "."])
|
||||
|
||||
# Verify top-level 'CLI' import and, if missing, attempt to make it available
|
||||
if not args.quiet:
|
||||
print("Verifying top-level 'CLI' import in venv...")
|
||||
try:
|
||||
rc = subprocess.run(
|
||||
@@ -477,6 +655,7 @@ def main() -> int:
|
||||
install_deno_requested = True
|
||||
|
||||
if install_deno_requested:
|
||||
if not args.quiet:
|
||||
print("Installing Deno runtime (local/system)...")
|
||||
rc = _install_deno(args.deno_version)
|
||||
if rc != 0:
|
||||
@@ -615,6 +794,7 @@ python -m medeia_macina.cli_entry @args
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not args.quiet:
|
||||
print(f"Installed global launchers to: {user_bin}")
|
||||
|
||||
else:
|
||||
@@ -716,6 +896,7 @@ python -m medeia_macina.cli_entry @args
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not args.quiet:
|
||||
print(f"Installed global launcher to: {mm_sh}")
|
||||
|
||||
except Exception as exc: # pragma: no cover - best effort
|
||||
|
||||
@@ -1,34 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
# Bootstrap script for POSIX (Linux/macOS) to create a Python venv and install the project.
|
||||
# Usage: scripts/bootstrap.sh [--editable] [--venv <path>] [--python <python>] [--desktop] [--no-install]
|
||||
set -e
|
||||
|
||||
# Ensure script is running under Bash. Some users invoke this script with `sh` (dash)
|
||||
# which does not support the Bash features used below (e.g., [[ ]], arrays, read -p).
|
||||
# If not running under Bash, re-exec using a discovered bash binary.
|
||||
if [ -z "${BASH_VERSION:-}" ]; then
|
||||
if command -v bash >/dev/null 2>&1; then
|
||||
echo "This script requires Bash; re-execing as 'bash $0'..."
|
||||
exec bash "$0" "$@"
|
||||
else
|
||||
echo "ERROR: This script requires Bash. Please run with 'bash $0' or install Bash." >&2
|
||||
exit 2
|
||||
fi
|
||||
# Thin POSIX wrapper that delegates to the canonical Python installer
|
||||
# (scripts/bootstrap.py). Platform bootstraps should prefer calling the
|
||||
# Python script using --no-delegate and -q/--quiet for quiet/non-interactive mode.
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO="$(cd "$SCRIPT_DIR/.." && pwd -P)"
|
||||
|
||||
# Prefer repo venv python, then system pythons
|
||||
if [ -x "$REPO/.venv/bin/python" ]; then
|
||||
PY="$REPO/.venv/bin/python"
|
||||
elif [ -x "$REPO/.venv/bin/python3" ]; then
|
||||
PY="$REPO/.venv/bin/python3"
|
||||
elif command -v python3 >/dev/null 2>&1; then
|
||||
PY="python3"
|
||||
elif command -v python >/dev/null 2>&1; then
|
||||
PY="python"
|
||||
else
|
||||
echo "Error: No Python interpreter found; please install Python 3 or create the project's .venv." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -euo pipefail
|
||||
# Translate -q into --quiet for the Python installer
|
||||
ARGS=()
|
||||
for a in "$@"; do
|
||||
if [ "$a" = "-q" ]; then
|
||||
ARGS+=("--quiet")
|
||||
else
|
||||
ARGS+=("$a")
|
||||
fi
|
||||
done
|
||||
|
||||
VENV_PATH=".venv"
|
||||
EDITABLE=false
|
||||
DESKTOP=false
|
||||
PYTHON_CMD=""
|
||||
NOINSTALL=false
|
||||
FORCE=false
|
||||
QUIET=false
|
||||
FIX_URLLIB3=false
|
||||
# Playwright options
|
||||
PLAYWRIGHT_BROWSERS="chromium" # comma-separated (chromium,firefox,webkit) or 'all'
|
||||
NO_PLAYWRIGHT=false
|
||||
REMOVE_PTH=false
|
||||
exec "$PY" "$REPO/scripts/bootstrap.py" --no-delegate "${ARGS[@]}"
|
||||
|
||||
# Prompt helper: read from the controlling terminal so prompts still work
|
||||
# when stdout/stderr are redirected or piped (e.g., piping output to sed).
|
||||
@@ -382,13 +386,8 @@ PY
|
||||
|
||||
else
|
||||
echo "WARNING: Could not import 'medeia_macina.cli_entry' from the venv." >&2
|
||||
# Check if legacy top-level module is present; if so, inform the user to prefer the packaged entrypoint
|
||||
if "$VENV_PY" -c 'import importlib; importlib.import_module("medeia_entry")' >/dev/null 2>&1; then
|
||||
echo "Note: 'medeia_entry' top-level module is present. It's recommended to install the project so 'medeia_macina.cli_entry' is available." >&2
|
||||
else
|
||||
echo "Action: Try running: $VENV_PY -m pip install -e . or inspect the venv site-packages to verify the installation." >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Verifying environment for known issues (urllib3 compatibility)..."
|
||||
if ! "$VENV_PY" -c 'from SYS.env_check import check_urllib3_compat; ok,msg = check_urllib3_compat(); print(msg); import sys; sys.exit(0 if ok else 2)'; then
|
||||
|
||||
724
scripts/setup.py
724
scripts/setup.py
@@ -1,724 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""DEPRECATED: scripts/setup.py
|
||||
|
||||
This file has been renamed to `scripts/bootstrap.py` to avoid having multiple
|
||||
`setup.py` files in the repository. Please use:
|
||||
|
||||
python ./scripts/bootstrap.py
|
||||
|
||||
This shim remains temporarily for backwards compatibility.
|
||||
|
||||
---
|
||||
|
||||
Original docstring:
|
||||
|
||||
scripts/setup.py
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import platform
|
||||
import shutil
|
||||
import os
|
||||
import time
|
||||
|
||||
|
||||
def run(cmd: list[str]) -> None:
|
||||
print(f"> {' '.join(cmd)}")
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
|
||||
# Helpers to find shell executables and to run the platform-specific
|
||||
# bootstrap script (scripts/bootstrap.sh or scripts/bootstrap.ps1).
|
||||
def _find_powershell() -> str | None:
|
||||
for name in ("pwsh", "powershell"):
|
||||
p = shutil.which(name)
|
||||
if p:
|
||||
return p
|
||||
return None
|
||||
|
||||
|
||||
def _find_shell() -> str | None:
|
||||
for name in ("bash", "sh"):
|
||||
p = shutil.which(name)
|
||||
if p:
|
||||
return p
|
||||
return None
|
||||
|
||||
|
||||
def run_platform_bootstrap(repo_root: Path) -> int:
|
||||
"""Run the platform bootstrap script in quiet/non-interactive mode if present.
|
||||
|
||||
Returns the script exit code (0 on success). If no script is present this is a
|
||||
no-op and returns 0.
|
||||
"""
|
||||
ps1 = repo_root / "scripts" / "bootstrap.ps1"
|
||||
sh_script = repo_root / "scripts" / "bootstrap.sh"
|
||||
|
||||
system = platform.system().lower()
|
||||
|
||||
if system == "windows" and ps1.exists():
|
||||
exe = _find_powershell()
|
||||
if not exe:
|
||||
print("PowerShell not found; cannot run bootstrap.ps1", file=sys.stderr)
|
||||
return 1
|
||||
cmd = [
|
||||
exe,
|
||||
"-NoProfile",
|
||||
"-NonInteractive",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
str(ps1),
|
||||
"-Quiet"
|
||||
]
|
||||
elif sh_script.exists():
|
||||
shell = _find_shell()
|
||||
if not shell:
|
||||
print("Shell not found; cannot run bootstrap.sh", file=sys.stderr)
|
||||
return 1
|
||||
# Use -q (quiet) to skip interactive prompts when supported.
|
||||
cmd = [shell, str(sh_script), "-q"]
|
||||
else:
|
||||
# Nothing to run
|
||||
return 0
|
||||
|
||||
print("Running platform bootstrap script:", " ".join(cmd))
|
||||
rc = subprocess.run(cmd, cwd=str(repo_root))
|
||||
if rc.returncode != 0:
|
||||
print(
|
||||
f"Bootstrap script failed with exit code {rc.returncode}",
|
||||
file=sys.stderr
|
||||
)
|
||||
return int(rc.returncode or 0)
|
||||
|
||||
|
||||
def playwright_package_installed() -> bool:
|
||||
try:
|
||||
import playwright # type: ignore
|
||||
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _build_playwright_install_cmd(browsers: str | None) -> list[str]:
|
||||
"""Return the command to install Playwright browsers.
|
||||
|
||||
- If browsers is None or empty: default to install Chromium only.
|
||||
- If browsers contains 'all': install all engines by running 'playwright install' with no extra args.
|
||||
- Otherwise, validate entries and return a command that installs the named engines.
|
||||
"""
|
||||
base = [sys.executable, "-m", "playwright", "install"]
|
||||
if not browsers:
|
||||
return base + ["chromium"]
|
||||
|
||||
items = [b.strip().lower() for b in browsers.split(",") if b.strip()]
|
||||
if not items:
|
||||
return base + ["chromium"]
|
||||
if "all" in items:
|
||||
return base
|
||||
|
||||
allowed = {"chromium",
|
||||
"firefox",
|
||||
"webkit"}
|
||||
invalid = [b for b in items if b not in allowed]
|
||||
if invalid:
|
||||
raise ValueError(
|
||||
f"invalid browsers specified: {invalid}. Valid choices: chromium, firefox, webkit, or 'all'"
|
||||
)
|
||||
return base + items
|
||||
|
||||
|
||||
def _install_deno(version: str | None = None) -> int:
|
||||
"""Install Deno runtime for the current platform.
|
||||
|
||||
Uses the official Deno install scripts:
|
||||
- Unix/macOS: curl -fsSL https://deno.land/x/install/install.sh | sh [-s <version>]
|
||||
- Windows: powershell iwr https://deno.land/x/install/install.ps1 -useb | iex; Install-Deno [-Version <version>]
|
||||
|
||||
Returns exit code 0 on success, non-zero otherwise.
|
||||
"""
|
||||
system = platform.system().lower()
|
||||
|
||||
try:
|
||||
if system == "windows":
|
||||
# Use official PowerShell installer
|
||||
if version:
|
||||
ver = version if version.startswith("v") else f"v{version}"
|
||||
ps_cmd = f"iwr https://deno.land/x/install/install.ps1 -useb | iex; Install-Deno -Version {ver}"
|
||||
else:
|
||||
ps_cmd = "iwr https://deno.land/x/install/install.ps1 -useb | iex"
|
||||
run(
|
||||
[
|
||||
"powershell",
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-Command",
|
||||
ps_cmd
|
||||
]
|
||||
)
|
||||
else:
|
||||
# POSIX: use curl + sh installer
|
||||
if version:
|
||||
ver = version if version.startswith("v") else f"v{version}"
|
||||
cmd = f"curl -fsSL https://deno.land/x/install/install.sh | sh -s {ver}"
|
||||
else:
|
||||
cmd = "curl -fsSL https://deno.land/x/install/install.sh | sh"
|
||||
run(["sh", "-c", cmd])
|
||||
|
||||
# Check that 'deno' is now available in PATH
|
||||
if shutil.which("deno"):
|
||||
print(f"Deno installed at: {shutil.which('deno')}")
|
||||
return 0
|
||||
else:
|
||||
print(
|
||||
"Deno installation completed but 'deno' not found in PATH. You may need to add Deno's bin directory to your PATH manually.",
|
||||
file=sys.stderr
|
||||
)
|
||||
return 1
|
||||
except subprocess.CalledProcessError as exc:
|
||||
print(f"Deno install failed: {exc}", file=sys.stderr)
|
||||
return int(exc.returncode or 1)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Setup Medios-Macina: install deps and Playwright browsers"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--skip-deps",
|
||||
action="store_true",
|
||||
help="Skip installing Python dependencies from requirements.txt"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-playwright",
|
||||
action="store_true",
|
||||
help="Skip running 'playwright install' (only install packages)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--playwright-only",
|
||||
action="store_true",
|
||||
help="Only run 'playwright install' (skips dependency installation)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--browsers",
|
||||
type=str,
|
||||
default="chromium",
|
||||
help=
|
||||
"Comma-separated list of browsers to install: chromium,firefox,webkit or 'all' (default: chromium)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--install-editable",
|
||||
action="store_true",
|
||||
help="Install the project in editable mode (pip install -e .) for running tests"
|
||||
)
|
||||
deno_group = parser.add_mutually_exclusive_group()
|
||||
deno_group.add_argument(
|
||||
"--install-deno",
|
||||
action="store_true",
|
||||
help="Install the Deno runtime (default behavior; kept for explicitness)"
|
||||
)
|
||||
deno_group.add_argument(
|
||||
"--no-deno",
|
||||
action="store_true",
|
||||
help="Skip installing Deno runtime (opt out)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--deno-version",
|
||||
type=str,
|
||||
default=None,
|
||||
help="Specific Deno version to install (e.g., v1.34.3)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--upgrade-pip",
|
||||
action="store_true",
|
||||
help="Upgrade pip/setuptools/wheel before installing requirements"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
repo_root = Path(__file__).resolve().parent.parent
|
||||
|
||||
# If invoked without any arguments, prefer to delegate to the platform
|
||||
# bootstrap script (if present). The bootstrap scripts support a quiet/
|
||||
# non-interactive mode, which we use so "python ./scripts/setup.py" just
|
||||
# does the right thing on Windows and *nix without extra flags.
|
||||
if len(sys.argv) == 1:
|
||||
rc = run_platform_bootstrap(repo_root)
|
||||
if rc != 0:
|
||||
return rc
|
||||
print("Platform bootstrap completed successfully.")
|
||||
return 0
|
||||
|
||||
if sys.version_info < (3, 8):
|
||||
print("Warning: Python 3.8+ is recommended.", file=sys.stderr)
|
||||
|
||||
# Opinionated: always create or use a local venv at the project root (.venv)
|
||||
venv_dir = repo_root / ".venv"
|
||||
|
||||
def _venv_python(p: Path) -> Path:
|
||||
if platform.system().lower() == "windows":
|
||||
return p / "Scripts" / "python.exe"
|
||||
return p / "bin" / "python"
|
||||
|
||||
def _ensure_local_venv() -> Path:
|
||||
"""Create (if missing) and return the path to the venv's python executable.
|
||||
|
||||
This is intentionally opinionated: we keep a venv at `./.venv` in the repo root
|
||||
and use that for all package operations to keep developer environments reproducible.
|
||||
"""
|
||||
try:
|
||||
if not venv_dir.exists():
|
||||
print(f"Creating local virtualenv at: {venv_dir}")
|
||||
run([sys.executable, "-m", "venv", str(venv_dir)])
|
||||
else:
|
||||
print(f"Using existing virtualenv at: {venv_dir}")
|
||||
|
||||
py = _venv_python(venv_dir)
|
||||
if not py.exists():
|
||||
# Try recreating venv if python is missing
|
||||
print(f"Local venv python not found at {py}; recreating venv")
|
||||
run([sys.executable, "-m", "venv", str(venv_dir)])
|
||||
py = _venv_python(venv_dir)
|
||||
if not py.exists():
|
||||
raise RuntimeError(f"Unable to locate venv python at {py}")
|
||||
return py
|
||||
except subprocess.CalledProcessError as exc:
|
||||
print(f"Failed to create or prepare local venv: {exc}", file=sys.stderr)
|
||||
raise
|
||||
|
||||
# Ensure a local venv is present and use it for subsequent installs.
|
||||
venv_python = _ensure_local_venv()
|
||||
print(f"Using venv python: {venv_python}")
|
||||
|
||||
# Enforce opinionated behavior: install deps, playwright, deno, and install project in editable mode.
|
||||
# Ignore `--skip-deps` and `--install-editable` flags to keep the setup deterministic.
|
||||
args.skip_deps = False
|
||||
args.install_editable = True
|
||||
args.no_playwright = False
|
||||
|
||||
try:
|
||||
if args.playwright_only:
|
||||
if not playwright_package_installed():
|
||||
print("'playwright' package not found; installing it via pip...")
|
||||
run([sys.executable, "-m", "pip", "install", "playwright"])
|
||||
|
||||
print(
|
||||
"Installing Playwright browsers (this may download several hundred MB)..."
|
||||
)
|
||||
try:
|
||||
cmd = _build_playwright_install_cmd(args.browsers)
|
||||
except ValueError as exc:
|
||||
print(f"Error: {exc}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
run(cmd)
|
||||
print("Playwright browsers installed successfully.")
|
||||
return 0
|
||||
|
||||
if args.upgrade_pip:
|
||||
print("Upgrading pip, setuptools, and wheel in local venv...")
|
||||
run(
|
||||
[
|
||||
str(venv_python),
|
||||
"-m",
|
||||
"pip",
|
||||
"install",
|
||||
"--upgrade",
|
||||
"pip",
|
||||
"setuptools",
|
||||
"wheel"
|
||||
]
|
||||
)
|
||||
|
||||
if not args.skip_deps:
|
||||
req_file = repo_root / "requirements.txt"
|
||||
if not req_file.exists():
|
||||
print(
|
||||
f"requirements.txt not found at {req_file}; skipping dependency installation.",
|
||||
file=sys.stderr
|
||||
)
|
||||
else:
|
||||
print(
|
||||
f"Installing Python dependencies into local venv from {req_file}..."
|
||||
)
|
||||
run([str(venv_python), "-m", "pip", "install", "-r", str(req_file)])
|
||||
|
||||
if not args.no_playwright:
|
||||
if not playwright_package_installed():
|
||||
print("'playwright' package not installed in venv; installing it...")
|
||||
run([str(venv_python), "-m", "pip", "install", "playwright"])
|
||||
|
||||
print(
|
||||
"Installing Playwright browsers (this may download several hundred MB)..."
|
||||
)
|
||||
try:
|
||||
cmd = _build_playwright_install_cmd(args.browsers)
|
||||
except ValueError as exc:
|
||||
print(f"Error: {exc}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
# Run Playwright install using the venv's python so binaries are available in venv
|
||||
cmd[0] = str(venv_python)
|
||||
run(cmd)
|
||||
|
||||
# Optional: install the project in editable mode so tests can import the package
|
||||
# Install the project into the local venv (editable mode is the default, opinionated)
|
||||
print("Installing project into local venv (editable mode)")
|
||||
run([str(venv_python), "-m", "pip", "install", "-e", "."])
|
||||
|
||||
# Verify top-level 'CLI' import and, if missing, attempt to make it available
|
||||
print("Verifying top-level 'CLI' import in venv...")
|
||||
try:
|
||||
import subprocess as _sub
|
||||
rc = _sub.run(
|
||||
[
|
||||
str(venv_python),
|
||||
"-c",
|
||||
"import importlib; importlib.import_module('CLI')"
|
||||
],
|
||||
check=False
|
||||
)
|
||||
if rc.returncode == 0:
|
||||
print("OK: top-level 'CLI' is importable in the venv.")
|
||||
else:
|
||||
print(
|
||||
"Top-level 'CLI' not importable; attempting to add repo path to venv site-packages via a .pth file..."
|
||||
)
|
||||
cmd = [
|
||||
str(venv_python),
|
||||
"-c",
|
||||
(
|
||||
"import site, sysconfig\n"
|
||||
"out=[]\n"
|
||||
"try:\n out.extend(site.getsitepackages())\nexcept Exception:\n pass\n"
|
||||
"try:\n p = sysconfig.get_paths().get('purelib')\n if p:\n out.append(p)\nexcept Exception:\n pass\n"
|
||||
"seen=[]; res=[]\n"
|
||||
"for x in out:\n if x and x not in seen:\n seen.append(x); res.append(x)\n"
|
||||
"for s in res:\n print(s)\n"
|
||||
)
|
||||
]
|
||||
out = _sub.check_output(cmd, text=True).strip().splitlines()
|
||||
site_dir = None
|
||||
for sp in out:
|
||||
if sp and Path(sp).exists():
|
||||
site_dir = Path(sp)
|
||||
break
|
||||
if site_dir is None:
|
||||
print(
|
||||
"Could not determine venv site-packages directory; skipping .pth fallback"
|
||||
)
|
||||
else:
|
||||
pth_file = site_dir / "medeia_repo.pth"
|
||||
if pth_file.exists():
|
||||
txt = pth_file.read_text(encoding="utf-8")
|
||||
if str(repo_root) in txt:
|
||||
print(f".pth already contains repo root: {pth_file}")
|
||||
else:
|
||||
with pth_file.open("a", encoding="utf-8") as fh:
|
||||
fh.write(str(repo_root) + "\n")
|
||||
print(f"Appended repo root to existing .pth: {pth_file}")
|
||||
else:
|
||||
with pth_file.open("w", encoding="utf-8") as fh:
|
||||
fh.write(str(repo_root) + "\n")
|
||||
print(
|
||||
f"Wrote .pth adding repo root to venv site-packages: {pth_file}"
|
||||
)
|
||||
|
||||
# Re-check whether CLI can be imported now
|
||||
rc2 = _sub.run(
|
||||
[
|
||||
str(venv_python),
|
||||
"-c",
|
||||
"import importlib; importlib.import_module('CLI')"
|
||||
],
|
||||
check=False
|
||||
)
|
||||
if rc2.returncode == 0:
|
||||
print("Top-level 'CLI' import works after adding .pth")
|
||||
else:
|
||||
print(
|
||||
"Adding .pth did not make top-level 'CLI' importable; consider creating an egg-link or checking the venv."
|
||||
)
|
||||
except Exception as exc:
|
||||
print(
|
||||
f"Warning: failed to verify or modify site-packages for top-level CLI: {exc}"
|
||||
)
|
||||
|
||||
# Optional: install Deno runtime (default: install unless --no-deno is passed)
|
||||
install_deno_requested = True
|
||||
if getattr(args, "no_deno", False):
|
||||
install_deno_requested = False
|
||||
elif getattr(args, "install_deno", False):
|
||||
install_deno_requested = True
|
||||
|
||||
if install_deno_requested:
|
||||
print("Installing Deno runtime (local/system)...")
|
||||
rc = _install_deno(args.deno_version)
|
||||
if rc != 0:
|
||||
print("Deno installation failed.", file=sys.stderr)
|
||||
return rc
|
||||
|
||||
# Write project-local launcher scripts (project root) that prefer the local .venv
|
||||
def _write_launchers():
|
||||
sh = repo_root / "mm"
|
||||
ps1 = repo_root / "mm.ps1"
|
||||
bat = repo_root / "mm.bat"
|
||||
|
||||
sh_text = """#!/usr/bin/env bash
|
||||
set -e
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO="$SCRIPT_DIR"
|
||||
VENV="$REPO/.venv"
|
||||
# Make tools installed into the local venv available in PATH for provider discovery
|
||||
export PATH="$VENV/bin:$PATH"
|
||||
PY="$VENV/bin/python"
|
||||
if [ -x "$PY" ]; then
|
||||
exec "$PY" -m medeia_macina.cli_entry "$@"
|
||||
else
|
||||
exec python -m medeia_macina.cli_entry "$@"
|
||||
fi
|
||||
"""
|
||||
try:
|
||||
sh.write_text(sh_text, encoding="utf-8")
|
||||
sh.chmod(sh.stat().st_mode | 0o111)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
ps1_text = r"""Param([Parameter(ValueFromRemainingArguments=$true)] $args)
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$repo = $scriptDir
|
||||
$venv = Join-Path $repo '.venv'
|
||||
# Ensure venv Scripts dir is on PATH for provider discovery
|
||||
$venvScripts = Join-Path $venv 'Scripts'
|
||||
if (Test-Path $venvScripts) { $env:PATH = $venvScripts + ';' + $env:PATH }
|
||||
$py = Join-Path $venv 'Scripts\python.exe'
|
||||
$cli = Join-Path $repo 'CLI.py'
|
||||
if (Test-Path $py) { & $py -m medeia_macina.cli_entry @args; exit $LASTEXITCODE }
|
||||
if (Test-Path $cli) { & python $cli @args; exit $LASTEXITCODE }
|
||||
# fallback
|
||||
python -m medeia_macina.cli_entry @args
|
||||
"""
|
||||
try:
|
||||
ps1.write_text(ps1_text, encoding="utf-8")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
bat_text = (
|
||||
"@echo off\r\n"
|
||||
"set SCRIPT_DIR=%~dp0\r\n"
|
||||
"set PATH=%SCRIPT_DIR%\\.venv\\Scripts;%PATH%\r\n"
|
||||
"if exist \"%SCRIPT_DIR%\\.venv\\Scripts\\python.exe\" \"%SCRIPT_DIR%\\.venv\\Scripts\\python.exe\" -m medeia_macina.cli_entry %*\r\n"
|
||||
"if exist \"%SCRIPT_DIR%\\CLI.py\" python \"%SCRIPT_DIR%\\CLI.py\" %*\r\n"
|
||||
"python -m medeia_macina.cli_entry %*\r\n"
|
||||
)
|
||||
try:
|
||||
# non-interactive mode, which we use so "python ./scripts/bootstrap.py" just
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
_write_launchers()
|
||||
|
||||
# Install user-global shims so `mm` can be executed from any shell session.
|
||||
def _install_user_shims(repo: Path) -> None:
|
||||
try:
|
||||
home = Path.home()
|
||||
system = platform.system().lower()
|
||||
|
||||
if system == "windows":
|
||||
user_bin = Path(os.environ.get("USERPROFILE", str(home))) / "bin"
|
||||
user_bin.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Write mm.cmd (CMD shim)
|
||||
mm_cmd = user_bin / "mm.cmd"
|
||||
cmd_text = (
|
||||
f"@echo off\r\n"
|
||||
f"set REPO={repo}\r\n"
|
||||
f"if exist \"%REPO%\\.venv\\Scripts\\mm.exe\" \"%REPO%\\.venv\\Scripts\\mm.exe\" %*\r\n"
|
||||
f"if defined MM_DEBUG (\r\n"
|
||||
f" echo MM_DEBUG: REPO=%REPO%\r\n"
|
||||
f" if exist \"%REPO%\\.venv\\Scripts\\python.exe\" \"%REPO%\\.venv\\Scripts\\python.exe\" -c \"import sys,importlib,importlib.util; print('sys.executable:', sys.executable); print('sys.path (first 8):', sys.path[:8]);\" \r\n"
|
||||
f")\r\n"
|
||||
f"if exist \"%REPO%\\.venv\\Scripts\\python.exe\" \"%REPO%\\.venv\\Scripts\\python.exe\" -m medeia_macina.cli_entry %*\r\n"
|
||||
f"python -m medeia_macina.cli_entry %*\r\n"
|
||||
)
|
||||
if mm_cmd.exists():
|
||||
bak = mm_cmd.with_suffix(f".bak{int(time.time())}")
|
||||
mm_cmd.replace(bak)
|
||||
mm_cmd.write_text(cmd_text, encoding="utf-8")
|
||||
|
||||
# Write mm.ps1 (PowerShell shim)
|
||||
mm_ps1 = user_bin / "mm.ps1"
|
||||
ps1_text = (
|
||||
"Param([Parameter(ValueFromRemainingArguments=$true)] $args)\n"
|
||||
f"$repo = \"{repo}\"\n"
|
||||
"$venv = Join-Path $repo '.venv'\n"
|
||||
"$exe = Join-Path $venv 'Scripts\\mm.exe'\n"
|
||||
"if (Test-Path $exe) { & $exe @args; exit $LASTEXITCODE }\n"
|
||||
"$py = Join-Path $venv 'Scripts\\python.exe'\n"
|
||||
"if (Test-Path $py) {\n"
|
||||
" if ($env:MM_DEBUG) {\n"
|
||||
" Write-Host \"MM_DEBUG: diagnostics\" -ForegroundColor Yellow\n"
|
||||
" & $py -c \"import sys,importlib,importlib.util,traceback; print('sys.executable:', sys.executable); print('sys.path (first 8):', sys.path[:8]);\"\n"
|
||||
" }\n"
|
||||
" & $py -m medeia_macina.cli_entry @args; exit $LASTEXITCODE\n"
|
||||
"}\n"
|
||||
"python -m medeia_macina.cli_entry @args\n"
|
||||
)
|
||||
if mm_ps1.exists():
|
||||
bak = mm_ps1.with_suffix(f".bak{int(time.time())}")
|
||||
mm_ps1.replace(bak)
|
||||
mm_ps1.write_text(ps1_text, encoding="utf-8")
|
||||
|
||||
# Attempt to add user_bin to the user's PATH if it's not present.
|
||||
try:
|
||||
cur = os.environ.get("PATH", "")
|
||||
str_bin = str(user_bin)
|
||||
if str_bin not in cur:
|
||||
ps_cmd = (
|
||||
"$bin = '{bin}';"
|
||||
"$cur = [Environment]::GetEnvironmentVariable('PATH','User');"
|
||||
"if ($cur -notlike \"*$bin*\") {[Environment]::SetEnvironmentVariable('PATH', ($bin + ';' + ($cur -ne $null ? $cur : '')), 'User')}"
|
||||
).format(bin=str_bin.replace('\\',
|
||||
'\\\\'))
|
||||
subprocess.run(
|
||||
["powershell",
|
||||
"-NoProfile",
|
||||
"-Command",
|
||||
ps_cmd],
|
||||
check=False
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
print(f"Installed global launchers to: {user_bin}")
|
||||
|
||||
else:
|
||||
# POSIX
|
||||
user_bin = Path(
|
||||
os.environ.get("XDG_BIN_HOME",
|
||||
str(home / ".local/bin"))
|
||||
)
|
||||
user_bin.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
mm_sh = user_bin / "mm"
|
||||
sh_text = (
|
||||
"#!/usr/bin/env bash\n"
|
||||
"set -e\n"
|
||||
f"REPO=\"{repo}\"\n"
|
||||
"# Prefer git top-level when available to avoid embedding a parent path.\n"
|
||||
"if command -v git >/dev/null 2>&1; then\n"
|
||||
" gitroot=$(git -C \"$REPO\" rev-parse --show-toplevel 2>/dev/null || true)\n"
|
||||
" if [ -n \"$gitroot\" ]; then\n"
|
||||
" REPO=\"$gitroot\"\n"
|
||||
" fi\n"
|
||||
"fi\n"
|
||||
"# If git not available or didn't resolve, walk up from CWD to find a project root.\n"
|
||||
"if [ ! -f \"$REPO/CLI.py\" ] && [ ! -f \"$REPO/pyproject.toml\" ]; then\n"
|
||||
" CUR=\"$(pwd -P)\"\n"
|
||||
" while [ \"$CUR\" != \"/\" ] && [ \"$CUR\" != \"\" ]; do\n"
|
||||
" if [ -f \"$CUR/CLI.py\" ] || [ -f \"$CUR/pyproject.toml\" ]; then\n"
|
||||
" REPO=\"$CUR\"\n"
|
||||
" break\n"
|
||||
" fi\n"
|
||||
" CUR=\"$(dirname \"$CUR\")\"\n"
|
||||
" done\n"
|
||||
"fi\n"
|
||||
"VENV=\"$REPO/.venv\"\n"
|
||||
"# Debug mode: set MM_DEBUG=1 to print repository, venv, and import diagnostics\n"
|
||||
"if [ -n \"${MM_DEBUG:-}\" ]; then\n"
|
||||
" echo \"MM_DEBUG: diagnostics\" >&2\n"
|
||||
" echo \"Resolved REPO: $REPO\" >&2\n"
|
||||
" echo \"Resolved VENV: $VENV\" >&2\n"
|
||||
" echo \"VENV exists: $( [ -d \"$VENV\" ] && echo yes || echo no )\" >&2\n"
|
||||
" echo \"Candidates:\" >&2\n"
|
||||
" echo \" VENV/bin/mm: $( [ -x \"$VENV/bin/mm\" ] && echo yes || echo no )\" >&2\n"
|
||||
" echo \" VENV/bin/python3: $( [ -x \"$VENV/bin/python3\" ] && echo yes || echo no )\" >&2\n"
|
||||
" echo \" VENV/bin/python: $( [ -x \"$VENV/bin/python\" ] && echo yes || echo no )\" >&2\n"
|
||||
" echo \" system python3: $(command -v python3 || echo none)\" >&2\n"
|
||||
" echo \" system python: $(command -v python || echo none)\" >&2\n"
|
||||
" for pycmd in \"$VENV/bin/python3\" \"$VENV/bin/python\" \"$(command -v python3 2>/dev/null)\" \"$(command -v python 2>/dev/null)\"; do\n"
|
||||
" if [ -n \"$pycmd\" ] && [ -x \"$pycmd\" ]; then\n"
|
||||
" echo \"---- Testing with: $pycmd ----\" >&2\n"
|
||||
" $pycmd - <<'PY'\nimport sys, importlib, traceback, importlib.util\nprint('sys.executable:', sys.executable)\nprint('sys.path (first 8):', sys.path[:8])\nfor mod in ('CLI','medeia_macina','medeia_macina.cli_entry'):\n try:\n spec = importlib.util.find_spec(mod)\n print(mod, 'spec:', spec)\n if spec:\n m = importlib.import_module(mod)\n print(mod, 'loaded at', getattr(m, '__file__', None))\n except Exception:\n print(mod, 'import failed')\n traceback.print_exc()\nPY\n"
|
||||
" fi\n"
|
||||
" done\n"
|
||||
" echo \"MM_DEBUG: end diagnostics\" >&2\n"
|
||||
"fi\n"
|
||||
"# Packaged console script in the venv if available\n"
|
||||
"if [ -x \"$VENV/bin/mm\" ]; then\n"
|
||||
" exec \"$VENV/bin/mm\" \"$@\"\n"
|
||||
"fi\n"
|
||||
"# Prefer venv's python3, then venv's python\n"
|
||||
"if [ -x \"$VENV/bin/python3\" ]; then\n"
|
||||
" exec \"$VENV/bin/python3\" -m medeia_macina.cli_entry \"$@\"\n"
|
||||
"fi\n"
|
||||
"if [ -x \"$VENV/bin/python\" ]; then\n"
|
||||
" exec \"$VENV/bin/python\" -m medeia_macina.cli_entry \"$@\"\n"
|
||||
"fi\n"
|
||||
"# Fallback to system python3, then system python (only if it's Python 3)\n"
|
||||
"if command -v python3 >/dev/null 2>&1; then\n"
|
||||
" exec python3 -m medeia_macina.cli_entry \"$@\"\n"
|
||||
"fi\n"
|
||||
"if command -v python >/dev/null 2>&1; then\n"
|
||||
" if python -c 'import sys; sys.exit(0 if sys.version_info[0] >= 3 else 1)'; then\n"
|
||||
" exec python -m medeia_macina.cli_entry \"$@\"\n"
|
||||
" fi\n"
|
||||
"fi\n"
|
||||
"echo 'Error: no suitable Python 3 interpreter found. Please install Python 3 or use the venv.' >&2\n"
|
||||
"exit 127\n"
|
||||
)
|
||||
if mm_sh.exists():
|
||||
bak = mm_sh.with_suffix(f".bak{int(time.time())}")
|
||||
mm_sh.replace(bak)
|
||||
mm_sh.write_text(sh_text, encoding="utf-8")
|
||||
mm_sh.chmod(mm_sh.stat().st_mode | 0o111)
|
||||
|
||||
# Ensure the user's bin is on PATH for future sessions by adding to ~/.profile
|
||||
cur_path = os.environ.get("PATH", "")
|
||||
if str(user_bin) not in cur_path:
|
||||
profile = home / ".profile"
|
||||
snippet = (
|
||||
"# Added by Medeia-Macina setup: ensure user local bin is on PATH\n"
|
||||
"if [ -d \"$HOME/.local/bin\" ] && [[ \":$PATH:\" != *\":$HOME/.local/bin:\"* ]]; then\n"
|
||||
" PATH=\"$HOME/.local/bin:$PATH\"\n"
|
||||
"fi\n"
|
||||
)
|
||||
try:
|
||||
txt = profile.read_text() if profile.exists() else ""
|
||||
if snippet.strip() not in txt:
|
||||
with profile.open("a", encoding="utf-8") as fh:
|
||||
fh.write("\n" + snippet)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
print(f"Installed global launcher to: {mm_sh}")
|
||||
|
||||
except Exception as exc: # pragma: no cover - best effort
|
||||
print(f"Failed to install global shims: {exc}", file=sys.stderr)
|
||||
|
||||
_install_user_shims(repo_root)
|
||||
|
||||
print("Setup complete.")
|
||||
return 0
|
||||
|
||||
except subprocess.CalledProcessError as exc:
|
||||
print(
|
||||
f"Error: command failed with exit {exc.returncode}: {exc}",
|
||||
file=sys.stderr
|
||||
)
|
||||
return int(exc.returncode or 1)
|
||||
except Exception as exc: # pragma: no cover - defensive
|
||||
print(f"Unexpected error: {exc}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
42
setup.py
42
setup.py
@@ -1,42 +0,0 @@
|
||||
"""
|
||||
Setup configuration for Medeia-Macina.
|
||||
|
||||
Medeia-Macina is a comprehensive media and data management system with support for:
|
||||
- Video downloading from multiple sources (YouTube, etc.)
|
||||
- Local and cloud-based file storage
|
||||
- Advanced metadata and tag management
|
||||
- Full-featured TUI and CLI interfaces
|
||||
"""
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
with open("requirements.txt") as f:
|
||||
requirements = [
|
||||
line.strip() for line in f if line.strip() and not line.startswith("#")
|
||||
]
|
||||
|
||||
setup(
|
||||
name="medeia-macina",
|
||||
version="1.0.0",
|
||||
description="Comprehensive media and data management system",
|
||||
author="Anonymous",
|
||||
python_requires=">=3.9",
|
||||
packages=find_packages(exclude=["tests",
|
||||
"*.tests"]),
|
||||
install_requires=requirements,
|
||||
entry_points={
|
||||
"console_scripts": [
|
||||
"mm=medeia_macina.cli_entry:main",
|
||||
"medeia=medeia_macina.cli_entry:main",
|
||||
],
|
||||
},
|
||||
classifiers=[
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
],
|
||||
)
|
||||
Reference in New Issue
Block a user