Donkey

Script Language Converter

Paste your script, pick a language, and get clean, idiomatic converted code with setup steps and a test checklist.

What you get: A Markdown report with the complete converted script in one copy-ready code block. It also covers setup and dependency steps, a table mapping source constructs to their target equivalents, the behavior differences and caveats to know about, and a checklist for testing the new version against the ori Price: $19 flat. Time: usually ready in minutes. Done by AI; you review it, with one free change.

We're not taking orders yet. Join the waitlist and we'll email you once when we open.

Real example

This is an actual result our AI made in a test run, from sample details. Yours is made from your details.

Show the example

Converted Script: Nightly Uploads Backup with Rotation (Bash → Python 3)

1. Summary

Item Detail
Source language Bash (given)
Target language and version Python 3.10+
Target environment Ubuntu 22.04 server, run by cron at 2:00 AM
Lines in / lines out ~23 / ~150 (the extra lines come from the flags, logging, docstrings and comments)
External dependencies The /var/www/app/uploads folder (must be readable), the /backups folder (must be writable), and the cron entry. The script no longer calls any outside programs (tar, ls, xargs, rm are all replaced).
Third-party packages added None (standard library only: argparse, logging, tarfile, pathlib, datetime)

Assumptions

  • The new flags are --src-dir, --dest-dir and --keep. Their defaults are /var/www/app/uploads, /backups and 7, the same as the original.
  • --keep must be at least 1. The original's KEEP was hard-coded, so this is a new safety check: with KEEP=0 the original would delete the backup it had just made.
  • Logging: normal (INFO) messages go to stdout, as you asked. Errors and warnings still go to stderr, like the original's >&2 messages. Every line looks like 2025-01-31 02:00:01 INFO Backup complete: ....
  • Messages keep their original wording ("Source dir missing: ...", "Backup failed", "Backup complete: ...") but now start with a timestamp and a level.
  • The backup file name keeps the same local-time stamp format: uploads_YYYY-MM-DD_HHMM.tar.gz.
  • The script runs as the same user as before, which may be root, with the same permissions.

What it does: It checks that the uploads folder exists and then packs its whole contents into a gzip-compressed tar file named uploads_<date>_<time>.tar.gz in the backup folder. Then it deletes all but the 7 newest uploads_*.tar.gz files, judged by modification time. Exit codes: 0 = success, 1 = source folder missing, 2 = backup failed.

2. Converted Code

#!/usr/bin/env python3
"""Nightly backup: zip the uploads folder, keep last 7 backups.

Python 3 port of the original Bash script. Creates a gzip-compressed tar
archive of SRC_DIR in DEST_DIR, then deletes all but the newest KEEP
uploads_*.tar.gz files.

Exit codes (same as the original):
    0  backup completed
    1  source directory missing
    2  backup failed (could not create DEST_DIR or write the archive)
"""

import argparse
import logging
import sys
import tarfile
from datetime import datetime
from pathlib import Path

DEFAULT_SRC_DIR = "/var/www/app/uploads"
DEFAULT_DEST_DIR = "/backups"
DEFAULT_KEEP = 7

BACKUP_PREFIX = "uploads_"
BACKUP_SUFFIX = ".tar.gz"

EXIT_OK = 0
EXIT_SRC_MISSING = 1
EXIT_BACKUP_FAILED = 2

log = logging.getLogger("backup_uploads")


def setup_logging() -> None:
    """Timestamped logging: INFO to stdout, WARNING/ERROR to stderr.

    The original wrote its error messages to stderr (>&2) and its success
    message to stdout; this split keeps that behavior.
    """
    formatter = logging.Formatter(
        "%(asctime)s %(levelname)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S"
    )

    stdout_handler = logging.StreamHandler(sys.stdout)
    stdout_handler.setLevel(logging.DEBUG)
    stdout_handler.addFilter(lambda record: record.levelno < logging.WARNING)
    stdout_handler.setFormatter(formatter)

    stderr_handler = logging.StreamHandler(sys.stderr)
    stderr_handler.setLevel(logging.WARNING)
    stderr_handler.setFormatter(formatter)

    log.setLevel(logging.INFO)
    log.addHandler(stdout_handler)
    log.addHandler(stderr_handler)


def positive_int(value: str) -> int:
    """argparse type: an integer >= 1 (KEEP=0 would delete the new backup)."""
    try:
        number = int(value)
    except ValueError:
        raise argparse.ArgumentTypeError(f"not an integer: {value!r}")
    if number < 1:
        raise argparse.ArgumentTypeError("must be 1 or greater")
    return number


def parse_args(argv=None) -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="Archive the uploads folder and keep only the newest N backups."
    )
    parser.add_argument(
        "--src-dir",
        default=DEFAULT_SRC_DIR,
        help="directory to back up (default: %(default)s)",
    )
    parser.add_argument(
        "--dest-dir",
        default=DEFAULT_DEST_DIR,
        help="directory to write backups to (default: %(default)s)",
    )
    parser.add_argument(
        "--keep",
        type=positive_int,
        default=DEFAULT_KEEP,
        help="number of newest backups to keep (default: %(default)s)",
    )
    return parser.parse_args(argv)


def create_archive(src_dir: Path, archive_path: Path) -> None:
    """Equivalent of: tar -czf ARCHIVE -C SRC_DIR .

    - arcname="." stores entries as ./file, ./subdir/file (like `-C SRC .`).
    - compresslevel=6 matches gzip's default used by `tar -z`
      (tarfile would otherwise use 9).
    - GNU_FORMAT matches GNU tar's default archive format on Ubuntu.
    - Symlinks are stored as links, not followed (same as tar's default).
    - Like `tar -czf`, an existing file with the same name is overwritten.
    """
    with tarfile.open(
        archive_path, "w:gz", compresslevel=6, format=tarfile.GNU_FORMAT
    ) as tar:
        tar.add(str(src_dir), arcname=".")


def prune_old_backups(dest_dir: Path, keep: int) -> None:
    """Delete old backups beyond KEEP.

    Equivalent of:
        ls -1t "$DEST_DIR"/uploads_*.tar.gz | tail -n +$((KEEP+1)) | xargs -r rm --
    """
    backups = []
    for path in dest_dir.glob(f"{BACKUP_PREFIX}*{BACKUP_SUFFIX}"):
        try:
            if path.is_file():
                backups.append((path, path.stat().st_mtime_ns))
        except OSError:
            # File vanished or is unreadable between glob and stat; skip it.
            continue

    # `ls -t` sorts newest first and breaks ties by name. Sort by name first,
    # then by mtime (Python's sort is stable, also wit

Questions

How does the script language converter work?

You answer a few questions, pay $19, and an AI worker does the job. The result appears on your private page, usually within minutes.

Can I get changes?

Yes. Every job includes one free change request: tell the donkey what to fix and it redoes the job with your notes.

Is it really done by AI?

Yes, fully. No human writes it. AI can make mistakes, so please review the result before you rely on it.

What happens to my information?

It is used only to do your job, is never used to train AI models, and is deleted after 30 days.

What if it fails?

Failed jobs are retried once, then refunded automatically.

Need something different?

Can't find it? Describe anything.

Our AI checks it's something we can do safely, then shows you exactly what you'll get and a flat price. Nothing is charged until you order.

More in Sales