local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local debug = _tl_compat and _tl_compat.debug or debug; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local loadfile = _tl_compat and _tl_compat.loadfile or loadfile; local os = _tl_compat and _tl_compat.os or os; local package = _tl_compat and _tl_compat.package or package; local pairs = _tl_compat and _tl_compat.pairs or pairs; local pcall = _tl_compat and _tl_compat.pcall or pcall; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table; local _tl_table_pack = table.pack or function(...) return { n = select("#", ...), ... } end; local _tl_table_unpack = unpack or table.unpack; local type = type; local xpcall = _tl_compat and _tl_compat.xpcall or xpcall

local cmd = { Module = {} }











local manif = require("luarocks.manif")
local config = require("luarocks.config")
local util = require("luarocks.util")
local path = require("luarocks.path")
local cfg = require("luarocks.core.cfg")
local dir = require("luarocks.dir")
local fun = require("luarocks.fun")
local fs = require("luarocks.fs")
local argparse = require("luarocks.vendor.argparse")













local hc_ok, hardcoded = pcall(require, "luarocks.core.hardcoded")
if not hc_ok then
   hardcoded = {}
end

local program = util.this_program("luarocks")

cmd.errorcodes = {
   OK = 0,
   UNSPECIFIED = 1,
   PERMISSIONDENIED = 2,
   CONFIGFILE = 3,
   LOCK = 4,
   CRASH = 99,
}

local function check_popen()
   local popen_ok, popen_result = pcall(io.popen, "")
   if popen_ok then
      if popen_result then
         popen_result:close()
      end
   else
      io.stderr:write("Your version of Lua does not support io.popen,\n")
      io.stderr:write("which is required by LuaRocks. Please check your Lua installation.\n")
      os.exit(cmd.errorcodes.UNSPECIFIED)
   end
end

local process_tree_args
do
   local function replace_tree(args, root, tree)
      root = dir.normalize(root)
      args.tree = root
      path.use_tree(tree or root)
   end

   local function strip_trailing_slashes()
      local cfg_root_dir = cfg.root_dir
      if type(cfg_root_dir) == "string" then
         cfg.root_dir = (cfg.root_dir):gsub("/+$", "")
      else
         (cfg.root_dir).root = (cfg.root_dir).root:gsub("/+$", "")
      end
      cfg.rocks_dir = cfg.rocks_dir:gsub("/+$", "")
      cfg.deploy_bin_dir = cfg.deploy_bin_dir:gsub("/+$", "")
      cfg.deploy_lua_dir = cfg.deploy_lua_dir:gsub("/+$", "")
      cfg.deploy_lib_dir = cfg.deploy_lib_dir:gsub("/+$", "")
   end

   local function set_named_tree(args, name)
      for _, tree in ipairs(cfg.rocks_trees) do
         if type(tree) == "table" and name == tree.name then
            if not tree.root then
               return nil, "Configuration error: tree '" .. tree.name .. "' has no 'root' field."
            end
            replace_tree(args, tree.root, tree)
            return true
         end
      end
      return false
   end

   process_tree_args = function(args, project_dir)

      if args.global then
         local ok, err = set_named_tree(args, "system")
         if not ok then
            return nil, err
         end
      elseif args.tree then
         local named = set_named_tree(args, args.tree)
         if not named then
            local root_dir = fs.absolute_name(args.tree)
            replace_tree(args, root_dir)
            if (args.deps_mode or cfg.deps_mode) ~= "order" then
               table.insert(cfg.rocks_trees, 1, { name = "arg", root = root_dir })
            end
         end
      elseif args["local"] then
         if fs.is_superuser() then
            return nil, "The --local flag is meant for operating in a user's home directory.\n" ..
            "You are running as a superuser, which is intended for system-wide operation.\n" ..
            "To force using the superuser's home, use --tree explicitly."
         else
            local ok, err = set_named_tree(args, "user")
            if not ok then
               return nil, err
            end
         end
      elseif args.project_tree then
         local tree = args.project_tree
         table.insert(cfg.rocks_trees, 1, { name = "project", root = tree })
         manif.load_rocks_tree_manifests()
         path.use_tree(tree)
      elseif project_dir then
         local project_tree = project_dir .. "/lua_modules"
         table.insert(cfg.rocks_trees, 1, { name = "project", root = project_tree })
         manif.load_rocks_tree_manifests()
         path.use_tree(project_tree)
      elseif cfg.local_by_default then
         local ok, err = set_named_tree(args, "user")
         if not ok then
            return nil, err
         end
      else
         local trees = cfg.rocks_trees
         path.use_tree(trees[#trees])
      end

      strip_trailing_slashes()

      cfg.variables.ROCKS_TREE = cfg.rocks_dir
      cfg.variables.SCRIPTS_DIR = cfg.deploy_bin_dir

      return true
   end
end

local function process_server_args(args)
   if args.server then
      local protocol, pathname = dir.split_url(args.server)
      table.insert(cfg.rocks_servers, 1, protocol .. "://" .. pathname)
   end

   if args.dev then
      for i, server in ipairs(cfg.rocks_servers) do
         if type(server) == "string" then
            cfg.rocks_servers[i] = dir.path(server, "dev")
         else
            for j, mirror in ipairs(server) do
               server[j] = dir.path(mirror, "dev")
            end
         end
      end
   end

   if args.only_server then
      if args.dev then
         return nil, "--only-server cannot be used with --dev"
      end
      if args.server then
         return nil, "--only-server cannot be used with --server"
      end
      cfg.rocks_servers = { args.only_server }
   end

   return true
end

local function error_handler(err)
   if not debug then
      return err
   end
   local mode = "Arch.: " .. (cfg and cfg.arch or "unknown")
   if package.config:sub(1, 1) == "\\" then
      if cfg and cfg.fs_use_modules then
         mode = mode .. " (fs_use_modules = true)"
      end
   end
   if cfg and cfg.is_binary then
      mode = mode .. " (binary)"
   end
   return debug.traceback("LuaRocks " .. cfg.program_version ..
   " bug (please report at https://github.com/luarocks/luarocks/issues).\n" ..
   mode .. "\n" .. err, 2)
end




local function die(message, exitcode)
   assert(type(message) == "string", "bad error, expected string, got: " .. type(message))
   assert(exitcode == nil or type(exitcode) == "number", "bad error, expected number, got: " .. type(exitcode) .. " - " .. tostring(exitcode))
   util.printerr("\nError: " .. message)

   local ok, err = xpcall(util.run_scheduled_functions, error_handler)
   if not ok then
      util.printerr("\nError: " .. err)
      exitcode = cmd.errorcodes.CRASH
   end

   os.exit(exitcode or cmd.errorcodes.UNSPECIFIED)
end

local function search_lua(lua_version, verbose, search_at)
   if search_at then
      return util.find_lua(search_at, lua_version, verbose)
   end

   local path_sep = (package.config:sub(1, 1) == "\\" and ";" or ":")
   local all_tried = {}
   for bindir in (os.getenv("PATH") or ""):gmatch("[^" .. path_sep .. "]+") do
      local searchdir = (bindir:gsub("[\\/]+bin[\\/]?$", ""))
      local detected, tried = util.find_lua(searchdir, lua_version)
      if detected then
         return detected
      else
         table.insert(all_tried, tried)
      end
   end
   return nil, "Could not find " ..
   (lua_version and "Lua " .. lua_version or "Lua") ..
   " in PATH." ..
   (verbose and " Tried:\n" .. table.concat(all_tried, "\n") or "")
end

local init_config
do
   local detect_config_via_args
   do
      local function find_project_dir(project_tree)
         if project_tree then
            return project_tree:gsub("[/\\][^/\\]+$", ""), true
         else
            local try = "."
            for _ = 1, 10 do
               if util.exists(try .. "/.luarocks") and util.exists(try .. "/lua_modules") then
                  return dir.normalize(try), false
               elseif util.exists(try .. "/.luarocks-no-project") then
                  break
               end
               try = try .. "/.."
            end
         end
         return nil
      end

      local function find_default_lua_version(args, project_dir)
         if hardcoded.FORCE_CONFIG then
            return nil
         end

         local dirs = {}
         if project_dir then
            table.insert(dirs, dir.path(project_dir, ".luarocks"))
         end
         if cfg.homeconfdir then
            table.insert(dirs, cfg.homeconfdir)
         end
         table.insert(dirs, cfg.sysconfdir)
         for _, d in ipairs(dirs) do
            local f = dir.path(d, "default-lua-version.lua")
            local mod, _ = loadfile(f, "t")
            if mod then
               local pok, ver = pcall(mod)
               if pok and type(ver) == "string" and ver:match("%d+.%d+") then
                  if args.verbose then
                     util.printout("Defaulting to Lua " .. ver .. " based on " .. f .. " ...")
                  end
                  return ver
               end
            end
         end
         return nil
      end

      local function find_version_from_config(dirname)
         return fun.find(util.lua_versions("descending"), function(v)
            if util.exists(dir.path(dirname, ".luarocks", "config-" .. v .. ".lua")) then
               return v
            end
         end)
      end

      local function detect_lua_via_args(args, project_dir)
         local lua_version = args.lua_version or
         find_default_lua_version(args, project_dir) or
         (project_dir and find_version_from_config(project_dir))

         if args.lua_dir then
            local detected, err = util.find_lua(args.lua_dir, lua_version)
            if not detected then
               local suggestion = (not args.lua_version) and
               "\nYou may want to specify a different Lua version with --lua-version\n" or
               ""
               die(err .. suggestion)
            end
            return detected
         end

         if lua_version then
            local detected = search_lua(lua_version)
            if detected then
               return detected
            end
            return {
               lua_version = lua_version,
            }
         end

         return {}
      end

      detect_config_via_args = function(args)
         local project_dir, given
         if not args.no_project then
            project_dir, given = find_project_dir(args.project_tree)
         end

         local detected = detect_lua_via_args(args, project_dir)
         if args.lua_version then
            detected.given_lua_version = args.lua_version
         end
         if args.lua_dir then
            detected.given_lua_dir = args.lua_dir
         end
         if given then
            detected.given_project_dir = project_dir
         end
         detected.project_dir = project_dir
         return detected
      end
   end

   init_config = function(args)
      local detected = detect_config_via_args(args)

      local ok, err = cfg.init(detected, util.warning)
      if not ok then
         return nil, err
      end

      return (detected.lua_dir ~= nil)
   end
end

local variables_help = [[
Variables:
   Variables from the "variables" table of the configuration file can be
   overridden with VAR=VALUE assignments.

]]

local lua_example = package.config:sub(1, 1) == "\\" and
"<d:\\path\\lua.exe>" or
"</path/lua>"

local function show_status(file, status, err)
   return (file and file .. " " or "") .. (status and "(ok)" or ("(" .. (err or "not found") .. ")"))
end

local function use_to_fix_location(key, what)
   local buf = "                   ****************************************\n"
   buf = buf .. "                   Use the command\n\n"
   buf = buf .. "                      luarocks config " .. key .. " " .. (what or "<dir>") .. "\n\n"
   buf = buf .. "                   to fix the location\n"
   buf = buf .. "                   ****************************************\n"
   return buf
end

local function get_config_text(conf)
   local deps = require("luarocks.deps")

   local libdir_ok = deps.check_lua_libdir(conf.variables)
   local incdir_ok = deps.check_lua_incdir(conf.variables)
   local lua_ok = conf.variables.LUA and fs.exists(conf.variables.LUA)

   local buf = "Configuration:\n"
   buf = buf .. "   Lua:\n"
   buf = buf .. "      Version    : " .. conf.lua_version .. "\n"
   if conf.luajit_version then
      buf = buf .. "      LuaJIT     : " .. conf.luajit_version .. "\n"
   end
   buf = buf .. "      LUA        : " .. show_status(conf.variables.LUA, lua_ok, "interpreter not found") .. "\n"
   if not lua_ok then
      buf = buf .. use_to_fix_location("variables.LUA", lua_example)
   end
   buf = buf .. "      LUA_INCDIR : " .. show_status(conf.variables.LUA_INCDIR, incdir_ok, "lua.h not found") .. "\n"
   if lua_ok and not incdir_ok then
      buf = buf .. use_to_fix_location("variables.LUA_INCDIR")
   end
   buf = buf .. "      LUA_LIBDIR : " .. show_status(conf.variables.LUA_LIBDIR, libdir_ok, "Lua library itself not found") .. "\n"
   if lua_ok and not libdir_ok then
      buf = buf .. use_to_fix_location("variables.LUA_LIBDIR")
   end

   buf = buf .. "\n   Configuration files:\n"
   local files = conf.config_files
   buf = buf .. "      System  : " .. show_status(fs.absolute_name(files.system.file), files.system.found) .. "\n"
   if files.user.file then
      buf = buf .. "      User    : " .. show_status(fs.absolute_name(files.user.file), files.user.found) .. "\n"
   else
      buf = buf .. "      User    : disabled in this LuaRocks installation.\n"
   end
   if files.project then
      buf = buf .. "      Project : " .. show_status(fs.absolute_name(files.project.file), files.project.found) .. "\n"
   end
   buf = buf .. "\n   Rocks trees in use: \n"
   for _, tree in ipairs(conf.rocks_trees) do
      if type(tree) == "string" then
         buf = buf .. "      " .. fs.absolute_name(tree)
      else
         local name = tree.name and " (\"" .. tree.name .. "\")" or ""
         buf = buf .. "      " .. fs.absolute_name(tree.root) .. name
      end
      buf = buf .. "\n"
   end

   return buf
end

local function get_parser(description, cmd_modules)
   local basename = dir.base_name(program)
   local parser = argparse(
   basename, "LuaRocks " .. cfg.program_version .. ", the Lua package manager\n\n" ..
   program .. " - " .. description, variables_help .. "Run '" .. basename ..
   "' without any arguments to see the configuration."):
   help_max_width(80):
   add_help_command():
   add_complete_command({
      help_max_width = 100,
      summary = "Output a shell completion script.",
      description = [[
Output a shell completion script.

Enabling completions for Bash:

   Add the following line to your ~/.bashrc:
      source <(]] .. basename .. [[ completion bash)
   or save the completion script to the local completion directory:
      ]] .. basename .. [[ completion bash > ~/.local/share/bash-completion/completions/]] .. basename .. [[


Enabling completions for Zsh:

   Save the completion script to a file in your $fpath.
   You can add a new directory to your $fpath by adding e.g.
      fpath=(~/.zfunc $fpath)
   to your ~/.zshrc.
   Then run:
      ]] .. basename .. [[ completion zsh > ~/.zfunc/_]] .. basename .. [[


Enabling completion for Fish:

   Add the following line to your ~/.config/fish/config.fish:
      ]] .. basename .. [[ completion fish | source
   or save the completion script to the local completion directory:
      ]] .. basename .. [[ completion fish > ~/.config/fish/completions/]] .. basename .. [[.fish
]], }):
   command_target("command"):
   require_command(false)

   parser:flag("--version", "Show version info and exit."):
   action(function()
      util.printout(program .. " " .. cfg.program_version)
      util.printout(description)
      util.printout()
      os.exit(cmd.errorcodes.OK)
   end)
   parser:flag("--dev", "Enable the sub-repositories in rocks servers for " ..
   "rockspecs of in-development versions.")
   parser:option("--server", "Fetch rocks/rockspecs from this server " ..
   "(takes priority over config file)."):
   hidden_name("--from")
   parser:option("--only-server", "Fetch rocks/rockspecs from this server only " ..
   "(overrides any entries in the config file)."):
   argname("<server>"):
   hidden_name("--only-from")
   parser:option("--only-sources", "Restrict downloads to paths matching the given URL."):
   argname("<url>"):
   hidden_name("--only-sources-from")
   parser:option("--namespace", "Specify the rocks server namespace to use."):
   convert(string.lower)
   parser:option("--lua-dir", "Which Lua installation to use."):
   argname("<prefix>")
   parser:option("--lua-version", "Which Lua version to use."):
   argname("<ver>"):
   convert(function(s) return (s:match("^%d+%.%d+$")) end)
   parser:option("--tree", "Which tree to operate on."):
   hidden_name("--to")
   parser:flag("--local", "Use the tree in the user's home directory.\n" ..
   "To enable it, see '" .. program .. " help path'.")
   parser:flag("--global", "Use the system tree when `local_by_default` is `true`.")
   parser:flag("--no-project", "Do not use project tree even if running from a project folder.")
   parser:flag("--force-lock", "Attempt to overwrite the lock for commands " ..
   "that require exclusive access, such as 'install'")
   parser:flag("--verbose", "Display verbose output of commands executed.")
   parser:option("--timeout", "Timeout on network operations, in seconds.\n" ..
   "0 means no timeout (wait forever). Default is " ..
   tostring(cfg.connection_timeout) .. "."):
   argname("<seconds>"):
   convert(tonumber)


   parser:option("--project-tree"):hidden(true)

   for _, module in util.sortedpairs(cmd_modules) do
      module.add_to_parser(parser)
   end

   return parser
end

local function get_first_arg()
   if not arg then
      return
   end
   local first_arg = arg[0]
   local i = -1
   while arg[i] do
      first_arg = arg[i]
      i = i - 1
   end
   return first_arg
end









function cmd.run_command(description, commands, external_namespace, ...)

   check_popen()


   cfg.init()

   fs.init()

   for _, module_name in ipairs(fs.modules(external_namespace)) do
      if not commands[module_name] then
         commands[module_name] = external_namespace .. "." .. module_name
      end
   end

   local cmd_modules = {}
   for name, module in pairs(commands) do
      local pok, mod = pcall(require, module)
      if pok and type(mod) == "table" then
         local original_command = mod.command
         if original_command then
            if not mod.add_to_parser then
               mod.add_to_parser = function(parser)
                  parser:command(name, mod.help, util.see_also()):
                  summary(mod.help_summary):
                  handle_options(false):
                  argument("input"):
                  args("*")
               end

               mod.command = function(args)
                  return original_command(args, _tl_table_unpack(args.input))
               end
            end
            cmd_modules[name] = mod
         else
            util.warning("command module " .. module .. " does not implement command(), skipping")
         end
      else
         util.warning("failed to load command module " .. module .. ": " .. tostring(mod))
      end
   end

   local function process_cmdline_vars(...)
      local args = _tl_table_pack(...)
      local cmdline_vars = {}
      local last = args.n
      for i = 1, args.n do
         if args[i] == "--" then
            last = i - 1
            break
         end
      end
      for i = last, 1, -1 do
         local arg_i = args[i]
         if arg_i:match("^[^-][^=]*=") then
            local var, val = arg_i:match("^([A-Z_][A-Z0-9_]*)=(.*)")
            if val then
               cmdline_vars[var] = val
               table.remove(args, i)
            else
               die("Invalid assignment: " .. arg_i)
            end
         end
      end

      return args, cmdline_vars
   end

   local cmdline_args, cmdline_vars = process_cmdline_vars(...)
   local parser = get_parser(description, cmd_modules)
   local args = parser:parse(cmdline_args)


   if args.nodeps then
      args.deps_mode = "none"
   end

   if args.timeout then
      cfg.connection_timeout = args.timeout
   end

   if args.command == "config" then
      if args.key == "lua_version" and args.value then
         args.lua_version = args.value
      elseif args.key == "lua_dir" and args.value then
         args.lua_dir = args.value
      end
   end


   local lua_found, err = init_config(args)
   if err then
      die(err)
   end




   fs.init()



   local tried
   if not lua_found then
      local detected
      detected, tried = search_lua(cfg.lua_version, args.verbose, cfg.variables.LUA_DIR)
      if detected then
         lua_found = true
         cfg.variables.LUA = detected.lua
         cfg.variables.LUA_DIR = detected.lua_dir
         cfg.variables.LUA_BINDIR = detected.lua_bindir
         if args.lua_dir then
            cfg.variables.LUA_INCDIR = nil
            cfg.variables.LUA_LIBDIR = nil
         end
      else
         cfg.variables.LUA = nil
         cfg.variables.LUA_DIR = nil
         cfg.variables.LUA_BINDIR = nil
         cfg.variables.LUA_INCDIR = nil
         cfg.variables.LUA_LIBDIR = nil
      end
   end

   if lua_found then
      assert(cfg.variables.LUA)
   else




      if not cfg.variables.LUA then
         local first_arg = get_first_arg()
         local bin_dir = dir.dir_name(fs.absolute_name(first_arg))
         local exe = dir.base_name(first_arg)
         exe = exe:match("rocks") and ("lua" .. (cfg.arch:match("win") and ".exe" or "")) or exe
         local full_path = dir.path(bin_dir, exe)
         if util.check_lua_version(full_path, cfg.lua_version) then
            cfg.variables.LUA = dir.path(bin_dir, exe)
            cfg.variables.LUA_DIR = bin_dir:gsub("[/\\]bin[/\\]?$", "")
            cfg.variables.LUA_BINDIR = bin_dir
            cfg.variables.LUA_INCDIR = nil
            cfg.variables.LUA_LIBDIR = nil
         end
      end
   end

   cfg.lua_found = lua_found

   if cfg.project_dir then
      cfg.project_dir = fs.absolute_name(cfg.project_dir)
   end

   if args.verbose then
      cfg.verbose = true
      print(("-"):rep(79))
      print("Current configuration:")
      print(("-"):rep(79))
      print(config.to_string(cfg))
      print(("-"):rep(79))
      fs.verbose()
   end

   if (not fs.current_dir()) or fs.current_dir() == "" then
      die("Current directory does not exist. Please run LuaRocks from an existing directory.")
   end

   local ok
   ok, err = process_tree_args(args, cfg.project_dir)
   if not ok then
      die(err)
   end

   ok, err = process_server_args(args)
   if not ok then
      die(err)
   end

   if args.only_sources then
      cfg.only_sources_from = args.only_sources
   end

   for k, v in pairs(cmdline_vars) do
      cfg.variables[k] = v
   end


   if fs.is_superuser() then
      cfg.local_cache = dir.path(fs.system_cache_dir(), "luarocks")
   end

   if args.no_manifest then
      cfg.no_manifest = true
   end

   if not args.command then
      parser:epilog(variables_help .. get_config_text(cfg))
      util.printout()
      util.printout(parser:get_help())
      util.printout()
      os.exit(cmd.errorcodes.OK)
   end

   if not cfg.variables["LUA"] and args.command ~= "config" and args.command ~= "help" then
      local flag = (not cfg.project_tree) and
      "--local " or
      ""
      if args.lua_version then
         flag = "--lua-version=" .. args.lua_version .. " " .. flag
      end
      die((tried or "Lua interpreter not found.") ..
      "\nPlease set your Lua interpreter with:\n\n" ..
      "   luarocks " .. flag .. "config variables.LUA " .. lua_example .. "\n")
   end

   local cmd_mod = cmd_modules[args.command]

   local lock
   if cmd_mod.needs_lock and cmd_mod.needs_lock(args) then
      ok, err = fs.check_command_permissions(args)
      if not ok then
         die(err, cmd.errorcodes.PERMISSIONDENIED)
      end

      lock, err = fs.lock_access(path.root_dir(cfg.root_dir), args.force_lock)
      if not lock then
         err = args.force_lock and
         ("failed to force the lock" .. (err and ": " .. err or "")) or
         (err and err ~= "File exists") and
         err or
         "try --force-lock to overwrite the lock"

         die("command '" .. args.command .. "' " ..
         "requires exclusive write access to " .. path.root_dir(cfg.root_dir) .. " - " ..
         err, cmd.errorcodes.LOCK)
      end
   end

   local call_ok
   local exitcode
   call_ok, ok, err, exitcode = xpcall(function()
      return cmd_mod.command(args)
   end, error_handler)

   if lock then
      fs.unlock_access(lock)
   end

   if not call_ok then
      die(tostring(ok), cmd.errorcodes.CRASH)
   elseif not ok then
      die(err, exitcode)
   end
   util.run_scheduled_functions()
end

return cmd
