Compare commits
7 Commits
27dfa57a5d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| cc8cce8787 | |||
| 990aee8168 | |||
| 8598e9ff57 | |||
| 6f92a80bb1 | |||
| 21f5ab5335 | |||
| da979811dd | |||
| cf19a6ec5d |
160
README.md
160
README.md
@@ -110,144 +110,20 @@ Explore organized examples that demonstrate Luash features:
|
|||||||
|
|
||||||
See [examples/README.md](examples/README.md) for the complete learning path.
|
See [examples/README.md](examples/README.md) for the complete learning path.
|
||||||
|
|
||||||
### What You Get
|
### Built-ins
|
||||||
|
|
||||||
#### File System Operations
|
Luash intentionally stays minimal. The injected helpers are:
|
||||||
```lua
|
|
||||||
-- Check file existence
|
|
||||||
if fs.exists("config.txt") then
|
|
||||||
content = fs.read("config.txt")
|
|
||||||
end
|
|
||||||
|
|
||||||
-- File operations
|
|
||||||
fs.write("output.txt", "Hello World")
|
|
||||||
fs.append("log.txt", "New entry\n")
|
|
||||||
|
|
||||||
-- Advanced file operations
|
|
||||||
file.copy("source.txt", "backup.txt")
|
|
||||||
file.move("old.txt", "new.txt")
|
|
||||||
lines = file.lines("data.txt") -- Read as array of lines
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Directory Operations
|
|
||||||
```lua
|
|
||||||
-- Directory management
|
|
||||||
dir.create("new_folder") -- mkdir -p equivalent
|
|
||||||
files = dir.list("/tmp") -- List directory contents
|
|
||||||
current = dir.pwd() -- Get current directory
|
|
||||||
|
|
||||||
-- Convenience aliases
|
|
||||||
mkdir("test")
|
|
||||||
cd("/home/user")
|
|
||||||
ls("/var/log")
|
|
||||||
```
|
|
||||||
|
|
||||||
#### String Utilities
|
|
||||||
```lua
|
|
||||||
-- String manipulation
|
|
||||||
parts = str.split("a,b,c", ",")
|
|
||||||
clean = str.trim(" whitespace ")
|
|
||||||
bool = str.starts_with("hello", "hel")
|
|
||||||
|
|
||||||
-- Text processing
|
|
||||||
result = text.grep("pattern", "file.txt")
|
|
||||||
lines = text.head(10, "large_file.txt")
|
|
||||||
count = text.wc("-l", "data.txt") -- Line count
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Array/Table Operations
|
|
||||||
```lua
|
|
||||||
-- Array utilities
|
|
||||||
found = array.contains({"a", "b", "c"}, "b")
|
|
||||||
doubled = array.map({1, 2, 3}, function(x) return x * 2 end)
|
|
||||||
evens = array.filter({1, 2, 3, 4}, function(x) return x % 2 == 0 end)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Path Utilities
|
|
||||||
```lua
|
|
||||||
-- Path manipulation
|
|
||||||
full_path = path.join("/usr", "local", "bin", "luash")
|
|
||||||
dir_name = path.dirname("/path/to/file.txt") -- "/path/to"
|
|
||||||
base_name = path.basename("/path/to/file.txt") -- "file.txt"
|
|
||||||
extension = path.ext("document.pdf") -- "pdf"
|
|
||||||
```
|
|
||||||
|
|
||||||
#### System Information
|
|
||||||
```lua
|
|
||||||
-- System utilities
|
|
||||||
os_name = sys.os() -- "linux", "darwin", etc.
|
|
||||||
arch = sys.arch() -- "x86_64", "arm64", etc.
|
|
||||||
hostname = sys.hostname() -- Get machine name
|
|
||||||
user = sys.whoami() -- Current username
|
|
||||||
memory_info = sys.memory() -- Memory usage
|
|
||||||
disk_info = sys.disk("/") -- Disk usage
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Network Operations
|
|
||||||
```lua
|
|
||||||
-- Network utilities
|
|
||||||
success = net.download("https://example.com/file.txt", "local_file.txt")
|
|
||||||
response = net.get("https://api.example.com/data")
|
|
||||||
post_result = net.post("https://api.example.com/submit", "key=value")
|
|
||||||
reachable = net.ping("google.com", 3) -- Ping 3 times
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Process Management
|
|
||||||
```lua
|
|
||||||
-- Process operations
|
|
||||||
my_pid = proc.pid()
|
|
||||||
git_path = proc.which("git") -- Find executable path
|
|
||||||
|
|
||||||
-- Job control
|
|
||||||
job.background("long_running_command")
|
|
||||||
job.kill(1234, "TERM") -- Kill process by PID
|
|
||||||
job.killall("firefox") -- Kill all processes by name
|
|
||||||
processes = job.ps() -- Get process list
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Logging and Colors
|
|
||||||
```lua
|
|
||||||
-- Colored logging
|
|
||||||
log.info("Information message")
|
|
||||||
log.warn("Warning message")
|
|
||||||
log.error("Error message")
|
|
||||||
log.success("Success message")
|
|
||||||
|
|
||||||
-- Color output
|
|
||||||
print(color.red("Error text"))
|
|
||||||
print(color.green("Success text"))
|
|
||||||
print(color.bold(color.yellow("Important notice")))
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Archive Operations
|
|
||||||
```lua
|
|
||||||
-- Compression utilities
|
|
||||||
archive.tar_create("backup.tar.gz", {"file1.txt", "file2.txt"})
|
|
||||||
archive.tar_extract("backup.tar.gz", "/destination")
|
|
||||||
archive.zip_create("archive.zip", {"folder1", "folder2"})
|
|
||||||
archive.zip_extract("archive.zip", "/extract_here")
|
|
||||||
```
|
|
||||||
|
|
||||||
### Convenience Aliases
|
|
||||||
|
|
||||||
Common shell commands are available as Lua functions:
|
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
ls() -- ls -la
|
-- Minimal helpers available globally
|
||||||
pwd() -- Get current directory
|
env_get(name) -- Get environment variable (with session overrides)
|
||||||
cd("/path") -- Change directory
|
env_set(name, value) -- Set environment variable (session + subprocess export)
|
||||||
mkdir("folder") -- Create directory
|
shell(cmd) -- Run shell command and return trimmed stdout
|
||||||
rm("file") -- Remove file
|
run(cmd) -- Run shell command and stream output (exit code returned)
|
||||||
cp("src", "dst") -- Copy file
|
|
||||||
mv("old", "new") -- Move/rename file
|
|
||||||
cat("file.txt") -- Read file contents
|
|
||||||
echo("Hello") -- Print and return text
|
|
||||||
grep("pattern", "file.txt") -- Search in file
|
|
||||||
head(5, "file.txt") -- First 5 lines
|
|
||||||
tail(10, "file.txt") -- Last 10 lines
|
|
||||||
wc("-l", "file.txt") -- Count lines
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Everything else is standard Lua.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Basic Usage
|
### Basic Usage
|
||||||
@@ -266,12 +142,15 @@ The interactive shell supports:
|
|||||||
- All luash preprocessing features
|
- All luash preprocessing features
|
||||||
- Multiline input for functions, loops, etc.
|
- Multiline input for functions, loops, etc.
|
||||||
- Command history with `.history`
|
- Command history with `.history`
|
||||||
- Screen clearing with `.clear`
|
|
||||||
- Special commands: `.help`, `.exit`, `.clear`
|
- Special commands: `.help`, `.exit`, `.clear`
|
||||||
- Real-time expression evaluation
|
- Real-time expression evaluation
|
||||||
|
|
||||||
See [examples/08_repl_guide.luash](examples/08_repl_guide.luash) for a comprehensive guide.
|
See [examples/08_repl_guide.luash](examples/08_repl_guide.luash) for a comprehensive guide.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- Raw terminal mode (immediate Ctrl+L/Ctrl+C/Ctrl+D) auto-enables when attached to a TTY.
|
||||||
|
- Force enable/disable with `LUASH_RAW=1` or `LUASH_RAW=0`.
|
||||||
|
|
||||||
### Debug Mode
|
### Debug Mode
|
||||||
```bash
|
```bash
|
||||||
LUASH_DEBUG=1 ./luash script.luash
|
LUASH_DEBUG=1 ./luash script.luash
|
||||||
@@ -329,13 +208,14 @@ file.write_lines("output.txt", processed)
|
|||||||
|
|
||||||
## Preprocessor Pipeline
|
## Preprocessor Pipeline
|
||||||
|
|
||||||
Luash applies several preprocessing steps:
|
Luash applies these preprocessing steps:
|
||||||
|
|
||||||
1. **Environment Variable Substitution**: `$VAR` and `${VAR}` → `env.get('VAR')`
|
1. **Shebang removal (line-preserving)**: `#!/usr/bin/env luash` is removed and replaced by a blank line to preserve line numbers.
|
||||||
2. **Interactive Command Processing**: `!command` → `run('command')`
|
2. **Environment variable assignment**: `$VAR = value` → `env_set('VAR', value)` (outside strings only)
|
||||||
3. **Shell Command Substitution**: `` `command` `` → `shell('command')`
|
3. **Environment variable substitution**: `$VAR` → `env_get('VAR')` (outside strings only; `${VAR}` is not supported)
|
||||||
4. **Variable Interpolation**: `#{var}` in commands → Lua string concatenation
|
4. **Interactive commands**: lines starting with `!cmd` → `run("cmd")`
|
||||||
5. **Library Injection**: Comprehensive shell utilities loaded automatically
|
5. **Command substitution**: `` `command` `` → `shell("command")`
|
||||||
|
6. **Lua variable interpolation in commands**: inside backticks, `#{var}` is replaced with the Lua value of `var`, with proper quoting handled by the caller
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
327
luash
327
luash
@@ -1,21 +1,33 @@
|
|||||||
#!/usr/bin/env lua
|
#!/usr/bin/env lua
|
||||||
-- luash: Minimal Lua shell scripting preprocessor
|
-- luash: Minimal Lua shell scripting preprocessor
|
||||||
|
|
||||||
-- Load injected library into global scope for REPL
|
-- Module path for local src/
|
||||||
|
package.path = (debug.getinfo(1, "S").source:match("@(.*/)") or "./") .. "src/?.lua;" .. package.path
|
||||||
|
|
||||||
|
-- Load injected library via module
|
||||||
local function load_injected_lib()
|
local function load_injected_lib()
|
||||||
local injected_lib_path = debug.getinfo(1, "S").source:match("@(.*/)") or "./"
|
local injected = require('injected')
|
||||||
local lib_file = io.open(injected_lib_path .. "__injected_lib.lua", "r")
|
injected.load()
|
||||||
if lib_file then
|
|
||||||
local lib_code = lib_file:read("*a")
|
|
||||||
lib_file:close()
|
|
||||||
local lib_func = load(lib_code)
|
|
||||||
if lib_func then lib_func() end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- REPL (Interactive Shell) Implementation
|
-- REPL (Interactive Shell) Implementation
|
||||||
function start_repl()
|
function start_repl()
|
||||||
load_injected_lib()
|
load_injected_lib()
|
||||||
|
local repl_mod = require('repl')
|
||||||
|
|
||||||
|
-- Preprocessing functions (simplified versions for REPL)
|
||||||
|
local function process_env_vars_repl(code)
|
||||||
|
local pre = require('preprocess')
|
||||||
|
return pre.process_env_vars(code)
|
||||||
|
end
|
||||||
|
local function process_shell_commands_repl(code)
|
||||||
|
local pre = require('preprocess')
|
||||||
|
return pre.process_shell_commands(code)
|
||||||
|
end
|
||||||
|
local function process_interactive_commands_repl(code)
|
||||||
|
local pre = require('preprocess')
|
||||||
|
return pre.process_interactive_commands(code)
|
||||||
|
end
|
||||||
|
|
||||||
-- Preprocessing functions (simplified versions for REPL)
|
-- Preprocessing functions (simplified versions for REPL)
|
||||||
local function process_env_vars_repl(code)
|
local function process_env_vars_repl(code)
|
||||||
@@ -133,7 +145,8 @@ function start_repl()
|
|||||||
|
|
||||||
-- Luash preprocessing function for REPL
|
-- Luash preprocessing function for REPL
|
||||||
local function preprocess_luash_repl(code)
|
local function preprocess_luash_repl(code)
|
||||||
code = code:gsub("^#![^\r\n]*[\r\n]?", "") -- Remove shebang
|
local pre = require('preprocess')
|
||||||
|
code = pre.remove_shebang_preserve_lines(code)
|
||||||
code = process_env_vars_repl(code)
|
code = process_env_vars_repl(code)
|
||||||
code = process_interactive_commands_repl(code)
|
code = process_interactive_commands_repl(code)
|
||||||
code = process_shell_commands_repl(code)
|
code = process_shell_commands_repl(code)
|
||||||
@@ -205,287 +218,43 @@ function start_repl()
|
|||||||
return s:match("^%s*(.-)%s*$")
|
return s:match("^%s*(.-)%s*$")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Main REPL loop
|
-- Delegate to module REPL
|
||||||
print("🚀 Luash Interactive Shell")
|
repl_mod.start(preprocess_luash_repl, load_injected_lib)
|
||||||
print("Type Lua expressions, use $VAR for env vars, `commands` for shell")
|
|
||||||
print("Special commands: .help .exit .clear .history")
|
|
||||||
print("")
|
|
||||||
|
|
||||||
while true do
|
|
||||||
local prompt = repl.buffer == "" and "luash> " or " >> "
|
|
||||||
io.write(prompt)
|
|
||||||
io.flush()
|
|
||||||
|
|
||||||
local line = io.read("*line")
|
|
||||||
|
|
||||||
if not line then -- EOF (Ctrl+D)
|
|
||||||
print("\nGoodbye!")
|
|
||||||
break
|
|
||||||
end
|
|
||||||
|
|
||||||
line = trim(line)
|
|
||||||
|
|
||||||
-- Handle special dot commands
|
|
||||||
if line == ".exit" or line == ".quit" then
|
|
||||||
break
|
|
||||||
elseif line == ".clear" then
|
|
||||||
repl.buffer = ""
|
|
||||||
print("Buffer cleared")
|
|
||||||
elseif line == ".history" then
|
|
||||||
for i, cmd in ipairs(repl.history) do
|
|
||||||
print(string.format("%3d: %s", i, cmd))
|
|
||||||
end
|
|
||||||
elseif line == ".help" then
|
|
||||||
print([[
|
|
||||||
Luash Interactive Shell Help:
|
|
||||||
|
|
||||||
Luash Features:
|
|
||||||
$VAR - Access environment variables
|
|
||||||
$VAR = "value" - Set environment variables
|
|
||||||
`command` - Execute shell commands
|
|
||||||
#{var} - Variable interpolation in commands
|
|
||||||
!command - Interactive shell commands
|
|
||||||
|
|
||||||
Special Commands:
|
|
||||||
.help - Show this help
|
|
||||||
.exit, .quit - Exit the shell
|
|
||||||
.clear - Clear input buffer
|
|
||||||
.history - Show command history
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
print("Hello " .. $USER)
|
|
||||||
files = `ls -la`
|
|
||||||
$GREETING = "Hello World"
|
|
||||||
result = `echo #{$GREETING}`
|
|
||||||
!ps aux | head -5
|
|
||||||
|
|
||||||
Multi-line input supported for functions, loops, etc.]])
|
|
||||||
else
|
|
||||||
-- Add to buffer
|
|
||||||
if repl.buffer ~= "" then
|
|
||||||
repl.buffer = repl.buffer .. "\n" .. line
|
|
||||||
else
|
|
||||||
repl.buffer = line
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Check if we have a complete expression
|
|
||||||
if not needs_continuation(repl.buffer) and line ~= "" then
|
|
||||||
-- Process and execute
|
|
||||||
table.insert(repl.history, repl.buffer)
|
|
||||||
|
|
||||||
local processed = preprocess_luash_repl(repl.buffer)
|
|
||||||
local func, err = load("return " .. processed, "@repl")
|
|
||||||
|
|
||||||
if not func then
|
|
||||||
-- Try without return (for statements)
|
|
||||||
func, err = load(processed, "@repl")
|
|
||||||
end
|
|
||||||
|
|
||||||
if func then
|
|
||||||
local success, result = pcall(func)
|
|
||||||
if success then
|
|
||||||
if result ~= nil then
|
|
||||||
print(tostring(result))
|
|
||||||
end
|
|
||||||
else
|
|
||||||
print("Error: " .. tostring(result))
|
|
||||||
end
|
|
||||||
else
|
|
||||||
print("Syntax error: " .. err)
|
|
||||||
end
|
|
||||||
|
|
||||||
repl.buffer = ""
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- 1. Check for an input file or REPL mode
|
-- 1. Check for an input file or REPL mode
|
||||||
|
local debug_mode = false -- legacy alias for highlight compile
|
||||||
|
local compile_mode = false
|
||||||
|
local highlight_mode = false
|
||||||
local filename = arg[1]
|
local filename = arg[1]
|
||||||
|
if filename == "-d" or filename == "--debug" then
|
||||||
|
debug_mode = true
|
||||||
|
compile_mode = false
|
||||||
|
highlight_mode = true
|
||||||
|
filename = arg[2]
|
||||||
|
elseif filename == "-c" or filename == "--compile" then
|
||||||
|
compile_mode = true
|
||||||
|
highlight_mode = false
|
||||||
|
filename = arg[2]
|
||||||
|
elseif filename == "-C" or filename == "--compile-highlight" then
|
||||||
|
compile_mode = true
|
||||||
|
highlight_mode = true
|
||||||
|
filename = arg[2]
|
||||||
|
end
|
||||||
|
|
||||||
if filename == "-h" or filename == "--help" then
|
if filename == "-h" or filename == "--help" then
|
||||||
print("Usage: luash <script.luash>")
|
print("Usage: luash <script.luash>")
|
||||||
print(" luash -i # Interactive mode")
|
print(" luash -i # Interactive mode")
|
||||||
|
print(" luash -c <file> # COMPILE MODE: print Lua and exit (no colors)")
|
||||||
|
print(" luash -C <file> # COMPILE MODE: print Lua with highlighting and exit")
|
||||||
|
print(" luash -d <file> # Print generated Lua (highlighted) then run")
|
||||||
print(" luash -h # Show this help")
|
print(" luash -h # Show this help")
|
||||||
return
|
return
|
||||||
elseif not filename or filename == "-i" or filename == "--interactive" then
|
elseif not filename or filename == "-i" or filename == "--interactive" then
|
||||||
start_repl()
|
start_repl()
|
||||||
end
|
|
||||||
|
|
||||||
local file = io.open(filename, "r")
|
|
||||||
if not file then
|
|
||||||
print("Error: Cannot open file '" .. filename .. "'")
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local source_code = file:read("*a")
|
-- Delegate to runner for file execution
|
||||||
file:close()
|
local runner = require('runner')
|
||||||
|
runner.run_file(filename, { debug = debug_mode, compile = compile_mode, highlight = highlight_mode })
|
||||||
-- Remove shebang line if present
|
|
||||||
source_code = source_code:gsub("^#![^\r\n]*[\r\n]?", "")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- Preprocessing functions
|
|
||||||
local function process_env_vars(code)
|
|
||||||
local lines = {}
|
|
||||||
for line in code:gmatch("([^\r\n]*)") do
|
|
||||||
if not line:match("^%s*%-%-") then -- Skip comments
|
|
||||||
|
|
||||||
-- Handle environment variable assignment: $VAR=value
|
|
||||||
if line:match("^%$([%w_]+)%s*=") then
|
|
||||||
line = line:gsub("^%$([%w_]+)%s*=%s*(.+)", function(var, value)
|
|
||||||
return "env_set('" .. var .. "', " .. value .. ")"
|
|
||||||
end)
|
|
||||||
else
|
|
||||||
-- Handle $VAR substitution, but not inside strings
|
|
||||||
local in_string = false
|
|
||||||
local quote_char = nil
|
|
||||||
local result = ""
|
|
||||||
local i = 1
|
|
||||||
|
|
||||||
while i <= #line do
|
|
||||||
local char = line:sub(i, i)
|
|
||||||
|
|
||||||
-- Track string boundaries
|
|
||||||
if (char == '"' or char == "'") and (i == 1 or line:sub(i-1, i-1) ~= "\\") then
|
|
||||||
if not in_string then
|
|
||||||
in_string = true
|
|
||||||
quote_char = char
|
|
||||||
elseif char == quote_char then
|
|
||||||
in_string = false
|
|
||||||
quote_char = nil
|
|
||||||
end
|
|
||||||
result = result .. char
|
|
||||||
i = i + 1
|
|
||||||
elseif char == "$" and not in_string then
|
|
||||||
-- Found $ outside of string, check for variable pattern
|
|
||||||
local var_match = line:sub(i):match("^%$([%w_]+)")
|
|
||||||
if var_match then
|
|
||||||
result = result .. "env_get('" .. var_match .. "')"
|
|
||||||
i = i + #var_match + 1
|
|
||||||
else
|
|
||||||
result = result .. char
|
|
||||||
i = i + 1
|
|
||||||
end
|
|
||||||
else
|
|
||||||
result = result .. char
|
|
||||||
i = i + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
line = result
|
|
||||||
end
|
|
||||||
end
|
|
||||||
table.insert(lines, line)
|
|
||||||
end
|
|
||||||
|
|
||||||
return table.concat(lines, "\n")
|
|
||||||
end
|
|
||||||
|
|
||||||
local function process_shell_commands(code)
|
|
||||||
local lines = {}
|
|
||||||
for line in code:gmatch("([^\r\n]*)") do
|
|
||||||
-- Skip processing backticks inside strings
|
|
||||||
local in_string = false
|
|
||||||
local quote_char = nil
|
|
||||||
local result = ""
|
|
||||||
local i = 1
|
|
||||||
|
|
||||||
while i <= #line do
|
|
||||||
local char = line:sub(i, i)
|
|
||||||
|
|
||||||
-- Track string boundaries
|
|
||||||
if (char == '"' or char == "'") and (i == 1 or line:sub(i-1, i-1) ~= "\\") then
|
|
||||||
if not in_string then
|
|
||||||
in_string = true
|
|
||||||
quote_char = char
|
|
||||||
elseif char == quote_char then
|
|
||||||
in_string = false
|
|
||||||
quote_char = nil
|
|
||||||
end
|
|
||||||
result = result .. char
|
|
||||||
i = i + 1
|
|
||||||
elseif char == "`" and not in_string then
|
|
||||||
-- Found backtick outside of string, find the closing backtick
|
|
||||||
local end_pos = line:find("`", i + 1)
|
|
||||||
if end_pos then
|
|
||||||
local command = line:sub(i + 1, end_pos - 1)
|
|
||||||
|
|
||||||
-- Handle variable interpolation with #{var} syntax
|
|
||||||
if command:find("#{") then
|
|
||||||
-- Escape existing quotes first, then do interpolation
|
|
||||||
local escaped_cmd = command:gsub('"', '\\"')
|
|
||||||
local interpolated_cmd = escaped_cmd:gsub("#{([%w_.]+)}", '" .. tostring(%1) .. "')
|
|
||||||
result = result .. 'shell("' .. interpolated_cmd .. '")'
|
|
||||||
else
|
|
||||||
result = result .. 'shell("' .. command:gsub('"', '\\"') .. '")'
|
|
||||||
end
|
|
||||||
i = end_pos + 1
|
|
||||||
else
|
|
||||||
result = result .. char
|
|
||||||
i = i + 1
|
|
||||||
end
|
|
||||||
else
|
|
||||||
result = result .. char
|
|
||||||
i = i + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
table.insert(lines, result)
|
|
||||||
end
|
|
||||||
|
|
||||||
return table.concat(lines, "\n")
|
|
||||||
end
|
|
||||||
|
|
||||||
local function process_interactive_commands(code)
|
|
||||||
local lines = {}
|
|
||||||
for line in code:gmatch("([^\r\n]*)") do
|
|
||||||
-- Only match ! at the beginning of the line (ignoring whitespace)
|
|
||||||
local command = line:match("^%s*!(.*)")
|
|
||||||
if command then
|
|
||||||
local indent = line:match("^(%s*)")
|
|
||||||
table.insert(lines, indent .. 'run("' .. command:gsub('"', '\\"') .. '")')
|
|
||||||
else
|
|
||||||
table.insert(lines, line)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return table.concat(lines, "\n")
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Load injected helper functions (minimal)
|
|
||||||
local injected_lib_path = debug.getinfo(1, "S").source:match("@(.*/)") or "./"
|
|
||||||
local lib_file = io.open(injected_lib_path .. "__injected_lib.lua", "r")
|
|
||||||
local injected_lib = ""
|
|
||||||
if lib_file then
|
|
||||||
injected_lib = lib_file:read("*a")
|
|
||||||
lib_file:close()
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Apply preprocessing
|
|
||||||
source_code = process_env_vars(source_code)
|
|
||||||
source_code = process_interactive_commands(source_code)
|
|
||||||
source_code = process_shell_commands(source_code)
|
|
||||||
|
|
||||||
-- Combine and execute
|
|
||||||
local final_code = injected_lib .. "\n" .. source_code
|
|
||||||
|
|
||||||
-- Debug output
|
|
||||||
if os.getenv("LUASH_DEBUG") then
|
|
||||||
print("-- Generated Lua code:")
|
|
||||||
print(final_code)
|
|
||||||
print("-- End generated code")
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Execute
|
|
||||||
local func, err = load(final_code, "@" .. filename)
|
|
||||||
if not func then
|
|
||||||
print("--- SYNTAX ERROR in " .. filename .. " ---")
|
|
||||||
print(err)
|
|
||||||
else
|
|
||||||
local success, result = pcall(func)
|
|
||||||
if not success then
|
|
||||||
print("--- RUNTIME ERROR in " .. filename .. " ---")
|
|
||||||
print(result)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|||||||
21
new_proj.luash
Normal file
21
new_proj.luash
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/env luash
|
||||||
|
|
||||||
|
-- Environment variables (clean syntax)
|
||||||
|
print("Hello " .. $USER .. "!")
|
||||||
|
|
||||||
|
-- Variable assignment
|
||||||
|
$MY_VAR = "some value"
|
||||||
|
|
||||||
|
-- Shell commands with backticks
|
||||||
|
files = `ls -la`
|
||||||
|
current_dir = `pwd`
|
||||||
|
|
||||||
|
-- Interactive commands
|
||||||
|
!ls
|
||||||
|
|
||||||
|
-- The power of Lua + shell
|
||||||
|
if `test -f README.md` ~= "" then
|
||||||
|
lines = `wc -l < README.md`
|
||||||
|
print("README has " .. lines .. " lines")
|
||||||
|
end
|
||||||
|
|
||||||
24
src/injected.lua
Normal file
24
src/injected.lua
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
local M = {}
|
||||||
|
|
||||||
|
function M.load()
|
||||||
|
local base = debug.getinfo(1, "S").source:match("@(.*/)") or "./"
|
||||||
|
base = base:gsub("/src/?$", "/")
|
||||||
|
local lib_file = io.open(base .. "src/lib/injected_lib.lua", "r")
|
||||||
|
if lib_file then
|
||||||
|
local lib_code = lib_file:read("*a")
|
||||||
|
lib_file:close()
|
||||||
|
local lib_func, err = load(lib_code, "@__injected_lib.lua")
|
||||||
|
if lib_func then
|
||||||
|
local ok, res = pcall(lib_func)
|
||||||
|
if not ok then
|
||||||
|
error("Error loading injected lib: " .. tostring(res))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
error("Syntax error in injected lib: " .. tostring(err))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
|
|
||||||
|
|
||||||
55
src/main.lua
Normal file
55
src/main.lua
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
local runner = require('runner')
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
function M.run(argv)
|
||||||
|
local debug_mode = false -- legacy: maps to compile-highlight
|
||||||
|
local compile_mode = false
|
||||||
|
local highlight_mode = false
|
||||||
|
local filename = argv[1]
|
||||||
|
if filename == "-d" or filename == "--debug" then
|
||||||
|
debug_mode = true
|
||||||
|
highlight_mode = true
|
||||||
|
compile_mode = false
|
||||||
|
filename = argv[2]
|
||||||
|
elseif filename == "-c" or filename == "--compile" then
|
||||||
|
compile_mode = true
|
||||||
|
highlight_mode = false
|
||||||
|
filename = argv[2]
|
||||||
|
elseif filename == "-C" or filename == "--compile-highlight" then
|
||||||
|
compile_mode = true
|
||||||
|
highlight_mode = true
|
||||||
|
filename = argv[2]
|
||||||
|
end
|
||||||
|
if filename == "-h" or filename == "--help" then
|
||||||
|
print("Usage: luash <script.luash>")
|
||||||
|
print(" luash -i # Interactive mode")
|
||||||
|
print(" luash -c <file> # COMPILE MODE: print Lua and exit (no colors)")
|
||||||
|
print(" luash -C <file> # COMPILE MODE: print Lua with highlighting and exit")
|
||||||
|
print(" luash -d <file> # Print generated Lua (highlighted) then run")
|
||||||
|
print(" luash -h # Show this help")
|
||||||
|
return
|
||||||
|
elseif not filename or filename == "-i" or filename == "--interactive" then
|
||||||
|
local entry = require('repl')
|
||||||
|
local preprocess = require('preprocess')
|
||||||
|
local injected = require('injected')
|
||||||
|
local function preprocess_luash_repl(code)
|
||||||
|
code = preprocess.remove_shebang_preserve_lines(code)
|
||||||
|
code = preprocess.process_env_vars(code)
|
||||||
|
code = preprocess.process_interactive_commands(code)
|
||||||
|
code = preprocess.process_shell_commands(code)
|
||||||
|
return code
|
||||||
|
end
|
||||||
|
local function load_injected_lib()
|
||||||
|
injected.load()
|
||||||
|
end
|
||||||
|
entry.start(preprocess_luash_repl, load_injected_lib)
|
||||||
|
return
|
||||||
|
else
|
||||||
|
runner.run_file(filename, { debug = debug_mode, compile = compile_mode, highlight = highlight_mode })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
|
|
||||||
|
|
||||||
120
src/preprocess.lua
Normal file
120
src/preprocess.lua
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
local M = {}
|
||||||
|
|
||||||
|
function M.remove_shebang_preserve_lines(source_code)
|
||||||
|
local has_shebang = source_code:match("^#!")
|
||||||
|
source_code = source_code:gsub("^#![^\r\n]*[\r\n]?", "")
|
||||||
|
if has_shebang then
|
||||||
|
source_code = "\n" .. source_code
|
||||||
|
end
|
||||||
|
return source_code
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.process_env_vars(code)
|
||||||
|
local lines = {}
|
||||||
|
for line in code:gmatch("([^\r\n]*)") do
|
||||||
|
if not line:match("^%s*%-%-") then
|
||||||
|
if line:match("^%$([%w_]+)%s*=") then
|
||||||
|
line = line:gsub("^%$([%w_]+)%s*=%s*(.+)", function(var, value)
|
||||||
|
return "env_set('" .. var .. "', " .. value .. ")"
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
local in_string = false
|
||||||
|
local quote_char = nil
|
||||||
|
local result = ""
|
||||||
|
local i = 1
|
||||||
|
while i <= #line do
|
||||||
|
local char = line:sub(i, i)
|
||||||
|
if (char == '"' or char == "'") and (i == 1 or line:sub(i-1, i-1) ~= "\\") then
|
||||||
|
if not in_string then
|
||||||
|
in_string = true
|
||||||
|
quote_char = char
|
||||||
|
elseif char == quote_char then
|
||||||
|
in_string = false
|
||||||
|
quote_char = nil
|
||||||
|
end
|
||||||
|
result = result .. char
|
||||||
|
i = i + 1
|
||||||
|
elseif char == "$" and not in_string then
|
||||||
|
local var_match = line:sub(i):match("^%$([%w_]+)")
|
||||||
|
if var_match then
|
||||||
|
result = result .. "env_get('" .. var_match .. "')"
|
||||||
|
i = i + #var_match + 1
|
||||||
|
else
|
||||||
|
result = result .. char
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
else
|
||||||
|
result = result .. char
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
line = result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.insert(lines, line)
|
||||||
|
end
|
||||||
|
return table.concat(lines, "\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.process_interactive_commands(code)
|
||||||
|
local lines = {}
|
||||||
|
for line in code:gmatch("([^\r\n]*)") do
|
||||||
|
local command = line:match("^%s*!(.*)")
|
||||||
|
if command then
|
||||||
|
local indent = line:match("^(%s*)")
|
||||||
|
table.insert(lines, indent .. 'run("' .. command:gsub('"', '\\"') .. '")')
|
||||||
|
else
|
||||||
|
table.insert(lines, line)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return table.concat(lines, "\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.process_shell_commands(code)
|
||||||
|
local lines = {}
|
||||||
|
for line in code:gmatch("([^\r\n]*)") do
|
||||||
|
local in_string = false
|
||||||
|
local quote_char = nil
|
||||||
|
local result = ""
|
||||||
|
local i = 1
|
||||||
|
while i <= #line do
|
||||||
|
local char = line:sub(i, i)
|
||||||
|
if (char == '"' or char == "'") and (i == 1 or line:sub(i-1, i-1) ~= "\\") then
|
||||||
|
if not in_string then
|
||||||
|
in_string = true
|
||||||
|
quote_char = char
|
||||||
|
elseif char == quote_char then
|
||||||
|
in_string = false
|
||||||
|
quote_char = nil
|
||||||
|
end
|
||||||
|
result = result .. char
|
||||||
|
i = i + 1
|
||||||
|
elseif char == "`" and not in_string then
|
||||||
|
local end_pos = line:find("`", i + 1)
|
||||||
|
if end_pos then
|
||||||
|
local command = line:sub(i + 1, end_pos - 1)
|
||||||
|
if command:find("#{") then
|
||||||
|
local escaped_cmd = command:gsub('"', '\\"')
|
||||||
|
local interpolated_cmd = escaped_cmd:gsub("#{([%w_.]+)}", '" .. tostring(%1) .. "')
|
||||||
|
result = result .. 'shell("' .. interpolated_cmd .. '")'
|
||||||
|
else
|
||||||
|
result = result .. 'shell("' .. command:gsub('"', '\\"') .. '")'
|
||||||
|
end
|
||||||
|
i = end_pos + 1
|
||||||
|
else
|
||||||
|
result = result .. char
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
else
|
||||||
|
result = result .. char
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.insert(lines, result)
|
||||||
|
end
|
||||||
|
return table.concat(lines, "\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
|
|
||||||
|
|
||||||
133
src/repl.lua
Normal file
133
src/repl.lua
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
local M = {}
|
||||||
|
|
||||||
|
function M.start(preprocess_fn, load_injected_lib)
|
||||||
|
load_injected_lib()
|
||||||
|
|
||||||
|
local util = require('util')
|
||||||
|
local function detect_tty() return util.is_tty() end
|
||||||
|
local env_raw = os.getenv("LUASH_RAW")
|
||||||
|
local raw_mode_enabled = (env_raw == "1") or (env_raw == nil and detect_tty())
|
||||||
|
|
||||||
|
local repl = { buffer = "", history = {}, history_index = 0 }
|
||||||
|
|
||||||
|
local function set_raw_mode(enable) util.set_raw_mode(enable) end
|
||||||
|
|
||||||
|
local function trim(s) return util.trim(s) end
|
||||||
|
|
||||||
|
local function raw_read_line(prompt)
|
||||||
|
io.write(prompt); io.flush()
|
||||||
|
local buf = {}
|
||||||
|
while true do
|
||||||
|
local ch = io.read(1)
|
||||||
|
if not ch then return nil end
|
||||||
|
local b = string.byte(ch)
|
||||||
|
if b == 10 or b == 13 then io.write("\n"); return table.concat(buf)
|
||||||
|
elseif b == 12 then -- Ctrl+L
|
||||||
|
repl.buffer = table.concat(buf)
|
||||||
|
io.write("\27[2J\27[H"); io.flush()
|
||||||
|
print("🚀 Luash Interactive Shell")
|
||||||
|
print("Type Lua expressions, use $VAR for env vars, `commands` for shell")
|
||||||
|
print("Special commands: .help .exit .clear .history (Ctrl+L to clear, Ctrl+D to exit)")
|
||||||
|
print("")
|
||||||
|
io.write(prompt); io.write(repl.buffer); io.flush()
|
||||||
|
elseif b == 3 then return "__CTRL_C__"
|
||||||
|
elseif b == 4 then return nil -- Ctrl+D as EOF
|
||||||
|
elseif b == 127 or b == 8 then if #buf > 0 then table.remove(buf); io.write("\b \b"); io.flush() end
|
||||||
|
else table.insert(buf, ch); io.write(ch); io.flush() end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
print("🚀 Luash Interactive Shell")
|
||||||
|
print("Type Lua expressions, use $VAR for env vars, `commands` for shell")
|
||||||
|
print("Special commands: .help .exit .clear .history (Ctrl+L to clear, Ctrl+D to exit)")
|
||||||
|
print("")
|
||||||
|
|
||||||
|
if raw_mode_enabled then set_raw_mode(true) end
|
||||||
|
local ok_loop, loop_err = pcall(function()
|
||||||
|
while true do
|
||||||
|
local prompt = repl.buffer == "" and "luash> " or " >> "
|
||||||
|
local line
|
||||||
|
if raw_mode_enabled then
|
||||||
|
line = raw_read_line(prompt)
|
||||||
|
if line == "__CTRL_C__" then
|
||||||
|
io.write("\r\27[K")
|
||||||
|
goto continue
|
||||||
|
end
|
||||||
|
else
|
||||||
|
io.write(prompt); io.flush()
|
||||||
|
local ok, l = pcall(io.read, "*line")
|
||||||
|
if not ok then print("\nInterrupted (Ctrl+C)"); break end
|
||||||
|
line = l
|
||||||
|
end
|
||||||
|
if not line then print("\nGoodbye!"); break end
|
||||||
|
|
||||||
|
if (not raw_mode_enabled) and line:find(string.char(12), 1, true) then
|
||||||
|
repl.buffer = ""; io.write("\27[2J\27[H"); io.flush()
|
||||||
|
print("🚀 Luash Interactive Shell")
|
||||||
|
print("Type Lua expressions, use $VAR for env vars, `commands` for shell")
|
||||||
|
print("Special commands: .help .exit .clear .history (Ctrl+L to clear, Ctrl+D to exit)")
|
||||||
|
print(""); goto continue
|
||||||
|
end
|
||||||
|
|
||||||
|
line = trim(line)
|
||||||
|
if line == ".exit" or line == ".quit" then break
|
||||||
|
elseif line == ".clear" then repl.buffer = ""; print("Buffer cleared")
|
||||||
|
elseif line == ".history" then for i, cmd in ipairs(repl.history) do print(string.format("%3d: %s", i, cmd)) end
|
||||||
|
elseif line == ".help" then print([[Luash Interactive Shell Help:
|
||||||
|
|
||||||
|
Luash Features:
|
||||||
|
$VAR - Access environment variables
|
||||||
|
$VAR = "value" - Set environment variables
|
||||||
|
`command` - Execute shell commands
|
||||||
|
#{var} - Variable interpolation in commands
|
||||||
|
!command - Interactive shell commands
|
||||||
|
|
||||||
|
Special Commands:
|
||||||
|
.help - Show this help
|
||||||
|
.exit, .quit - Exit the shell
|
||||||
|
.clear - Clear input buffer
|
||||||
|
.history - Show command history
|
||||||
|
|
||||||
|
Multi-line input supported for functions, loops, etc.]])
|
||||||
|
else
|
||||||
|
if repl.buffer ~= "" then repl.buffer = repl.buffer .. "\n" .. line else repl.buffer = line end
|
||||||
|
|
||||||
|
if line ~= "" then
|
||||||
|
local processed = preprocess_fn(repl.buffer)
|
||||||
|
local func, err = load("return " .. processed, "@repl")
|
||||||
|
if not func then
|
||||||
|
if err and err:match("<eof>") then
|
||||||
|
-- incomplete
|
||||||
|
else
|
||||||
|
func, err = load(processed, "@repl")
|
||||||
|
if not func then
|
||||||
|
if err and err:match("<eof>") then
|
||||||
|
-- incomplete
|
||||||
|
else
|
||||||
|
print("Syntax error: " .. err); repl.buffer = ""
|
||||||
|
end
|
||||||
|
else
|
||||||
|
table.insert(repl.history, repl.buffer)
|
||||||
|
local success, result = pcall(func)
|
||||||
|
if success then if result ~= nil then print(tostring(result)) end else print("Error: " .. tostring(result)) end
|
||||||
|
repl.buffer = ""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
table.insert(repl.history, repl.buffer)
|
||||||
|
local success, result = pcall(func)
|
||||||
|
if success then if result ~= nil then print(tostring(result)) end else print("Error: " .. tostring(result)) end
|
||||||
|
repl.buffer = ""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
::continue::
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
if raw_mode_enabled then set_raw_mode(false) end
|
||||||
|
if not ok_loop then if raw_mode_enabled then set_raw_mode(false) end; print("\nREPL error: " .. tostring(loop_err)) end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
|
|
||||||
|
|
||||||
96
src/runner.lua
Normal file
96
src/runner.lua
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
local preprocess = require('preprocess')
|
||||||
|
local injected = require('injected')
|
||||||
|
local util = require('util')
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
function M.run_file(filename, opts)
|
||||||
|
local file = io.open(filename, "r")
|
||||||
|
if not file then
|
||||||
|
print("Error: Cannot open file '" .. filename .. "'")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local source_code = file:read("*a")
|
||||||
|
file:close()
|
||||||
|
|
||||||
|
source_code = preprocess.remove_shebang_preserve_lines(source_code)
|
||||||
|
source_code = preprocess.process_env_vars(source_code)
|
||||||
|
source_code = preprocess.process_interactive_commands(source_code)
|
||||||
|
source_code = preprocess.process_shell_commands(source_code)
|
||||||
|
|
||||||
|
-- Load injected helpers into global scope (not prepended to source)
|
||||||
|
injected.load()
|
||||||
|
|
||||||
|
-- Optional compile output of generated Lua
|
||||||
|
local dbg_env = os.getenv("LUASH_DEBUG")
|
||||||
|
local dbg_flag = opts and opts.debug == true
|
||||||
|
local compile_flag = opts and opts.compile == true
|
||||||
|
local highlight_flag = opts and opts.highlight == true
|
||||||
|
if dbg_flag or compile_flag or (dbg_env == "1" or dbg_env == "true" or dbg_env == "yes") then
|
||||||
|
-- If env var triggered, prefer highlighting
|
||||||
|
if not highlight_flag and (dbg_flag or (dbg_env == "1" or dbg_env == "true" or dbg_env == "yes")) then
|
||||||
|
highlight_flag = true
|
||||||
|
end
|
||||||
|
-- Try to read injected library source for holistic view
|
||||||
|
local injected_src = nil
|
||||||
|
do
|
||||||
|
local base = debug.getinfo(1, "S").source:match("@(.*/)") or "./"
|
||||||
|
base = base:gsub("/src/?$", "/")
|
||||||
|
local lib_path = base .. "src/lib/injected_lib.lua"
|
||||||
|
local f = io.open(lib_path, "r")
|
||||||
|
if f then
|
||||||
|
injected_src = f:read("*a")
|
||||||
|
f:close()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if injected_src and injected_src ~= "" then
|
||||||
|
print("--- Luash COMPILE MODE: injected library ---")
|
||||||
|
if highlight_flag then
|
||||||
|
local highlighted_inj = util.highlight_lua_ansi(injected_src)
|
||||||
|
if highlighted_inj then
|
||||||
|
io.write(highlighted_inj)
|
||||||
|
if not highlighted_inj:match("\n$") then io.write("\n") end
|
||||||
|
else
|
||||||
|
print(injected_src)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
print(injected_src)
|
||||||
|
end
|
||||||
|
print("--- end injected library ---")
|
||||||
|
end
|
||||||
|
|
||||||
|
print("--- Luash COMPILE MODE: generated Lua (" .. filename .. ") ---")
|
||||||
|
if highlight_flag then
|
||||||
|
local highlighted = util.highlight_lua_ansi(source_code)
|
||||||
|
if highlighted then
|
||||||
|
io.write(highlighted)
|
||||||
|
if not highlighted:match("\n$") then io.write("\n") end
|
||||||
|
else
|
||||||
|
print(source_code)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
print(source_code)
|
||||||
|
end
|
||||||
|
print("--- end generated Lua ---")
|
||||||
|
|
||||||
|
-- Only skip execution in explicit compile mode
|
||||||
|
if compile_flag then return end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Execute
|
||||||
|
local func, err = load(source_code, "@" .. filename)
|
||||||
|
if not func then
|
||||||
|
print("--- SYNTAX ERROR in " .. filename .. " ---")
|
||||||
|
print(err)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local success, result = pcall(func)
|
||||||
|
if not success then
|
||||||
|
print("--- RUNTIME ERROR in " .. filename .. " ---")
|
||||||
|
print(result)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
|
|
||||||
|
|
||||||
55
src/util.lua
Normal file
55
src/util.lua
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
local M = {}
|
||||||
|
|
||||||
|
function M.trim(s)
|
||||||
|
return s:match("^%s*(.-)%s*$")
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.is_tty()
|
||||||
|
local r = os.execute("test -t 0 >/dev/null 2>&1")
|
||||||
|
if type(r) == "number" then return r == 0 end
|
||||||
|
return r == true
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.is_stdout_tty()
|
||||||
|
local r = os.execute("test -t 1 >/dev/null 2>&1")
|
||||||
|
if type(r) == "number" then return r == 0 end
|
||||||
|
return r == true
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.set_raw_mode(enable)
|
||||||
|
if enable then
|
||||||
|
os.execute("stty -echo -icanon -isig min 1 time 0")
|
||||||
|
else
|
||||||
|
os.execute("stty sane")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.has_command(cmd)
|
||||||
|
local r = os.execute("command -v " .. cmd .. " >/dev/null 2>&1")
|
||||||
|
if type(r) == "number" then return r == 0 end
|
||||||
|
return r == true
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.highlight_lua_ansi(code)
|
||||||
|
if not M.has_command("highlight") then return nil end
|
||||||
|
local tmpin = os.tmpname()
|
||||||
|
local f = io.open(tmpin, "w")
|
||||||
|
if not f then return nil end
|
||||||
|
f:write(code)
|
||||||
|
f:close()
|
||||||
|
local handle = io.popen("highlight -O ansi -S lua " .. tmpin .. " 2>/dev/null")
|
||||||
|
if not handle then os.remove(tmpin); return nil end
|
||||||
|
local out = handle:read("*a") or ""
|
||||||
|
handle:close()
|
||||||
|
os.remove(tmpin)
|
||||||
|
if out == "" then return nil end
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.script_dir()
|
||||||
|
return (debug.getinfo(2, "S").source:match("@(.*/)") or "./")
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user