diff --git a/README.md b/README.md index 602299f..48e5c84 100644 --- a/README.md +++ b/README.md @@ -40,11 +40,19 @@ Enter the repo: Inside, do the tests: - sh scripts/test.sh + pandoc lua scripts/test.lua For other kind of tests, do: - sh scripts/test.sh -h + pandoc lua scripts/test.lua -h + +For other scripts, do: + + pandoc lua scripts/SCRIPT + +For example, do: + + pandoc lua scripts/make_dist.lua Contribute! diff --git a/dist/lin.lua b/dist/lin.lua index d77d203..e5311ad 100644 --- a/dist/lin.lua +++ b/dist/lin.lua @@ -6258,6 +6258,105 @@ do package.preload[module_name] = nil end local fnl = mod + +------------------------------------ MODS ------------------------------------ + +-- Tries popen +-- @param ... string: Chunks for popen +-- @return boolean, string: Status and output of popen +function io.try(...) + local cmd = table.concat({...}, " ") .. " 2>&1" + local handle = io.popen(cmd) + local output = handle:read("*a") + local status = (handle:close() ~= nil and true or false) + return status, output +end + +-- Gets OS short name +-- It is just intented to know if it is Linux, macOS or Windows. +-- @return string: "linux" or "macos" or "windows" +function os.uname() + local status, output = io.try("uname") + if status then + output = output:gsub(" .*", ""):lower() + if output ~= "linux" then + return "macos" + end + return "linux" + end + return "windows" +end + +-- Checks if OS is windows +-- @return boolean: Windows or not +function os.iswin() + return os.uname() == "windows" +end + +-- Checks if OS is Unix +-- @return boolean: Unix or not +function os.isunix() + return os.uname() ~= "windows" +end + +-- Changes newlines so everything is in one line +-- @return string: String with formatted newlines as literal "\n" +function string:linearize() + return self:gsub("\n", "\\\\n") +end + +-- Checks if string is empty +-- @return boolean: Empty or not +function string:isempty() + return self == '' +end + +function string:lstrip() + return self:gsub("^%s+", "") +end + +function string:rstrip() + return self:gsub("%s+$", "") +end + +function string:strip() + return self:lstrip():rstrip() +end + +-- The following are heavily influenced by Python pathlib +-- Check: https://docs.python.org/3/library/pathlib.html + +-- Checks if string is a file or directory +-- @return boolean: Exists or not +function string:exists() + return os.rename(self, self) ~= nil +end + +-- Checks if string is a file +-- @return boolean: File or not +function string:isfile() + if self:exists() then + return io.open(self, "a+") ~= nil + end + return false +end + +-- Checks if string is a directory +-- @return boolean: Directory or not +function string:isdir() + if self:exists() then + return io.open(self, "a+") == nil + end + return false +end + +-- Reads file content +-- @return string or nil: File content or nil +function string:read_text() + if self:exists() then + return io.open(self):read("*a") + end +end ----------------------------------- NATURAL ----------------------------------- local nat = {} @@ -6265,29 +6364,8 @@ local nat = {} function nat.get(str) return str end - ----------------------------------- LITERATE ---------------------------------- --- Types functions extensions - --- Changes newlines so everything is in one line -function string:linearize() - return self:gsub("\n", "\\\\n") -end - --- Checks if is empty -function string:isempty() - return self == '' -end - --- Checks if is directory -function string:isdir() - if self ~= "." and os.rename(self, self) == nil then - return false - end - return true -end - -- Variable for all literate stuff local lit = {} @@ -6331,6 +6409,7 @@ function lit.checkmetatype(type, meta, key) local err = "" if type ~= mtype then if type == "path" then + -- TODO if not(pandoc.path.directory(mval):isdir()) then err = "Invalid path '" .. mval .. "' in key '" .. key .. "'" end @@ -6448,7 +6527,6 @@ function lit.puts(msg, kind) end function lit.parseyaml(rawyaml) - -- TODO: avoid popen? local yaml = io.popen("pandoc -t json <<<\"" .. rawyaml .. "\" 2>&1") if yaml:read("*l"):sub(1, 1) == "{" then yaml = pandoc.read(rawyaml).meta diff --git a/scripts/make_dist.lua b/scripts/make_dist.lua new file mode 100644 index 0000000..2d7fed0 --- /dev/null +++ b/scripts/make_dist.lua @@ -0,0 +1,45 @@ +-- Adds Lua custom extensions +require "../src/extensions" + +function make_dist() + -- Variables + local name = "lin.lua" + local dist = pandoc.path.join({"dist", name}) + local license = string.strip([[ +Computable Pandoc & Fennel Bundle: + A Pandoc filter for literate and natural programming +Computable Pandoc: + (C) 2023 perro hi@perrotuerto.blog + License: GPLv3 https://git.cuates.net/perro/computable-pandoc/src/branch/no-masters/LICENSE.txt + Source: https://git.cuates.net/perro/computable-pandoc +Fennel: + (C) 2016-2023 Calvin Rose and contributors + License: MIT License https://git.sr.ht/~technomancy/fennel/tree/main/item/LICENSE + Source: https://sr.ht/~technomancy/fennel or https://github.com/bakpakin/Fennel/issues + Website: https://fennel-lang.org + ]]) + + function chomp(str, without) + local without = without or false + str = string.read_text(str):strip() + str = str:gsub('\n+--[^\n]+\nrequire "extensions"', "") + if without then + return str:gsub("\n[^\n]+$", "") + end + return "\n" .. str .. "\n" + end + + local ext = chomp("src/extensions.lua") + local pan = chomp("src/pandoc.lua") + local fnl = chomp("opt/fennel.lua", true) + local nat = chomp("src/natural.lua", true) + local lit = chomp("src/literate.lua", true) + + -- Bundles Fennel and Computable Pandoc + file = io.open(dist, "w") + file:write("--[[\n", license, "\n]]--\n") + file:write(fnl, "\nlocal fnl = mod\n", ext, nat, lit, pan) + file:close() +end + +make_dist() diff --git a/scripts/make_dist.sh b/scripts/make_dist.sh deleted file mode 100644 index 8686496..0000000 --- a/scripts/make_dist.sh +++ /dev/null @@ -1,26 +0,0 @@ -# Makes distribution release - -# Variables -NAME="lin.lua" -DIST=dist/$NAME -LICENSE="--[[ -Computable Pandoc & Fennel Bundle: - A Pandoc filter for literate and natural programming -Computable Pandoc: - (C) 2023 perro hi@perrotuerto.blog - License: GPLv3 https://git.cuates.net/perro/computable-pandoc/src/branch/no-masters/LICENSE.txt - Source: https://git.cuates.net/perro/computable-pandoc -Fennel: - (C) 2016-2023 Calvin Rose and contributors - License: MIT License https://git.sr.ht/~technomancy/fennel/tree/main/item/LICENSE - Source: https://sr.ht/~technomancy/fennel or https://github.com/bakpakin/Fennel/issues - Website: https://fennel-lang.org -]]--" - -# Bundles Fennel and computable Pandoc -(echo "$LICENSE") > $DIST -head -n -1 opt/fennel.lua >> $DIST -echo "local fnl = mod" >> $DIST -head -n -1 src/natural.lua >> $DIST -head -n -1 src/literate.lua >> $DIST -cat src/pandoc.lua >> $DIST diff --git a/scripts/test.lua b/scripts/test.lua new file mode 100644 index 0000000..2f975cc --- /dev/null +++ b/scripts/test.lua @@ -0,0 +1,108 @@ +-- Adds Lua custom extensions +require "../src/extensions" +require "../scripts/make_dist" + +-- Variables +local filter = "-L dist/lin.lua" +local verbose = "" +local trace = "" +local files = {} +local cmds = { + ["unix"] = { + ["pandoc"] = "pandoc ", + ["clear"] = "clear ", + ["ls"] = "ls ", + }, + ["win"] = { + ["pandoc"] = "pandoc.exe ", + ["clear"] = "cls ", + ["ls"] = "dir ", + }, +} + +-- Prints help +local function help() + local cmd = " pandoc lua script/test.lua" + print(table.concat({ + "Usage:", + cmd .. " [OPTIONS] [FILES]", + "Options", + " -V --verbose Prints verbose debbuging output", + " -t --trace Prints diagnostic output tracing parser progress", + " -h --help Prints this help", + "Examples:", + cmd .. " Tests for 'tests/*.*", + cmd .. " -V Verbose tests for 'tests/*.*'", + cmd .. " -t Tests and tracing for 'tests/*.*'", + cmd .. " tests/fail* Tests for 'tests/fail*'", + cmd .. " -Vt tests/fail* Verbose tests and tracing for 'tests/fail*'", + cmd .. " -h Display this help", + }, "\n")) + os.exit() +end + +-- Gets command according to OS +local function getcmd(str) + local system = (os.isunix() and "unix" or "win") + return cmds[system][str] +end + +-- Obtains result as "pass" | "fail" | "diff" (AST doesn't match) +local function getresult(file, f, v, t) + local tmp, name = "tmp.json", pandoc.path.filename(file) + local json = pandoc.path.join({"tests", "asts", name .. ".json"}) + local ok, out = io.try(getcmd("pandoc"), f, v, t, "-t json", "-o", tmp, file) + local json1, json2 = tmp:read_text(), json:read_text() + os.remove("tmp.json") + if (ok and json2 == nil) or (ok and json1 == json2) then + return "pass", out:strip() + elseif ok and json1 ~= json2 then + return "diff", out:strip() + else + return "fail", out:strip() + end +end + +-- Parses args +for _, a in ipairs(arg) do + if a:match("^-.*h.*") then + help() + elseif a == "-V" or a == "--verbose" then + verbose = "--verbose" + elseif a == "-t" or a == "--trace" then + trace = "--trace" + elseif a == "-Vt" or a == "-tV" then + verbose = "--verbose" + trace = "--trace" + elseif a:match("^-") == nil then + table.insert(files, pandoc.path.normalize(a)) + end +end + +-- Default files +if #files == 0 then + for file in io.popen(getcmd("ls") .. "tests"):lines() do + if file ~= "asts" then + table.insert(files, pandoc.path.join({"tests", file})) + end + end +end + +-- Makes distribution bundle +make_dist() + +-- Clears terminal +os.execute(getcmd("clear")) +print "🐾 Starting tests" + +-- Does tests +for _, file in ipairs(files) do + local expectation = pandoc.path.filename(file):gsub("%W.+$", "") + local result, output = getresult(file, filter, verbose, trace) + print("⚗️ " .. file .. ":") + print(" Expect: " .. expectation) + print(" Result: " .. result) + if #verbose > 0 or #trace > 0 then + print(output) + end +end diff --git a/scripts/test.sh b/scripts/test.sh deleted file mode 100644 index 85a1187..0000000 --- a/scripts/test.sh +++ /dev/null @@ -1,71 +0,0 @@ -# Makes tests - -# Variables -FILTER=dist/lin.lua -FILES="tests/*.md" -VERBOSE="" -AST=false -CMD="sh $0" - -# Prints help -echo_help () { - echo "Usage:" - echo " $CMD [-vah] [FILES]" - echo "Examples:" - echo " $CMD Tests for 'tests/*.md'" - echo " $CMD -v Verbose tests for 'tests/*.md'" - echo " $CMD -a Tests and AST for 'tests/*.md'" - echo " $CMD tests/fail* Tests for 'tests/fail*'" - echo " $CMD -va tests/fail* Verbose tests and AST for 'tests/fail*'" - echo " $CMD -h Display this help" - exit -} - -# Obtains result as "pass" | "fail" | "diff" (AST doesn't match) -get_result () { - result=$([[ $1 -ne 0 ]] && echo "fail" || echo "pass") - if [ -f $2.json ]; then - diff1=$(cat tmp.json | jq) - diff2=$(cat $2.json | jq) - difference=$(diff <( printf '%s\n' "$diff1" ) <( printf '%s\n' "$diff2" )) - result=$([[ $? -eq 0 ]] && echo "pass" || echo "diff") - fi - echo $result -} - -# Checks options -while getopts ':vah' opt; do - case "$opt" in - v) VERBOSE="--verbose" ; shift ;; - a) AST=true ; shift ;; - h) echo_help ;; - ?) echo "ERROR: only -v, -a or -h is allowed" ; exit 1 ;; - esac -done - -# Checks for files -if [ $# -ne 0 ]; then - for file in $*; do - if [ ! -f $file ]; then - echo "ERROR: '$file' is not a file; do '$CMD -h' for more info" ; exit 1 - fi - done - FILES=$* -fi - -# Makes distribution bundle -sh scripts/make_dist.sh - -# Does tests -clear && echo "🐾 Starting tests" -for file in $FILES; do - echo "⚗️ $file:" - expectation=${file:6:4} - ast=$(pandoc $VERBOSE -L $FILTER -t json -o tmp.json $file) - result=$(get_result $? $file) - echo " Expect: $expectation" - echo " Result: $result" - [ "$VERBOSE" = "--verbose" ] && echo -e "$ast" - [ "$AST" = true ] && pandoc -L $FILTER -t native $file - rm tmp.json -done diff --git a/src/extensions.lua b/src/extensions.lua new file mode 100644 index 0000000..08fbee9 --- /dev/null +++ b/src/extensions.lua @@ -0,0 +1,98 @@ +------------------------------------ MODS ------------------------------------ + +-- Tries popen +-- @param ... string: Chunks for popen +-- @return boolean, string: Status and output of popen +function io.try(...) + local cmd = table.concat({...}, " ") .. " 2>&1" + local handle = io.popen(cmd) + local output = handle:read("*a") + local status = (handle:close() ~= nil and true or false) + return status, output +end + +-- Gets OS short name +-- It is just intented to know if it is Linux, macOS or Windows. +-- @return string: "linux" or "macos" or "windows" +function os.uname() + local status, output = io.try("uname") + if status then + output = output:gsub(" .*", ""):lower() + if output ~= "linux" then + return "macos" + end + return "linux" + end + return "windows" +end + +-- Checks if OS is windows +-- @return boolean: Windows or not +function os.iswin() + return os.uname() == "windows" +end + +-- Checks if OS is Unix +-- @return boolean: Unix or not +function os.isunix() + return os.uname() ~= "windows" +end + +-- Changes newlines so everything is in one line +-- @return string: String with formatted newlines as literal "\n" +function string:linearize() + return self:gsub("\n", "\\\\n") +end + +-- Checks if string is empty +-- @return boolean: Empty or not +function string:isempty() + return self == '' +end + +function string:lstrip() + return self:gsub("^%s+", "") +end + +function string:rstrip() + return self:gsub("%s+$", "") +end + +function string:strip() + return self:lstrip():rstrip() +end + +-- The following are heavily influenced by Python pathlib +-- Check: https://docs.python.org/3/library/pathlib.html + +-- Checks if string is a file or directory +-- @return boolean: Exists or not +function string:exists() + return os.rename(self, self) ~= nil +end + +-- Checks if string is a file +-- @return boolean: File or not +function string:isfile() + if self:exists() then + return io.open(self, "a+") ~= nil + end + return false +end + +-- Checks if string is a directory +-- @return boolean: Directory or not +function string:isdir() + if self:exists() then + return io.open(self, "a+") == nil + end + return false +end + +-- Reads file content +-- @return string or nil: File content or nil +function string:read_text() + if self:exists() then + return io.open(self):read("*a") + end +end diff --git a/src/literate.lua b/src/literate.lua index 75ce241..c3bd8d4 100644 --- a/src/literate.lua +++ b/src/literate.lua @@ -1,24 +1,7 @@ ----------------------------------- LITERATE ---------------------------------- --- Types functions extensions - --- Changes newlines so everything is in one line -function string:linearize() - return self:gsub("\n", "\\\\n") -end - --- Checks if is empty -function string:isempty() - return self == '' -end - --- Checks if is directory -function string:isdir() - if self ~= "." and os.rename(self, self) == nil then - return false - end - return true -end +-- Adds Lua custom extensions +require "extensions" -- Variable for all literate stuff local lit = {} @@ -63,6 +46,7 @@ function lit.checkmetatype(type, meta, key) local err = "" if type ~= mtype then if type == "path" then + -- TODO if not(pandoc.path.directory(mval):isdir()) then err = "Invalid path '" .. mval .. "' in key '" .. key .. "'" end @@ -180,7 +164,6 @@ function lit.puts(msg, kind) end function lit.parseyaml(rawyaml) - -- TODO: avoid popen? local yaml = io.popen("pandoc -t json <<<\"" .. rawyaml .. "\" 2>&1") if yaml:read("*l"):sub(1, 1) == "{" then yaml = pandoc.read(rawyaml).meta diff --git a/src/natural.lua b/src/natural.lua index ace3905..57978a0 100644 --- a/src/natural.lua +++ b/src/natural.lua @@ -1,5 +1,8 @@ ----------------------------------- NATURAL ----------------------------------- +-- Adds Lua custom extensions +require "extensions" + local nat = {} function nat.get(str) diff --git a/tests/pass.lit.infos.md.json b/tests/asts/pass.lit.infos.md.json similarity index 100% rename from tests/pass.lit.infos.md.json rename to tests/asts/pass.lit.infos.md.json diff --git a/tests/pass.lit.warns.md.json b/tests/asts/pass.lit.warns.md.json similarity index 100% rename from tests/pass.lit.warns.md.json rename to tests/asts/pass.lit.warns.md.json