Advertisement
ccpm

ccpm - remove now working

Apr 28th, 2016
146
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 15.01 KB | None | 0 0
  1. --
  2. local args, commands = { ... }, { };
  3. local headers = { Accept = 'application/lua', ['User-Agent'] = 'CCPM-Client/0.1.0' };
  4. local registryURL = 'https://ccpm-schroffl.rhcloud.com/registry/';
  5. local packageFile, moduleBaseDir, cfgFile = 'package.ccp', 'modules', '.ccpm';
  6.  
  7. -- Read and parse the ccpm config
  8. local function getConfigFile(dir)
  9.     local cfgFilePath = '/' .. shell.resolve(dir .. '/' .. cfgFile);
  10.  
  11.     if fs.exists(cfgFilePath) then return textutils.unserialize( fs.open(cfgFilePath, 'r').readAll() ) or { }
  12.     else
  13.         local fHandle = fs.open(cfgFilePath, 'w');
  14.         local data = { registry = 'https://ccpm-schroffl.rhcloud.com/registry/' };
  15.  
  16.         fHandle.write(textutils.serialize(data));
  17.         fHandle.close();
  18.         return data;
  19.     end
  20. end
  21.  
  22. -- Save ccpm config
  23. local function saveConfigFile(dir, data)
  24.     local cfgFilePath = '/' .. shell.resolve(dir .. '/' .. cfgFile);
  25.     local fHandle = fs.open(cfgFilePath, 'w');
  26.  
  27.     fHandle.write(textutils.serialize( data ));
  28.  
  29.     return fHandle.close();
  30. end
  31.  
  32. -- Set a property in the config
  33. local function setConfigProperty(key, ...)
  34.     local cfg = getConfigFile('/');
  35.  
  36.     cfg[key] = table.concat(arg or nil, ' ');
  37.  
  38.     saveConfigFile('/', cfg);
  39.     return cfg[key];
  40. end
  41.  
  42. -- Get a property from the config
  43. local function getConfigProperty(key)
  44.     return getConfigFile('/')[key];
  45. end
  46.  
  47.  
  48. -- Check whether a given directory has a package file
  49. local function hasPackageFile(dir)
  50.     return fs.exists(dir .. '/' .. packageFile) and not fs.isDir(dir .. '/' .. packageFile);
  51. end
  52.  
  53. -- Get the package file for a given directory
  54. -- @param dir - The directory to get the file from
  55. local function getPackageFile(dir)
  56.     local path = dir .. '/' .. packageFile;
  57.  
  58.     if hasPackageFile(dir) then
  59.         local fHandle, content, parsed = fs.open(path, 'r');
  60.         content = fHandle.readAll();
  61.         parsed = textutils.unserialize(content);
  62.  
  63.         fHandle.close();
  64.  
  65.         if type(parsed) == 'nil' then return error('Unable to parse ' .. packageFile);
  66.         else return parsed end
  67.     else
  68.         return error(packageFile .. ' not found for\n' .. dir);
  69.     end
  70. end
  71.  
  72. -- Save a given table as a package file
  73. -- @param dir - The directory of the file
  74. -- @param data - The table to save
  75. local function savePackageFile(dir, data)
  76.     local path = dir .. '/' .. packageFile;
  77.  
  78.     local fHandle = fs.open(path, 'w');
  79.  
  80.     fHandle.write(textutils.serialize( data ));
  81.     fHandle.close();
  82. end
  83.  
  84. -- Get the module directory for a given path
  85. -- @param dir
  86. local function getModuleDir(dir)
  87.     return '/' .. shell.resolve(dir .. '/' .. moduleBaseDir .. '/') .. '/';
  88. end
  89.  
  90.  
  91. -- Merge two tables and return it
  92. -- @param tbl1 - The table to merge into
  93. -- @param tbl2 - The table to merge
  94. local function merge(tbl1, tbl2)
  95.     for k, v in pairs(tbl2) do tbl1[k] = v end
  96.     return tbl1;
  97. end
  98.  
  99.  
  100. -- Returns a hexadecimal representation of the passed string
  101. -- @param str - The string to convert
  102. local function hexify(str)
  103.     local hexString = '';
  104.  
  105.     for i = 0, #str do
  106.         local char = string.byte( string.sub(str, i, i) );
  107.  
  108.         if char then
  109.             local hexChar = string.format('%x', char);
  110.             hexChar = string.len(hexChar) < 2 and '0' .. hexChar or hexChar;
  111.             hexString = hexString .. hexChar;
  112.         end
  113.     end
  114.  
  115.     return hexString;
  116. end
  117.  
  118.  
  119. -- Convert a directory structure to a table
  120. -- @param dir - The directory to parse
  121. local function tablify(dir, tbl)
  122.     -- If tbl is not of type 'table', make it one
  123.     if type(tbl) ~= 'table' then tbl = { } end
  124.  
  125.     -- Loop through all listings in the given directory
  126.     for i, file in ipairs(fs.list(dir)) do
  127.  
  128.         local path = dir .. '/' .. file;
  129.  
  130.         -- The current item is a directory -> go recursive
  131.         if fs.isDir(path) and file ~= moduleBaseDir then
  132.             tbl[file] = { };
  133.             tablify(path, tbl[file]);
  134.  
  135.         -- Otherwise just append it to the table
  136.         elseif file ~= moduleBaseDir then
  137.             local fHandle = fs.open(path, 'r');
  138.  
  139.             tbl[file] = hexify(fHandle.readAll());
  140.             fHandle.close();
  141.         end
  142.     end
  143.  
  144.     return tbl;
  145. end
  146.  
  147. -- Convert a table to a directory structure
  148. -- @param dir - The directory to extract everything into
  149. local function dirify(dir, tbl, isHex)
  150.     -- If the directory does not exist, create it
  151.     if not fs.exists(dir) then fs.makeDir(dir) end
  152.  
  153.     -- Loop through all listings in the table
  154.     for file, content in pairs(tbl) do
  155.         local path = dir .. '/' .. file;
  156.  
  157.         if type(content) == 'table' and file ~= moduleBaseDir then dirify(path, content, isHex)
  158.         elseif file ~= moduleBaseDir then
  159.             local fHandle, clearString = fs.open(path, 'w'), '';
  160.  
  161.             if not fHandle then return error('Could not open file handle for\n' .. path) end
  162.  
  163.             if isHex then for char in string.gmatch(content, '..') do
  164.                 clearString = clearString .. string.char(tonumber( char, 16 ));
  165.             end else
  166.                 clearString = content;
  167.             end
  168.  
  169.             fHandle.write(clearString);
  170.             fHandle.close();
  171.         end
  172.     end
  173. end
  174.  
  175.  
  176. -- Join given arguments together to form a url
  177. -- @param base - The base url
  178. local function urlify(base, ...)
  179.     local lastChar, urltable = string.sub(base, -1);
  180.  
  181.     if lastChar == '/' then base = string.sub(base, 0, -2) end
  182.     urltable = { base };
  183.  
  184.     for i, part in ipairs(arg) do
  185.         part = string.gsub(part, '^/+', '');
  186.         part = string.gsub(part, '/+$', '');
  187.  
  188.         table.insert(urltable, part);
  189.     end
  190.  
  191.     return table.concat(urltable, '/');
  192. end
  193.  
  194. -- Perform an http request
  195. -- @param method - The HTTP method
  196. -- @param parse - Parse the response to a table?
  197. -- @param url - The url
  198. -- @param ... - Any other args passed to the given method
  199. local function httpRequest(method, parse, url, ...)
  200.     local requestString, handle = method:upper() .. ' ' .. url, http[method]( url, unpack(arg) );
  201.  
  202.     if type(handle) == 'table' then
  203.         local content = handle.readAll();
  204.         content = parse and textutils.unserialize(content) or nil;
  205.  
  206.         if parse and not content then return error('HTTP request could not be parsed:\n' .. requestString)
  207.         else
  208.             handle.getResponseCode = function() return content.statusCode end
  209.  
  210.             return {
  211.                 success = handle.getResponseCode() == 200,
  212.                 body = content.body };
  213.         end
  214.     else
  215.         return error('HTTP request did not return a handle:\n' .. requestString);
  216.     end
  217. end
  218.  
  219.  
  220. -- Prompt a user
  221. -- @param text - The prompting text
  222. -- @param mask - The mask character for read()
  223. local function prompt(text, mask)
  224.     write(text);
  225.     return read(mask);
  226. end
  227.  
  228.  
  229. -- Gather requried credentials from a user
  230. local function gatherCredentials()
  231.     return prompt('username: '), prompt('password: ', '*');
  232. end
  233.  
  234.  
  235. -- Throw an error that was returned by the server
  236. -- @param err - The error text
  237. local function throwServerResponse(err)
  238.     return error('> ' .. (err or ''), -1);
  239. end
  240.  
  241.  
  242. -- Initialize a new package in the given directory
  243. -- @param dir - The directory in which to set up the package
  244. commands['init'] = {
  245.     func = function(dir)
  246.         if hasPackageFile(dir) then error('This directory already contains a ' .. packageFile, -1) end
  247.  
  248.         local req = httpRequest('get', true, urlify(registryURL, 'init'), headers);
  249.  
  250.         if req.success then
  251.             -- Start prompting the user for required information
  252.             for i, key in ipairs(req.body.prompt) do
  253.                 if type(req.body[packageFile][key]) == 'string' then req.body[packageFile][key] = string.gsub(req.body[packageFile][key], '%$dirname', dir):gsub('^/', '') end
  254.  
  255.                 local res = prompt(key .. ': ');
  256.                 req.body[packageFile][key] = #res > 0 and res or req.body[packageFile][key];
  257.             end
  258.  
  259.             dirify(dir, { [packageFile] = textutils.serialize(req.body[packageFile]) });
  260.         else
  261.             throwServerResponse(req.body.error);
  262.         end
  263.     end,
  264.     help = "Initialize a new package in the current directory. You will be prompted for required information",
  265.     usage = "init"
  266. };
  267.  
  268. -- Publish the package in the given directory
  269. -- @param dir - The directory of the package
  270. commands['publish'] = {
  271.     func = function(dir)
  272.         local pkgInfo = getPackageFile(dir);
  273.         local pkgDir = tablify(dir);
  274.  
  275.         pkgDir[packageFile] = pkgInfo;
  276.  
  277.         local uName, uPass = gatherCredentials();
  278.         local authHeaders = { ['x-auth-username'] = uName, ['x-auth-password'] = uPass };
  279.  
  280.         local req = httpRequest( 'post', true, urlify(registryURL, 'package', pkgInfo.name, pkgInfo.version), textutils.serializeJSON(pkgDir), merge(authHeaders, headers) );
  281.  
  282.         if req.success then
  283.             print(req.body.msg);
  284.         else
  285.             throwServerResponse(req.body.error);
  286.         end
  287.     end,
  288.     help = "Publish a package. You will be prompted for username and password",
  289.     usage = "publish"
  290. };
  291.  
  292. -- Install a package into a directory module folder
  293. -- @param dir - The directory in which to install the module
  294. -- @param input - An input string containing package name and version
  295. commands['install'] = {
  296.     func = function(dir, input, save, iterations)
  297.         -- No package specified? Silly user...
  298.         if type(input) ~= 'string' then error('No package specified', -1)
  299.         elseif type(iterations) ~= 'number' then iterations = 0 end
  300.  
  301.         -- Extract package name and version, if an invalid version format is used, it's going to install the latest version
  302.         local name, rest = string.match(input:lower(), '^([%a\-\_]+)(.*)$');
  303.         local version = rest and string.match(rest:lower(), '^@(%d+%.%d+%.%d+)$') or 'latest';
  304.  
  305.         -- Perform the request
  306.         local req = httpRequest('get', true, urlify(registryURL, 'package', name, version), headers);
  307.  
  308.         -- It was successful
  309.         if req.success then
  310.             local pkgInfo = req.body[packageFile];
  311.             local pkgExists = hasPackageFile(getModuleDir(dir) .. pkgInfo.name) and getPackageFile(getModuleDir(dir) .. pkgInfo.name) or nil;
  312.  
  313.             -- Save the package to dependencies
  314.             if save == '--save' then
  315.                 local pkgFile = getPackageFile(dir);
  316.                 pkgFile.dependencies[pkgInfo.name] = pkgInfo.version;
  317.                 savePackageFile(dir, pkgFile);
  318.             end
  319.  
  320.             -- Gives some idea of dependency level
  321.             io.write(string.rep(' ', iterations));
  322.  
  323.             -- Tell the user if the package is already installed
  324.             if pkgExists then print(pkgInfo.name , 'already installed. Updating');
  325.             else print('Installing', pkgInfo.name .. '@' .. pkgInfo.version) end
  326.  
  327.             -- Repeat this process for all dependencies
  328.             for n, v in pairs(pkgInfo.dependencies) do commands['install']( dir, n .. '@' .. v, '', iterations + 1 ) end
  329.  
  330.             req.body[packageFile] = hexify(textutils.serialize( pkgInfo ));
  331.             dirify(getModuleDir(dir) .. pkgInfo.name, req.body, true);
  332.  
  333.             io.write(string.rep(' ', iterations)); -- Again: dependency level
  334.             print('Successfully installed', pkgInfo.name .. '@' .. pkgInfo.version);
  335.  
  336.         -- It wasn't...
  337.         else
  338.             throwServerResponse(req.body.error);
  339.         end
  340.     end,
  341.     help = "Install a given package",
  342.     usage = "install <name[@version]> <[--save]>"
  343. };
  344.  
  345. -- Remove a package from a directories modules folder
  346. -- @param dir - The directory
  347. -- @param pkg the packages name
  348. commands['remove'] = {
  349.     func = function(dir, pkgName, save)
  350.         if type(pkgName) ~= 'string' then error('No package specified', -1) end
  351.  
  352.         local pkgDir = getModuleDir(dir) .. pkgName;
  353.         local pkgInfo = getPackageFile(pkgDir);
  354.  
  355.         -- Remove the package from the dependencies as well?
  356.         if save == '--save' then
  357.         end
  358.  
  359.         print('Removing', pkgDir);
  360.         fs.delete(pkgDir);
  361.     end,
  362.     help = "Delete a given package",
  363.     usage = "remove <name>"
  364. };
  365.  
  366. -- Run a given file
  367. commands['run'] = {
  368.     func = function(dir, file)
  369.         local pkgFile = not file and getPackageFile(dir) or { };
  370.  
  371.         local function require(fName, path)
  372.             local isPkgName = string.find(fName, '^[%a%-_]+$') and true or false;
  373.             local fPath = '';
  374.  
  375.             -- It's a package name
  376.             if isPkgName then
  377.                 local pkgPath = getModuleDir(dir) .. fName .. '/';
  378.                 fPath =  pkgPath .. '/' .. getPackageFile(pkgPath).main;
  379.             -- It's an absolute or relative path
  380.             else
  381.                 fPath = path .. '/' .. fName;
  382.             end
  383.  
  384.             fPath = '/' .. shell.resolve(fPath);
  385.  
  386.             -- Does the file even exist?308
  387.             if not fs.exists(fPath) then error('File not found:\n' .. (fPath or 'nil')) end
  388.  
  389.             -- Set up the environment and load the file
  390.             local pkgEnv = setmetatable({ }, { __index = _G });
  391.             local pkgFunc = assert(loadfile( fPath ));
  392.  
  393.             -- Create a wrapper to automatically keep track of the current path
  394.             pkgEnv.require = function(fName)
  395.                 local extractedPath = string.match(fPath, '^(.*/)(.+)$');
  396.                 return require(fName, extractedPath);
  397.             end
  398.  
  399.             -- Set the environment
  400.             setfenv(pkgFunc, pkgEnv);
  401.  
  402.             -- Return the packages 'exports'
  403.             return pkgFunc();
  404.         end
  405.  
  406.         -- Require this package or the given file to get everything running
  407.         require('./' .. file, dir);
  408.     end,
  409.     help = "Run a file with function require exposed to it",
  410.     usage = "ccpm run <file>"
  411. };
  412.  
  413. -- Create a new account
  414. commands['register'] = {
  415.     func = function(dir)
  416.         local uName, uPass = gatherCredentials();
  417.  
  418.         local req = httpRequest( 'post', true, urlify(registryURL, 'user'), textutils.serializeJSON({ name = uName, pass = uPass }), headers );
  419.  
  420.         if req.success then
  421.             print(req.body.msg);
  422.         else
  423.             throwServerResponse(req.body.error);
  424.         end
  425.     end,
  426.     help = "Create a new account. You will be prompted for username and password",
  427.     usage = "register"
  428. };
  429.  
  430. -- Update the version of a package
  431. commands['version'] = {
  432.     func = function(dir, input)
  433.         local pkgFile = getPackageFile(dir);
  434.         local major, minor, patch = string.match(pkgFile.version, '^(%d+)%.(%d+)%.(%d+)$')
  435.  
  436.         major = tonumber(major);
  437.         minor = tonumber(minor);
  438.         patch = tonumber(patch);
  439.  
  440.         local previous = major .. '.' .. minor .. '.' .. patch;
  441.  
  442.         if input == 'major' then
  443.             major = major + 1;
  444.             minor = 0;
  445.             patch = 0;
  446.         elseif input == 'minor' then
  447.             minor = minor + 1;
  448.             patch = 0;
  449.         elseif input == 'patch' then
  450.             patch = patch + 1;
  451.         else
  452.             major, minor, patch = string.match(input or '', '^(%d+)%.(%d+)%.(%d+)$');
  453.             if not major or not minor or not patch then error('Invalid version format', -1) end
  454.         end
  455.  
  456.         pkgFile.version = major .. '.' .. minor .. '.' .. patch;
  457.  
  458.         print(previous, '->', pkgFile.version);
  459.  
  460.         savePackageFile(dir, pkgFile);
  461.     end,
  462.     help = "Increment the version of your package",
  463.     usage = "version <major|minor|patch>"
  464. };
  465.  
  466. -- Set a properties value in the config
  467. commands['set'] = {
  468.     func = function(dir, key, ...)
  469.         print(key, '->', setConfigProperty( key, unpack(arg) ));
  470.     end,
  471.     help = "Set a property in the ccpm config",
  472.     usage = "set <property> <value>"
  473. };
  474.  
  475. -- Get a properties value from the config
  476. commands['get'] = {
  477.     func = function(dir, key)
  478.         print(getConfigProperty( key ));
  479.     end,
  480.     help = "Get a property from the ccpm config",
  481.     usage = "get <property>"
  482. };
  483.  
  484. -- Alias for install
  485. commands['i'] = commands['install'];
  486.  
  487. -- Print some help
  488. commands['help'] = {
  489.     func = function(dir, command)
  490.         if type(commands[command]) == 'nil' then error('Unkown command: ' .. (command or 'nil'), -1)
  491.         else
  492.             print(commands[command].help, '\n');
  493.             print('Usage:', commands[command].usage);
  494.         end
  495.     end,
  496.     help = "Print the help message for a given command",
  497.     usage = "help <command>"
  498. };
  499.  
  500. -- Get the registry URL from the config
  501. registryURL = getConfigFile('/').registry or registryURL;
  502.  
  503. -- Exctract the supplied command and call its corresponding function
  504. local cmd = table.remove(args, 1);
  505.  
  506. cmd = type(commands[cmd]) == 'nil' and 'help' or cmd;
  507.  
  508. commands[ cmd ].func( unpack({ '/' .. shell.dir(), unpack(args) }) );
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement