From 428129fe543255f6c5b1321ce1742d29e75e9cf4 Mon Sep 17 00:00:00 2001 From: russellhaley Date: Tue, 23 Jan 2018 23:59:02 -0800 Subject: [PATCH 1/4] Initial outline for powershell support. This impelentation creates a new shell each execution, which is not ideal. TODO: figure out how to change shell once permenantly (or does it always create a new shell?). TODO: Sanitize quotes for powershell as per in-code comment. --- powersh_test.lua | 9 ++++ sh.lua | 131 ++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 134 insertions(+), 6 deletions(-) create mode 100644 powersh_test.lua diff --git a/powersh_test.lua b/powersh_test.lua new file mode 100644 index 0000000..3df9bb7 --- /dev/null +++ b/powersh_test.lua @@ -0,0 +1,9 @@ +local lfs = require("lfs") +lfs.chdir("C:\\users\\russh\\git\\luash") +local sh = require("sh") + +print(ls("~")) +--ls = sh.command("ls") + +--print(ls("~")) + diff --git a/sh.lua b/sh.lua index e09ebdf..f1d485d 100644 --- a/sh.lua +++ b/sh.lua @@ -1,4 +1,84 @@ local M = {} +local slash = package.config:sub(1,1) +tmpfile = "/tmp/" +trim = "^%s*(.-)%s*$" +filename = '.shluainput' +local function return_os() + if slash == "\\" then + return "WIN" + else + local ok = return_shell_output("uname -s") + if ok then + return ok + else + return "POSIX" + end + end +end +_os = return_os() + +local function return_shell() + if _os == "WIN" then + return "powershell" + else + return return_shell_output("echo $SHELL",trim) + end +end + +local shell = return_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 test_path(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() + local loc_data = "echo $HOME" + if _os == "WIN" then + loc_data = "powershell $env:localappdata" + end + return return_shell_output(loc_data,trim) +end -- converts key and it's argument to "-k" or "-k=v" or just "" local function arg(k, a) @@ -46,24 +126,30 @@ 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 = 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 +180,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 +216,12 @@ setmetatable(M, { end }) +tmpfile = string.format("%s%s%s",return_home_dir(),slash,filename) +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 From e0e075cfb566eafb6ea5f6e1c6a1e38de0f08be7 Mon Sep 17 00:00:00 2001 From: russellhaley Date: Wed, 24 Jan 2018 23:50:26 -0800 Subject: [PATCH 2/4] Improved powershell support. Powershell commands are all hyphenated which is not lua freindly. My current solution is to script the command as verb__noun to replace the powershell verb-noun pattern using gsub. This works, but now we need a way of running managed assemblies from the command line. See pwr_sh.lua for the start of it. My idea is to pass a function instead of a string to sh.command(). upon execute, it will see it's a function and instead of appending arguments, pass the varargs in, and that gets added as the parameters. --- bsh.lua | 88 +++++++++++++++++++++++++++++++++++++++++ powersh_test.lua | 9 ----- pwr_sh.lua | 37 ++++++++++++++++++ pwr_sh_test.lua | 27 +++++++++++++ sh.lua | 100 ++++++++++------------------------------------- 5 files changed, 172 insertions(+), 89 deletions(-) create mode 100644 bsh.lua delete mode 100644 powersh_test.lua create mode 100644 pwr_sh.lua create mode 100644 pwr_sh_test.lua diff --git a/bsh.lua b/bsh.lua new file mode 100644 index 0000000..fb0abe0 --- /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/powersh_test.lua b/powersh_test.lua deleted file mode 100644 index 3df9bb7..0000000 --- a/powersh_test.lua +++ /dev/null @@ -1,9 +0,0 @@ -local lfs = require("lfs") -lfs.chdir("C:\\users\\russh\\git\\luash") -local sh = require("sh") - -print(ls("~")) ---ls = sh.command("ls") - ---print(ls("~")) - 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 f1d485d..e6024ec 100644 --- a/sh.lua +++ b/sh.lua @@ -1,84 +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 slash = package.config:sub(1,1) -tmpfile = "/tmp/" -trim = "^%s*(.-)%s*$" -filename = '.shluainput' -local function return_os() - if slash == "\\" then - return "WIN" - else - local ok = return_shell_output("uname -s") - if ok then - return ok - else - return "POSIX" - end - end -end -_os = return_os() - -local function return_shell() - if _os == "WIN" then - return "powershell" - else - return return_shell_output("echo $SHELL",trim) - end -end - -local shell = return_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 bootsrp = require("bsh") -local function test_path(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() - local loc_data = "echo $HOME" - if _os == "WIN" then - loc_data = "powershell $env:localappdata" - end - return return_shell_output(loc_data,trim) -end +--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) @@ -141,6 +80,7 @@ local function command(cmd, ...) 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') @@ -216,7 +156,7 @@ setmetatable(M, { end }) -tmpfile = string.format("%s%s%s",return_home_dir(),slash,filename) + M.get_temp = get_temp M.set_temp = set_temp M.get_shell = get_shell From d2b6b40b2a95950c3392a50f05e4ed6180184f48 Mon Sep 17 00:00:00 2001 From: russellhaley Date: Wed, 24 Jan 2018 23:54:11 -0800 Subject: [PATCH 3/4] Fixed bad indent. --- bsh.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bsh.lua b/bsh.lua index fb0abe0..37b6ec6 100644 --- a/bsh.lua +++ b/bsh.lua @@ -5,8 +5,8 @@ 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 + 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) From 6ba8047c3afe474b2ff281aa5d9c4662b1e9272c Mon Sep 17 00:00:00 2001 From: Russell Haley Date: Thu, 25 Jan 2018 00:02:05 -0800 Subject: [PATCH 4/4] Update README.md Temporary fork note. --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) 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)