diff --git a/README.md b/README.md index dc11d83..12d2b88 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,10 @@ # luash +**Fork note: Powershell support is still very preliminary. Add-Assembly and calling managed assemblies is not yet supported. To call cmdlets, use verb__noun instead of verb-noun:** + +``` +local sh = require("sh") +write__host("Hello World.") +``` [![Build Status](https://travis-ci.org/zserge/luash.svg)](https://travis-ci.org/zserge/luash) diff --git a/bsh.lua b/bsh.lua new file mode 100644 index 0000000..37b6ec6 --- /dev/null +++ b/bsh.lua @@ -0,0 +1,88 @@ +--[[ +Bootstrap shell + +--]] + + +local function return_shell_output (cmd, pattern, debug) + if not cmd then io.stderr:write("cmd to pass to a shell was blank") return nil end + if debug then print(string.format("cmd: %s, pattern: %s", cmd, pattern)) end + + local match = false + local handle = io.popen(cmd) + if not pattern then + match = handle:read("*a") + elseif type(pattern) == "string" or type(pattern) == "function"then + for v in handle:lines() do + if debug then print(v) end + match = string.match(v, pattern) + if match then + if debug then print(string.format("Found %s", match)) end + break + end + end + else + io.stderr:write("Pattern was of wrong type for command " .. cmd) + end + handle:close() + return match +end + +local function return_os() + if package.config:sub(1,1) == "\\" then + return "WIN" + else + local ok = return_shell_output("uname -s") + if ok then + return ok + else + return "POSIX" + end + end +end + + +local function return_shell(os) + if os == "WIN" then + return "powershell" + else + return return_shell_output("echo $SHELL",trim) + end +end + + +local function test_path(os,location) + local cmd = "" + local pattern = "" + local ok = nil + assert(location) + --NOT string.match %s, this is replaced with the value of 'location'. + pattern = string.format("(%s)",location) + --check location exists + if os == WIN then + cmd = string.format("if($(test-path -path %s){echo %s}", location, location) + else + cmd = string.format("[ -d \'%s\' ] && echo \'%s\'", location, location) + end + + if return_shell_output(cmd,pattern) then + return true + else + return false + end +end + +local function return_home_dir(os) + local loc_data = "echo $HOME" + if os == "WIN" then + loc_data = "powershell $env:localappdata" + end + return return_shell_output(loc_data,trim) +end + +return { + get_home_dir = return_home_dir, + get_os = return_os, + get_shell = return_shell, + get_shell_output = return_shell_output + } \ No newline at end of file diff --git a/pwr_sh.lua b/pwr_sh.lua new file mode 100644 index 0000000..e1f3723 --- /dev/null +++ b/pwr_sh.lua @@ -0,0 +1,37 @@ +--[[ +Powershell extensions for sh.lua +--]] + +local funciton pwrsh_clean(str,pattern) + local pattern = pattern or "__" + return str::gsub(pattern,"-") +end + +--[[ +This is what I am attempting to achieve: +local unzip = '"Add-Type -assembly \'system.io.compression.filesystem\'; [io.compression.zipfile]::ExtractToDirectory(\'\',\'\')"' +--]] + +local function net_params_format(one,two,three,four) +-- local function net_class_call(net_type, method,...) + --iterate and format according to resolved type. + return string.format("\'%s\','\%s\'",one,two) +end + +local function net_class_call(net_type, method, one, two, three, four) +-- local function net_class_call(net_type, method,...) + local net_type_fmt = string.format("Add-Type -assembly \'%s\';",net_type) + local method_fmt = string.format("[%s]::%s(\%s))",net_type,method) + local net_call = string.format('"%s %s"',net_type_fmt,method_fmt) +-- return string.format('"%s%s %s"',add_type_fmt,net_type_fmt,method_fmt) + local net_call_fun = function (one,two,three,four) + return string.format(net_call,net_params_format(one,two) + end + return net_call_fun + end + + local function sanitize_quotes(str) + -- sanitize quotes for powershell. Not sure what the means quite yet, + -- but I know that powershell uses quote escapes differently. + return str + end \ No newline at end of file diff --git a/pwr_sh_test.lua b/pwr_sh_test.lua new file mode 100644 index 0000000..b1eac20 --- /dev/null +++ b/pwr_sh_test.lua @@ -0,0 +1,27 @@ +local sh = require("sh") + +--print(ls("~")) +local name = "luarocks-2.4.3-win32" +local ext = ".zip" +local uri = "http://luarocks.github.io/luarocks/releases/"..name..ext +local dest_dir = "C:\\temp\\" +local destination = dest_dir..name..ext + +--uri = "-Uri "..uri +local outfile = "-OutFile"--..destination +print("-uri",uri,outfile, destination) +print(Invoke__WebRequest ("-uri",uri,"-outfile", destination)) +--(uri,outfile ) + +--cd("C:\\temp") +--print(ls()) + +--print(type(out)) +--for i,v in pairs(out) do +-- print("i: "..i,"v: "..v) +--end +--print(out) +--ls = sh.command("ls") + +--print(ls("~")) + diff --git a/sh.lua b/sh.lua index e09ebdf..e6024ec 100644 --- a/sh.lua +++ b/sh.lua @@ -1,4 +1,23 @@ +--[[ +lua-shell. Converts global scoped function calls into shell commands. +TODO: create lines co-routine iterator over result +TODO: create pipe() function to use lua memory as a buffer +TODO: investigate scope of shell invocation (once, many?). +follow up is how to preserve environment? +TODO: can we change global scope to prevent "pollution" of actual global scope? +--]] local M = {} +local bootsrp = require("bsh") + +--init... +local slash = package.config:sub(1,1) +local filename = '.shluainput' +local tmpfile = "/tmp/"..filename +local trim = "^%s*(.-)%s*$" +local _os = bootsrp.get_os() +local shell = bootsrp.get_shell(_os) +local home_dir = bootsrp.get_home_dir(_os) +tmpfile = string.format("%s%s%s",home_dir,slash,filename) -- converts key and it's argument to "-k" or "-k=v" or just "" local function arg(k, a) @@ -46,24 +65,31 @@ local function command(cmd, ...) return function(...) local args = flatten({...}) local s = cmd + --TODO: Powershell requires quote sanitization. Only outer quotes of the command parameter can be double quotes for _, v in ipairs(prearg) do s = s .. ' ' .. v end for k, v in pairs(args.args) do s = s .. ' ' .. v end - + if args.input == "" then args.input = false end if args.input then - local f = io.open(M.tmpfile, 'w') + local f = io.open(tmpfile, 'w') f:write(args.input) f:close() - s = s .. ' <'..M.tmpfile + s = s .. ' <'..tmpfile end + myt = {} + s= s:gsub("__","-") + s = string.format("%s %s",shell, s) local p = io.popen(s, 'r') local output = p:read('*a') local _, exit, status = p:close() - os.remove(M.tmpfile) - + + if args.input then + os.remove(tmpfile) + end + local t = { __input = output, __exitcode = exit == 'exit' and status or 127, @@ -94,9 +120,34 @@ mt.__index = function(t, cmd) return command(cmd) end +local function set_temp(location) + --This should be sanitized + if test_path(location) then + tmpfile = location + return true + else + return nil, "location not found" + end +end + +local function get_temp() + return tmpfile +end +local function get_shell() + return shell +end + +local function set_shell(sh_name) + --assert(false,"NOT IMPLEMENTED") + if not os == "WIN" then + local ok = return_shell_output("which "..sh_name,trim) + if ok then shell = sh_name return true end + end + return nil, "Not supported" +end + -- export command() function and configurable temporary "input" file M.command = command -M.tmpfile = '/tmp/shluainput' -- allow to call sh to run shell commands setmetatable(M, { @@ -105,4 +156,12 @@ setmetatable(M, { end }) + +M.get_temp = get_temp +M.set_temp = set_temp +M.get_shell = get_shell +M.set_shell = set_shell +M.slash = slash +M.os = _os + return M