From 4941a48407f08544c5517542ffc11b0c26fd4236 Mon Sep 17 00:00:00 2001
From: Hisham Muhammad <hisham@gobolinux.org>
Date: Thu, 29 Mar 2018 19:11:26 -0300
Subject: Beginning support for namespaced queries

---
 src/luarocks/cmd/install.lua  |  3 +-
 src/luarocks/cmd/list.lua     |  7 ++--
 src/luarocks/cmd/purge.lua    |  5 +--
 src/luarocks/cmd/remove.lua   |  3 +-
 src/luarocks/cmd/search.lua   |  4 +-
 src/luarocks/deps.lua         |  6 +--
 src/luarocks/download.lua     |  6 +--
 src/luarocks/manif/writer.lua |  5 +--
 src/luarocks/queries.lua      | 96 +++++++++++++++++++++++++++++++++++++++++++
 src/luarocks/remove.lua       |  7 ++--
 src/luarocks/search.lua       | 66 ++++++-----------------------
 11 files changed, 132 insertions(+), 76 deletions(-)
 create mode 100644 src/luarocks/queries.lua

diff --git a/src/luarocks/cmd/install.lua b/src/luarocks/cmd/install.lua
index 018e9246..6901a680 100644
--- a/src/luarocks/cmd/install.lua
+++ b/src/luarocks/cmd/install.lua
@@ -11,6 +11,7 @@ local deps = require("luarocks.deps")
 local writer = require("luarocks.manif.writer")
 local remove = require("luarocks.remove")
 local search = require("luarocks.search")
+local queries = require("luarocks.queries")
 local cfg = require("luarocks.core.cfg")
 
 install.help_summary = "Install a rock."
@@ -174,7 +175,7 @@ function install.command(flags, name, version)
       writer.check_dependencies(nil, deps.get_deps_mode(flags))
       return name, version
    else
-      local url, err = search.find_suitable_rock(search.make_query(name:lower(), version))
+      local url, err = search.find_suitable_rock(queries.new(name:lower(), version))
       if not url then
          return nil, err
       end
diff --git a/src/luarocks/cmd/list.lua b/src/luarocks/cmd/list.lua
index ed94bc68..e07c4ff3 100644
--- a/src/luarocks/cmd/list.lua
+++ b/src/luarocks/cmd/list.lua
@@ -4,6 +4,7 @@
 local list = {}
 
 local search = require("luarocks.search")
+local queries = require("luarocks.queries")
 local vers = require("luarocks.vers")
 local cfg = require("luarocks.core.cfg")
 local util = require("luarocks.util")
@@ -31,8 +32,7 @@ local function check_outdated(trees, query)
       table.sort(versions, vers.compare_versions)
       local latest_installed = versions[1]
 
-      local query_available = search.make_query(name:lower())
-      query.exact_name = true
+      local query_available = queries.new(name:lower(), nil, false)
       local results_available, err = search.search_repos(query_available)
       
       if results_available[name] then
@@ -69,8 +69,7 @@ end
 -- @param version string or nil: a version may also be passed.
 -- @return boolean: True if succeeded, nil on errors.
 function list.command(flags, filter, version)
-   local query = search.make_query(filter and filter:lower() or "", version)
-   query.exact_name = false
+   local query = queries.new(filter and filter:lower() or "", version, true)
    local trees = cfg.rocks_trees
    if flags["tree"] then
       trees = { flags["tree"] }
diff --git a/src/luarocks/cmd/purge.lua b/src/luarocks/cmd/purge.lua
index bbce9f8c..15efb0ef 100644
--- a/src/luarocks/cmd/purge.lua
+++ b/src/luarocks/cmd/purge.lua
@@ -12,6 +12,7 @@ local repos = require("luarocks.repos")
 local writer = require("luarocks.manif.writer")
 local cfg = require("luarocks.core.cfg")
 local remove = require("luarocks.remove")
+local queries = require("luarocks.queries")
 
 purge.help_summary = "Remove all installed rocks from a tree."
 purge.help_arguments = "--tree=<tree> [--old-versions]"
@@ -37,8 +38,6 @@ function purge.command(flags)
    end
    
    local results = {}
-   local query = search.make_query("")
-   query.exact_name = false
    if not fs.is_dir(tree) then
       return nil, "Directory not found: "..tree
    end
@@ -46,7 +45,7 @@ function purge.command(flags)
    local ok, err = fs.check_command_permissions(flags)
    if not ok then return nil, err, cfg.errorcodes.PERMISSIONDENIED end
 
-   search.manifest_search(results, path.rocks_dir(tree), query)
+   search.manifest_search(results, path.rocks_dir(tree), queries.all())
 
    local sort = function(a,b) return vers.compare_versions(b,a) end
    if flags["old-versions"] then
diff --git a/src/luarocks/cmd/remove.lua b/src/luarocks/cmd/remove.lua
index b8148ba7..2624854d 100644
--- a/src/luarocks/cmd/remove.lua
+++ b/src/luarocks/cmd/remove.lua
@@ -11,6 +11,7 @@ local search = require("luarocks.search")
 local path = require("luarocks.path")
 local deps = require("luarocks.deps")
 local writer = require("luarocks.manif.writer")
+local queries = require("luarocks.queries")
 
 cmd_remove.help_summary = "Uninstall a rock."
 cmd_remove.help_arguments = "[--force|--force-fast] <name> [<version>]"
@@ -50,7 +51,7 @@ function cmd_remove.command(flags, name, version)
 
    local results = {}
    name = name:lower()
-   search.manifest_search(results, cfg.rocks_dir, search.make_query(name, version))
+   search.manifest_search(results, cfg.rocks_dir, queries.new(name, version))
    if not results[name] then
       return nil, "Could not find rock '"..name..(version and " "..version or "").."' in "..path.rocks_tree_to_string(cfg.root_dir)
    end
diff --git a/src/luarocks/cmd/search.lua b/src/luarocks/cmd/search.lua
index d12f5314..ccdbda7b 100644
--- a/src/luarocks/cmd/search.lua
+++ b/src/luarocks/cmd/search.lua
@@ -6,6 +6,7 @@ local cmd_search = {}
 local cfg = require("luarocks.core.cfg")
 local util = require("luarocks.util")
 local search = require("luarocks.search")
+local queries = require("luarocks.queries")
 
 cmd_search.help_summary = "Query the LuaRocks servers."
 cmd_search.help_arguments = "[--source] [--binary] { <name> [<version>] | --all }"
@@ -55,8 +56,7 @@ function cmd_search.command(flags, name, version)
       return nil, "Enter name and version or use --all. "..util.see_help("search")
    end
    
-   local query = search.make_query(name:lower(), version)
-   query.exact_name = false
+   local query = queries.new(name:lower(), version, true)
    local results, err = search.search_repos(query)
    local porcelain = flags["porcelain"]
    local full_name = name .. (version and " " .. version or "")
diff --git a/src/luarocks/deps.lua b/src/luarocks/deps.lua
index 015e2527..229c3843 100644
--- a/src/luarocks/deps.lua
+++ b/src/luarocks/deps.lua
@@ -8,6 +8,7 @@ local path = require("luarocks.path")
 local dir = require("luarocks.dir")
 local util = require("luarocks.util")
 local vers = require("luarocks.vers")
+local queries = require("luarocks.queries")
 
 --- Attempt to match a dependency to an installed rock.
 -- @param dep table: A dependency parsed in table format.
@@ -90,8 +91,7 @@ local function values_set(tbl)
 end
 
 local function rock_status(name, deps_mode, rocks_provided)
-   local search = require("luarocks.search")
-   local installed = match_dep(search.make_query(name), nil, deps_mode, rocks_provided)
+   local installed = match_dep(queries.new(name), nil, deps_mode, rocks_provided)
    local installation_type = rocks_provided[name] and "provided by VM" or "installed"
    return installed and installed.." "..installation_type or "not installed"
 end
@@ -184,7 +184,7 @@ function deps.fulfill_dependencies(rockspec, deps_mode)
             return nil, "Failed matching dependencies"
          end
 
-         local url, search_err = search.find_suitable_rock(dep)
+         local url, search_err = search.find_suitable_rock(queries.from_constraints(dep.name, dep.constraints))
          if not url then
             return nil, "Could not satisfy dependency "..vers.show_dep(dep)..": "..search_err
          end
diff --git a/src/luarocks/download.lua b/src/luarocks/download.lua
index ec9996b2..77564b52 100644
--- a/src/luarocks/download.lua
+++ b/src/luarocks/download.lua
@@ -3,6 +3,7 @@ local download = {}
 local path = require("luarocks.path")
 local fetch = require("luarocks.fetch")
 local search = require("luarocks.search")
+local queries = require("luarocks.queries")
 local fs = require("luarocks.fs")
 local dir = require("luarocks.dir")
 local cfg = require("luarocks.core.cfg")
@@ -22,12 +23,11 @@ local function get_file(filename)
 end
 
 function download.download(arch, name, version, all)
-   local query = search.make_query(name, version)
-   if arch then query.arch = arch end
+   local substring = (all and name == "")
+   local query = queries.new(name, version, substring, arch)
    local search_err
 
    if all then
-      if name == "" then query.exact_name = false end
       local results = search.search_repos(query)
       local has_result = false
       local all_ok = true
diff --git a/src/luarocks/manif/writer.lua b/src/luarocks/manif/writer.lua
index 2d17d328..e6edd27d 100644
--- a/src/luarocks/manif/writer.lua
+++ b/src/luarocks/manif/writer.lua
@@ -13,6 +13,7 @@ local fetch = require("luarocks.fetch")
 local path = require("luarocks.path")
 local persist = require("luarocks.persist")
 local manif = require("luarocks.manif")
+local queries = require("luarocks.queries")
 
 --- Update storage table to account for items provided by a package.
 -- @param storage table: a table storing items in the following format:
@@ -296,9 +297,7 @@ function writer.make_manifest(repo, deps_mode, remote)
       return nil, "Cannot access repository at "..repo
    end
 
-   local query = search.make_query("")
-   query.exact_name = false
-   query.arch = "any"
+   local query = queries.all("any")
    local results = search.disk_search(repo, query)
    local manifest = { repository = {}, modules = {}, commands = {} }
 
diff --git a/src/luarocks/queries.lua b/src/luarocks/queries.lua
new file mode 100644
index 00000000..5bd7462e
--- /dev/null
+++ b/src/luarocks/queries.lua
@@ -0,0 +1,96 @@
+
+local queries = {}
+
+local vers = require("luarocks.vers")
+local cfg = require("luarocks.core.cfg")
+
+local safer = require("safer")
+
+--- Convert the arch field of a query table to table format.
+-- @param input string, table or nil
+local function arch_to_table(input)
+   if type(input) == "table" then
+      return input
+   elseif type(input) == "string" then
+      local arch = {}
+      for a in input:gmatch("[%w_-]+") do
+         arch[a] = true
+      end
+      return arch
+   else
+      local arch = {}
+      arch["src"] = true
+      arch["all"] = true
+      arch["rockspec"] = true
+      arch["installed"] = true
+      arch[cfg.arch] = true
+      return arch
+   end
+end
+
+-- Split name and namespace of a package name.
+-- @param name a name that may be in "namespace/name" format
+-- @return string, string? - name and optionally a namespace
+local function split_namespace(name)
+   local p1, p2 = name:match("^([^/]+)/([^/]+)$") 
+   if p1 then
+      return p2, p1
+   end
+   return name
+end
+
+--- Prepare a query in dependency table format.
+-- @param name string: The query name.
+-- @param version string or nil: 
+-- @param substring boolean: match substrings of the name
+-- (default is false, match full name)
+-- @param arch string: a string with pipe-separated accepted arch values
+-- @param operator string: operator for version matching (default is "==")
+-- @return table: A query in table format
+function queries.new(name, version, substring, arch, operator)
+   assert(type(name) == "string")
+   assert(type(version) == "string" or not version)
+   assert(type(substring) == "boolean" or not substring)
+   assert(type(arch) == "string" or not arch)
+   assert(type(operator) == "string" or not operator)
+   
+   operator = operator or "=="
+
+   local namespace
+   name, namespace = split_namespace(name)
+   
+   local query = {
+      name = name,
+      namespace = namespace,
+      constraints = {},
+      substring = substring,
+      arch = arch_to_table(arch),
+   }
+   if version then
+      table.insert(query.constraints, { op = operator, version = vers.parse_version(version)})
+   end
+   return safer.readonly(query)
+end
+
+-- Query for all packages
+-- @param arch string (optional)
+function queries.all(arch)
+   assert(type(arch) == "string" or not arch)
+
+   return queries.new("", nil, true, arch)
+end
+
+function queries.from_constraints(name, constraints)
+   local namespace
+   name, namespace = split_namespace(name)
+   local query = {
+      name = name,
+      namespace = namespace,
+      constraints = constraints,
+      substring = false,
+      arch = arch_to_table(nil),
+   }
+   return safer.readonly(query)
+end
+
+return queries
diff --git a/src/luarocks/remove.lua b/src/luarocks/remove.lua
index 6cc8334f..a469d149 100644
--- a/src/luarocks/remove.lua
+++ b/src/luarocks/remove.lua
@@ -7,6 +7,7 @@ local repos = require("luarocks.repos")
 local path = require("luarocks.path")
 local util = require("luarocks.util")
 local cfg = require("luarocks.core.cfg")
+local queries = require("luarocks.queries")
 
 --- Obtain a list of packages that depend on the given set of packages
 -- (where all packages of the set are versions of one program).
@@ -22,8 +23,7 @@ local function check_dependents(name, versions, deps_mode)
       blacklist[name][version] = true
    end
    local local_rocks = {}
-   local query_all = search.make_query("")
-   query_all.exact_name = false
+   local query_all = queries.all()
    search.manifest_search(local_rocks, cfg.rocks_dir, query_all)
    local_rocks[name] = nil
    for rock_name, rock_versions in pairs(local_rocks) do
@@ -105,7 +105,8 @@ end
 
 function remove.remove_other_versions(name, version, force, fast)
    local results = {}
-   search.manifest_search(results, cfg.rocks_dir, { name = name, exact_name = true, constraints = {{ op = "~=", version = version}} })
+   local query = queries.new(name, version, false, nil, "~=")
+   search.manifest_search(results, cfg.rocks_dir, query)
    if results[name] then
       return remove.remove_search_results(results, name, cfg.deps_mode, force, fast)
    end
diff --git a/src/luarocks/search.lua b/src/luarocks/search.lua
index 35010c2b..4e0cb847 100644
--- a/src/luarocks/search.lua
+++ b/src/luarocks/search.lua
@@ -6,29 +6,7 @@ local manif = require("luarocks.manif")
 local vers = require("luarocks.vers")
 local cfg = require("luarocks.core.cfg")
 local util = require("luarocks.util")
-
---- Convert the arch field of a query table to table format.
--- @param query table: A query table.
-local function query_arch_as_table(query)
-   local format = type(query.arch)
-   if format == "table" then
-      return
-   elseif format == "nil" then
-      local accept = {}
-      accept["src"] = true
-      accept["all"] = true
-      accept["rockspec"] = true
-      accept["installed"] = true
-      accept[cfg.arch] = true
-      query.arch = accept
-   elseif format == "string" then
-      local accept = {}
-      for a in query.arch:gmatch("[%w_-]+") do
-         accept[a] = true
-      end
-      query.arch = accept
-   end
-end
+local queries = require("luarocks.queries")
 
 --- Store a search result (a rock or rockspec) in the results table.
 -- @param results table: The results table, where keys are package names and
@@ -56,7 +34,7 @@ function search.store_result(results, name, version, arch, repo)
 end
 
 --- Test the name field of a query.
--- If query has a boolean field exact_name set to false,
+-- If query has a boolean field substring set to true,
 -- then substring match is performed; otherwise, exact string
 -- comparison is done.
 -- @param query table: A query in dependency table format.
@@ -65,7 +43,7 @@ end
 local function match_name(query, name)
    assert(type(query) == "table")
    assert(type(name) == "string")
-   if query.exact_name == false then
+   if query.substring then
       return name:find(query.name, 0, true) and true or false
    else
       return name == query.name
@@ -84,7 +62,7 @@ end
 -- @param version string: The version of the package being tested.
 -- @param arch string: The arch of the package being tested.
 -- @param query table: A table describing the query in dependency
--- format (for example, {name = "filesystem", exact_name = false,
+-- format (for example, {name = "filesystem", substring = true,
 -- constraints = {op = "~>", version = {1,0}}}, arch = "rockspec").
 -- If the arch field is omitted, the local architecture (cfg.arch)
 -- is used. The special value "any" is also recognized, returning all
@@ -102,7 +80,7 @@ end
 --- Perform search on a local repository.
 -- @param repo string: The pathname of the local repository.
 -- @param query table: A table describing the query in dependency
--- format (for example, {name = "filesystem", exact_name = false,
+-- format (for example, {name = "filesystem", substring = true,
 -- constraints = {op = "~>", version = {1,0}}}, arch = "rockspec").
 -- If the arch field is omitted, the local architecture (cfg.arch)
 -- is used. The special value "any" is also recognized, returning all
@@ -123,7 +101,6 @@ function search.disk_search(repo, query, results)
    if not results then
       results = {}
    end
-   query_arch_as_table(query)
    
    for name in fs.dir(repo) do
       local pathname = dir.path(repo, name)
@@ -149,7 +126,7 @@ end
 -- @param repo string: The URL of a rocks server or
 -- the pathname of a rocks tree (as returned by path.rocks_dir()).
 -- @param query table: A table describing the query in dependency
--- format (for example, {name = "filesystem", exact_name = false,
+-- format (for example, {name = "filesystem", substring = true,
 -- constraints = {op = "~>", version = {1,0}}}, arch = "rockspec").
 -- If the arch field is omitted, the local architecture (cfg.arch)
 -- is used. The special value "any" is also recognized, returning all
@@ -160,8 +137,11 @@ function search.manifest_search(results, repo, query, lua_version)
    assert(type(results) == "table")
    assert(type(repo) == "string")
    assert(type(query) == "table")
-   
-   query_arch_as_table(query)
+
+   if query.namespace then
+      repo = repo .. "/manifests/" .. query.namespace
+   end
+
    local manifest, err, errcode = manif.load_manifest(repo, lua_version)
    if not manifest then
       return nil, err, errcode
@@ -216,24 +196,6 @@ function search.search_repos(query, lua_version)
    return results
 end
 
---- Prepare a query in dependency table format.
--- @param name string: The query name.
--- @param version string or nil: 
--- @return table: A query in table format
-function search.make_query(name, version)
-   assert(type(name) == "string")
-   assert(type(version) == "string" or not version)
-   
-   local query = {
-      name = name,
-      constraints = {}
-   }
-   if version then
-      table.insert(query.constraints, { op = "==", version = vers.parse_version(version)})
-   end
-   return query
-end
-
 --- Get the URL for the latest in a set of versions.
 -- @param name string: The package name to be used in the URL.
 -- @param versions table: An array of version informations, as stored
@@ -369,8 +331,7 @@ function search.act_on_src_or_rockspec(action, name, version, ...)
    assert(type(name) == "string")
    assert(type(version) == "string" or not version)
 
-   local query = search.make_query(name, version)
-   query.arch = "src|rockspec"
+   local query = queries.new(name, version, nil, "src|rockspec")
    local url, err = search.find_suitable_rock(query)
    if not url then
       return nil, "Could not find a result named "..name..(version and " "..version or "")..": "..err
@@ -380,8 +341,7 @@ end
 
 function search.pick_installed_rock(name, version, given_tree)
    local results = {}
-   local query = search.make_query(name, version)
-   query.exact_name = true
+   local query = queries.new(name, version, true)
    local tree_map = {}
    local trees = cfg.rocks_trees
    if given_tree then
-- 
cgit v1.2.3-55-g6feb