#! /usr/bin/env python3
# Copyright 2026 SigmaSRC, Inc. All Rights Reserved.

"""Fuel compiler front-end.

Usage: %prog [options] file|directory ...

Each file X.fuel is translated to X.fuelc and X_fuel.py.  All files
named explicitly on the command line are translated, as are any Fuel
modules imported by those (directly or indirectly) whose .fuelc file
is out of date.

If directories are given on the command line, all .fuel files found in
those, including subdirectories, are also compiled.

The -f, -u and -c options affect this behavior: -f forces
recompilation of all dependencies; -u suppresses compilation even of
explicitly given modules if their .fuelc is up to date; -c does not
compile anything, but instead removes all compiler output, as well as
.fuel~ and .py~ files (but does not follow dependencies).

If -f, -u or -c is given without any file or directory arguments, the 
directories on the fuel module search path are processed; otherwise,
at least one file or directory argument must be given.

The X.fuelc contains the symbol table used by further compilations;
the generated Python source code is written to X_fuel.py.

"""

# Python imports
import os
import sys
import logging
from optparse import OptionParser

# Sigma imports
from sigma.util import avalog
from sigma.util.errors import usage, error
from sigma.fuelc.compile import CompilationManager
from sigma.fuelc.modreg import defaultfuelpath

# Global objects
logger = logging.getLogger("fuelc")
parser = OptionParser(usage=__doc__.strip())
parser.add_option("-f", "--force", dest="force", action="store_true",
                  help="force compilation of all dependents too")
parser.add_option("-u", "--update", dest="update", action="store_true",
                  help="ensure files and dependents are compiled")
parser.add_option("-c", "--cleanup", dest="cleanup", action="store_true",
                  help="remove compiler output "
                       "for specified files/directories")
parser.add_option("-p", "--fuelpath", dest="fuelpath",
                  help="override fuel module search path")

def main(args=None):
    opts, args = parser.parse_args(args)
    force = bool(opts.force)
    update = bool(opts.update)
    cleanup = bool(opts.cleanup)
    any = force + update + cleanup
    if any > 1:
        return usage("please use at most one of --force, --update, --cleanup")
    if not any and not args:
        return usage("please pass one or more filenames or directories")
    if opts.fuelpath:
        fuelpath = opts.fuelpath.split(os.pathsep)
    else:
        fuelpath = defaultfuelpath()
    if any and not args:
        args = fuelpath
    avalog.setupLogging()
    cm = CompilationManager(fuelpath=fuelpath, force=force, update=update)
    if cleanup:
        return do_cleanup(args)
    fnlist = []
    for arg in args:
        if os.path.isdir(arg):
            for dirpath, dirnames, filenames in os.walk(arg):
                dirnames.sort()
                filenames.sort()
                for fn in filenames:
                    if fn.endswith(".fuel") and not fn.startswith("."):
                        fn = os.path.join(dirpath, fn)
                        fn = cm.addfile(fn)
                        fnlist.append(fn)
        else:
            if not arg.endswith(".fuel"):
                return usage("non-Fuel filename %r", arg)
            fn = cm.addfile(arg)
            fnlist.append(fn)
    cm.compileall()
    badfiles = []
    for fn in fnlist:
        if cm.getstatus(fn) != "done":
            badfiles.append(fn)
    if badfiles:
        badfiles.sort()
        return error("compilation error(s) in %s", " ".join(badfiles))
    return 0

def do_cleanup(args):
    sts = 0
    for arg in args:
        if os.path.isdir(arg):
            for dirpath, dirnames, filenames in os.walk(arg):
                filenames.sort()
                dirnames.sort()
                for fn in filenames:
                    if (fn.endswith(".fuelc") or
                        fn.endswith(".fuel~") or
                        fn.endswith("_fuel.py") or
                        fn.endswith("_fuel.py~") or
                        fn.endswith("_fuel.pyc") or
                        fn.endswith("_fuel.pyo")):
                        fn = os.path.join(dirpath, fn)
                        try:
                            os.remove(fn)
                        except os.error as err:
                            logger.error("can't remove %r: %s", fn, err)
                            sts = sts or 1
                        else:
                            logger.info("removed %s" % fn)
        else:
            if not arg.endswith(".fuel"):
                usage("non-Fuel filename %r", arg)
                sts = 2
            else:
                base = arg[:-5]
                files = [arg + "c",
                         arg + "~",
                         base + "_fuel.py",
                         base + "_fuel.py~",
                         base + "_fuel.pyc",
                         base + "_fuel.pyo"]
                for fn in files:
                    if os.path.exists(fn):
                        try:
                            os.remove(fn)
                        except os.error as err:
                            logger.error("can't remove %r: %s", fn, err)
                            sts = sts or 1
                        else:
                            logger.info("removed %s" % fn)
    return sts

if __name__ == "__main__":
    sys.exit(main())
