mirror of
				https://codeberg.org/1414codeforge/moonspeak.git
				synced 2025-06-05 22:09:19 +02:00 
			
		
		
		
	[crush,.lovedeps] Add crush support.
This commit is contained in:
		
							
								
								
									
										3
									
								
								.lovedeps
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.lovedeps
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | { | ||||||
|  |     serialize = 'https://git.doublefourteen.io/lua/df-serialize' | ||||||
|  | } | ||||||
							
								
								
									
										284
									
								
								crush.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										284
									
								
								crush.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,284 @@ | |||||||
|  | --- crush - The uncomplicated dependency system for LÖVE. | ||||||
|  | -- | ||||||
|  | -- Author: Lorenzo Cogotti | ||||||
|  | -- Copyright: 2022 The DoubleFourteen Code Forge | ||||||
|  | -- License: MIT (see LICENSE file for details) | ||||||
|  |  | ||||||
|  | local io = require 'io' | ||||||
|  | local os = require 'os' | ||||||
|  |  | ||||||
|  | -- System specific functions | ||||||
|  | -- | ||||||
|  | -- Portions of this code are based on work from the LuaRocks project. | ||||||
|  | -- LuaRocks is free software and uses the MIT license. | ||||||
|  | -- | ||||||
|  | -- LuaRocks website: https://luarocks.org | ||||||
|  | -- LuaRocks sources: https://github.com/luarocks/luarocks | ||||||
|  |  | ||||||
|  | local is_windows = package.config:sub(1,1) == "\\" | ||||||
|  |  | ||||||
|  | local is_directory | ||||||
|  | local Q | ||||||
|  | local quiet | ||||||
|  | local chdir | ||||||
|  | local mkdir | ||||||
|  |  | ||||||
|  | if is_windows then | ||||||
|  |     -- --------------- | ||||||
|  |     -- NOTE: untested! | ||||||
|  |     -- --------------- | ||||||
|  |  | ||||||
|  |     -- local | ||||||
|  |     function is_directory(path) | ||||||
|  |         local fh, _, code = io.open(path, 'r') | ||||||
|  |  | ||||||
|  |         if code == 13 then  -- directories return "Permission denied" | ||||||
|  |             fh, _, code = io.open(path.."\\", 'r') | ||||||
|  |             if code == 2 then  -- directories return 2, files return 22 | ||||||
|  |                 return true | ||||||
|  |             end | ||||||
|  |         end | ||||||
|  |         if fh then | ||||||
|  |             fh:close() | ||||||
|  |         end | ||||||
|  |         return false | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     local function split_path(s) | ||||||
|  |         local drive = "" | ||||||
|  |         local root = "" | ||||||
|  |         local rest | ||||||
|  |  | ||||||
|  |         local unquoted = s:match("^['\"](.*)['\"]$") | ||||||
|  |         if unquoted then | ||||||
|  |             s = unquoted | ||||||
|  |         end | ||||||
|  |         if s:match("^.:") then | ||||||
|  |             drive = s:sub(1, 2) | ||||||
|  |             s = s:sub(3) | ||||||
|  |         end | ||||||
|  |         if s:match("^[\\/]") then | ||||||
|  |             root = s:sub(1, 1) | ||||||
|  |             rest = s:sub(2) | ||||||
|  |         else | ||||||
|  |             rest = s | ||||||
|  |         end | ||||||
|  |         return drive, root, rest | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     -- local | ||||||
|  |     function Q(s) | ||||||
|  |         local drive, root, rest = split_path(s) | ||||||
|  |         if root ~= "" then | ||||||
|  |             s = s:gsub("/", "\\") | ||||||
|  |         end | ||||||
|  |         if s == "\\" then | ||||||
|  |             return '\\' -- CHDIR needs special handling for root dir | ||||||
|  |         end | ||||||
|  |  | ||||||
|  |         -- URLs and anything else | ||||||
|  |         s = s:gsub('\\(\\*)"', '\\%1%1"') | ||||||
|  |         s = s:gsub('\\+$', '%0%0') | ||||||
|  |         s = s:gsub('"', '\\"') | ||||||
|  |         s = s:gsub('(\\*)%%', '%1%1"%%"') | ||||||
|  |         return '"'..s..'"' | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     -- local | ||||||
|  |     function quiet(cmd) | ||||||
|  |         return cmd.."  2> NUL 1> NUL" | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     -- local | ||||||
|  |     function chdir(newdir, cmd) | ||||||
|  |         local drive = newdir:match("^([A-Za-z]:)") | ||||||
|  |  | ||||||
|  |         cmd = "cd "..Q(newdir).." & "..cmd | ||||||
|  |         if drive then | ||||||
|  |             cmd = drive.." & "..cmd | ||||||
|  |         end | ||||||
|  |         return cmd | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     -- local | ||||||
|  |     function mkdir(path) | ||||||
|  |         local cmd = "mkdir "..Q(path).." 2> NUL 1> NUL" | ||||||
|  |  | ||||||
|  |         os.execute(cmd) | ||||||
|  |         if not is_directory(path) then | ||||||
|  |             error("Couldn't create directory '"..path.."'.") | ||||||
|  |         end | ||||||
|  |     end | ||||||
|  | else | ||||||
|  |     -- local | ||||||
|  |     function is_directory(path) | ||||||
|  |         local fh, _, code = io.open(path.."/.", 'r') | ||||||
|  |  | ||||||
|  |         if code == 2 then   -- "No such file or directory" | ||||||
|  |             return false | ||||||
|  |         end | ||||||
|  |         if code == 20 then  -- "Not a directory", regardless of permissions | ||||||
|  |             return false | ||||||
|  |         end | ||||||
|  |         if code == 13 then  -- "Permission denied", but is a directory | ||||||
|  |             return true | ||||||
|  |         end | ||||||
|  |         if fh then | ||||||
|  |             _, _, code = fh:read(1) | ||||||
|  |             fh:close() | ||||||
|  |             if code == 21 then  -- "Is a directory" | ||||||
|  |                 return true | ||||||
|  |             end | ||||||
|  |         end | ||||||
|  |         return false | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     -- local | ||||||
|  |     function Q(s) | ||||||
|  |         return "'"..s:gsub("'", "'\\''").."'" | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     -- local | ||||||
|  |     function quiet(cmd) | ||||||
|  |         return cmd.." >/dev/null 2>&1" | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     -- local | ||||||
|  |     function chdir(newdir, cmd) | ||||||
|  |         return "cd "..Q(newdir).." && "..cmd | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     -- local | ||||||
|  |     function mkdir(path) | ||||||
|  |         local cmd = "mkdir "..Q(path).." >/dev/null 2>&1" | ||||||
|  |  | ||||||
|  |         os.execute(cmd) | ||||||
|  |         if not is_directory(path) then | ||||||
|  |             error("Couldn't create directory '"..path.."'.") | ||||||
|  |         end | ||||||
|  |     end | ||||||
|  | end | ||||||
|  |  | ||||||
|  | -- Dependency fetch | ||||||
|  |  | ||||||
|  | local function fetch(dep) | ||||||
|  |     local dest = 'lib/'..dep.name | ||||||
|  |  | ||||||
|  |     print(("Dependency %s -> %s (%s)"):format(dep.name, dest, dep.url)) | ||||||
|  |  | ||||||
|  |     local cmd, fullcmd | ||||||
|  |  | ||||||
|  |     if is_directory(dest) then | ||||||
|  |         -- Directory exists, pull operation | ||||||
|  |         cmd = "git pull" | ||||||
|  |         fullcmd = chdir(dest, quiet("git pull")) | ||||||
|  |     else | ||||||
|  |         -- Directory doesn't exist, clone operation | ||||||
|  |         cmd = "git clone "..Q(dep.url).." "..Q(dep.name) | ||||||
|  |         fullcmd = chdir("lib", quiet(cmd)) | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     if not os.execute(fullcmd) then | ||||||
|  |         error(name..": Dependency fetch failed ("..cmd..").") | ||||||
|  |     end | ||||||
|  | end | ||||||
|  |  | ||||||
|  | -- .lovedeps file scan | ||||||
|  |  | ||||||
|  | local function map_file(name) | ||||||
|  |     local fh = io.open(name, 'r') | ||||||
|  |     if fh == nil then | ||||||
|  |         error(name..": can't read file.") | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     local contents = fh:read('*all') | ||||||
|  |     fh:close() | ||||||
|  |  | ||||||
|  |     return contents | ||||||
|  | end | ||||||
|  |  | ||||||
|  | local function scandeps(manifest, mode, deps) | ||||||
|  |     mode = mode or 'nodups' | ||||||
|  |     deps = deps or {} | ||||||
|  |  | ||||||
|  |     local contents = map_file(manifest) | ||||||
|  |     contents = "return "..contents | ||||||
|  |  | ||||||
|  |     local fun, res = load(contents, manifest, 't', {}) | ||||||
|  |     if not fun then | ||||||
|  |         error(res) | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     local ok, def = pcall(fun) | ||||||
|  |     if not ok then | ||||||
|  |         error(def)  -- def is now pcall()'s error message | ||||||
|  |     end | ||||||
|  |     if type(def) ~= 'table' then | ||||||
|  |         error("[string \""..manifest.."\"]: Loading resulted in a '"..type(def).."', while 'table' was expected.") | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     for name,url in pairs(def) do | ||||||
|  |         if type(url) == 'function' then | ||||||
|  |             goto skip  -- ignore functions | ||||||
|  |         end | ||||||
|  |  | ||||||
|  |         if type(url) ~= 'string' then | ||||||
|  |             error("[string \""..manifest.."\"]: "..name..": git repository URL must be a 'string'.") | ||||||
|  |         end | ||||||
|  |  | ||||||
|  |         for i in ipairs(deps) do | ||||||
|  |             if name == deps[i].name then | ||||||
|  |                 if mode == 'skipdups' then | ||||||
|  |                     goto skip | ||||||
|  |                 end | ||||||
|  |  | ||||||
|  |                 error("[string \""..manifest.."\"]: "..name..": Duplicate dependency.") | ||||||
|  |             end | ||||||
|  |         end | ||||||
|  |  | ||||||
|  |         deps[#deps+1] = { name = name, url = url } | ||||||
|  |  | ||||||
|  |         ::skip:: | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     return deps | ||||||
|  | end | ||||||
|  |  | ||||||
|  | -- Entry point | ||||||
|  |  | ||||||
|  | local function file_exists(name) | ||||||
|  |     local fh = io.open(name, 'r') | ||||||
|  |     if fh ~= nil then | ||||||
|  |         fh:close() | ||||||
|  |  | ||||||
|  |         return true | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     return false | ||||||
|  | end | ||||||
|  |  | ||||||
|  | local function run() | ||||||
|  |     local deps = scandeps(".lovedeps") | ||||||
|  |  | ||||||
|  |     mkdir("lib") | ||||||
|  |  | ||||||
|  |     -- NOTE: deps array may grow while scanning | ||||||
|  |     local i = 1 | ||||||
|  |     while i <= #deps do | ||||||
|  |         local dep = deps[i] | ||||||
|  |  | ||||||
|  |         -- Fetch dependency | ||||||
|  |         fetch(dep) | ||||||
|  |  | ||||||
|  |         -- Resolve dependency's dependencies | ||||||
|  |         local depmanifest = "lib/"..dep.name.."/.lovedeps" | ||||||
|  |  | ||||||
|  |         if file_exists(depmanifest) then | ||||||
|  |             scandeps(depmanifest, 'skipdups', deps) | ||||||
|  |         end | ||||||
|  |  | ||||||
|  |         i = i + 1 | ||||||
|  |     end | ||||||
|  | end | ||||||
|  |  | ||||||
|  | run() | ||||||
		Reference in New Issue
	
	Block a user