diff --git a/.github/workflows/minify.yml b/.github/workflows/minify.yml index a6b5cf9..7898a12 100644 --- a/.github/workflows/minify.yml +++ b/.github/workflows/minify.yml @@ -3,8 +3,6 @@ name: Minify Scripts on: push: branches: [ "main" ] - paths: - - src/** workflow_dispatch: @@ -22,18 +20,22 @@ jobs: lua-version: 5.3 install-luarocks: true + - name: Install Minifier + run: wget https://raw.githubusercontent.com/stravant/lua-minify/master/minify.lua + - name: Minify Scripts run: | rm -rf min mkdir min + touch min/movescript.lua lua minify.lua minify src/movescript.lua > min/movescript.lua - lua minify.lua minify src/itemscript.lua > min/itemscript.lua - name: Clean Up Repository run: | rm -rf .lua rm -rf .luarocks rm -rf .source + rm -f minify.lua - name: Create Pull Request uses: peter-evans/create-pull-request@v4 diff --git a/README.md b/README.md index 7e0a6d2..4cbb5b6 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,2 @@ -# Movescript +# movescript Simplified robot actions for Minecraft's ComputerCraft mod. - -For **installation, starter guides, examples, and reference documentation**, please refer to the [Movescript Documentation Pages](https://andrewlalis.github.io/movescript/). - -## Development - -All sources are developed under `src`, and automatic minified versions of them are added to the `min` directory. - -There are two main GitHub Actions that can run: - -1. On pushes to `main` with changes under `docs/`, the VuePress documentation site will be rebuilt and deployed to GitHub pages. -2. On pushes to `main` with changes under `src/`, all sources will be minified and an automated pull-request will be opened, to be manually merged by an administrator. diff --git a/docs/src/.vuepress/config.js b/docs/src/.vuepress/config.js index dae83da..c187fde 100755 --- a/docs/src/.vuepress/config.js +++ b/docs/src/.vuepress/config.js @@ -18,7 +18,7 @@ module.exports = { * ref:https://v1.vuepress.vuejs.org/config/#head */ head: [ - ['meta', { name: 'theme-color', content: '#de9502' }], + ['meta', { name: 'theme-color', content: '#3eaf7c' }], ['meta', { name: 'apple-mobile-web-app-capable', content: 'yes' }], ['meta', { name: 'apple-mobile-web-app-status-bar-style', content: 'black' }] ], @@ -56,19 +56,7 @@ module.exports = { children: [ '', 'spec', - 'settings', - 'reference' - ] - } - ], - '/guide/itemscript/': [ - { - title: 'Itemscript Module', - collapsable: false, - children: [ - '', - 'filters', - 'reference' + 'settings' ] } ] diff --git a/docs/src/.vuepress/styles/palette.styl b/docs/src/.vuepress/styles/palette.styl index 47cd36a..6490cb3 100755 --- a/docs/src/.vuepress/styles/palette.styl +++ b/docs/src/.vuepress/styles/palette.styl @@ -4,7 +4,7 @@ * ref:https://v1.vuepress.vuejs.org/zh/config/#palette-styl */ -$accentColor = #de9502 +$accentColor = #3eaf7c $textColor = #2c3e50 $borderColor = #eaecef -$codeBgColor = #233536 +$codeBgColor = #282c34 diff --git a/docs/src/guide/itemscript/README.md b/docs/src/guide/itemscript/README.md index 153e48d..ded1c7f 100644 --- a/docs/src/guide/itemscript/README.md +++ b/docs/src/guide/itemscript/README.md @@ -1,18 +1,3 @@ # Getting Started -The Itemscript module provides a flexible, powerful interface for managing a turtle's inventory, and any connected inventories. - -## Installing - -To install this module, run the following command from your turtle's console: - -```shell -wget https://raw.githubusercontent.com/andrewlalis/movescript/main/min/itemscript.lua -``` - -And then use it in a script: - -```lua -local is = require("itemscript") -print("Non-log items: " .. is.totalCount("!log")) -``` +> This page is a work-in-progress. \ No newline at end of file diff --git a/docs/src/guide/itemscript/filters.md b/docs/src/guide/itemscript/filters.md deleted file mode 100644 index afe43a8..0000000 --- a/docs/src/guide/itemscript/filters.md +++ /dev/null @@ -1,66 +0,0 @@ -# Filters - -Most of the functions provided by Itemscript make use of *filter expressions*. You'll often see it denoted in function parameters as `filterExpr`. A filter expression can be one of several things: - -- A function that takes an `item` (as obtained from `getItemDetail`) and returns a boolean `true` if the item matches, or `false` if it doesn't. -- A string or list of strings (see [filter expression strings](#filter-expression-strings)). - -## Filter Functions - -Filter functions **must** follow these rules to avoid errors or undefined behavior: - -- The function accepts a single `item` parameter, which may be `nil`, or a table like `{ name = "item_name", count = 32 }`. -- The function returns `true` if the given item should pass the filter or `false` if not. - -Below is a simple example of a filter function that only allows item stacks with more than 10 items. - -```lua -function myFilterFunction(item) - return item ~= nil and item.count > 10 -end -``` - -## Filter Expression Strings - -In the case of strings, a **filter expression string** is a string that can be used to match against an item with some advanced options. - -The most basic form of an expression string is just an item name, like `"minecraft:dirt"`, or `"create:train_door"`. Most normal items will begin with the `minecraft:` *namespace* prefix. If you don't include such a prefix, and you're not doing a [fuzzy match](#fuzzy-match), itemscript will add `minecraft:` for you. - -For example, we can count the number of stone items in our inventory like this: - -```lua -local is = require("itemscript") -print(is.totalCount("minecraft:stone")) -print(is.totalCount("stone")) -- "minecraft:" is added for us. -``` - -### Negation - -If `!` is added to the beginning of the string, only items that **don't** match will pass the filter. - -For example, suppose we want to drop everything except for oak planks: - -```lua -local is = require("itemscript") -is.dropAll("!oak_planks") -``` - -### Fuzzy Match - -If `#` is added to the beginning of the string, a *fuzzy* match will be performed, instead of a normal one. That is, instead of looking for an item whose name exactly matches, we look for the first item whose name we can find a given pattern in. In other words, normally when matching we check if `item.name == your_text`, and in a fuzzy match, we check if `string.find(item.name, your_text)` is not `nil`. - -For example, suppose we want to count the total number of logs of any type. This would be quite tedious to do normally, but with fuzzy matching, it's trivial: - -```lua -local is = require("itemscript") -print(is.totalCount("#log")) -``` - -Because a fuzzy match is nothing more than passing your text to Lua's `string.find` function, you can also take advantage of the more advanced *character classes* to define matching patterns. [Read about Lua's pattern matching documentation here.](https://www.lua.org/pil/20.2.html) - -In the example below, we filter to all items that begin with the text `minecraft:red_` by using the special `^` character. - -```lua -local is = require("itemscript") -print(is.totalCount("#^minecraft:red_")) -``` \ No newline at end of file diff --git a/docs/src/guide/itemscript/reference.md b/docs/src/guide/itemscript/reference.md deleted file mode 100644 index 0a1e0cb..0000000 --- a/docs/src/guide/itemscript/reference.md +++ /dev/null @@ -1,28 +0,0 @@ -# Module Reference - -The following is a complete reference of the **itemscript** module. All symbols defined here belong to the `itemscript` module, and can be accessed via an instance of that module. For example: - -```lua -local is = require("itemscript") -is.dropAll("stone") -``` - -## `totalCount(filterExpr)` - -Computes the total number of items matching the given [filter expression](./filters.md). - -## `select(filterExpr)` - -Selects the first inventory slot containing an item that matches the given [filter expression](./filters.md). Returns `true` if a slot was selected successfully, or `false` if no matching item could be found. - -## `dropAll(filterExpr)` - -Drops all items from the turtle's inventory matching the given [filter expression](./filters.md). - -## `dropAllDown(filterExpr)` - -Variant of [dropAll](#dropall-filterexpr) which drops items downward. - -## `dropAllUp(filterExpr)` - -Variant of [dropAll](#dropall-filterexpr) which drops items upward. \ No newline at end of file diff --git a/docs/src/guide/movescript/README.md b/docs/src/guide/movescript/README.md index 560fce4..54c9f20 100644 --- a/docs/src/guide/movescript/README.md +++ b/docs/src/guide/movescript/README.md @@ -7,7 +7,7 @@ The Movescript module provides a simple interface for executing *movescript sour To install this module, run the following command from your turtle's console: ```shell -wget https://raw.githubusercontent.com/andrewlalis/movescript/main/min/movescript.lua +wget https://raw.githubusercontent.com/andrewlalis/movescript/main/src/movescript.lua movescript.lua ``` And then use it in a script: diff --git a/docs/src/guide/movescript/reference.md b/docs/src/guide/movescript/reference.md deleted file mode 100644 index 4fccbd8..0000000 --- a/docs/src/guide/movescript/reference.md +++ /dev/null @@ -1,20 +0,0 @@ -# Module Reference - -The following is a complete reference of the **movescript** module. All symbols defined here belong to the `movescript` module, and can be accessed via an instance of that module. For example: - -```lua -local ms = require("movescript") -ms.run("2F") -``` - -## `run(script, settings)` - -Runs the given `script` string as a movescript, and optionally a `settings` table can be provided. Otherwise, [default settings](settings.md) will be used. - -## `runFile(filename, settings)` - -Reads content from the given filename and executes it as a script. Just like with `run`, an optional `settings` table can be provided. - -## `defaultSettings` - -A table containing the default settings for any script executed by the movescript module. \ No newline at end of file diff --git a/min/itemscript.lua b/min/itemscript.lua deleted file mode 100644 index 998ad87..0000000 --- a/min/itemscript.lua +++ /dev/null @@ -1 +0,0 @@ -a="0.0.1"local b=turtle local c={}local function d(k,l,m)return k~=nil and((not m and k.name==l)or string.find(k.name,l))end local function e(k)return function(l)return not k(l)end end local function f(k)return function(l)for m,n in pairs(k)do if not n(l)then return false end end return true end end local function g(k)return function(l)for m,n in pairs(k)do if n(l)then return true end end return false end end local function h(k)local l,m=string.find(k,"^[!#]+")local n=false local o=false if l~=nil then for q=l,m do local r=string.sub(k,q,q)if r=="!"then o=true elseif r=="#"then n=true end end k=string.sub(k,m+1,string.len(k))end local p=string.find(k,":")if p==nil and not n then k="minecraft:"..k end return function(q)if q==nil then return false end local r=d(q,k,n)if o then r=not r end return r end end local function i(k)if type(k)=="table"and#k>0 and type(k[1])=="string"then local l={}for m,n in pairs(k)do table.insert(l,h(n))end return g(l)elseif type(k)=="string"then return h(k)elseif type(k)=="function"then return k else error("Unsupported filter type: "..type(k))end end function c.totalCount(k)local l=i(k)local m=0 for n=1,16 do local o=b.getItemDetail(n)if l(o)then m=m+o.count end end return m end function c.select(k)local l=i(k)for m=1,16 do local n=b.getItemDetail(m)if l(n)then b.select(m)return true end end return false end local function j(k,l)for m=1,16 do local n=b.getItemDetail(m)if l(n)then b.select(m)k()end end end function c.dropAll(k)j(b.drop,i(k))end function c.dropAllDown(k)j(b.dropDown,i(k))end function c.dropAllUp(k)j(b.dropUp,i(k))end function c.organize()error("Not yet implemented.")end return c \ No newline at end of file diff --git a/min/movescript.lua b/min/movescript.lua index 1210e63..f8c8ee2 100644 --- a/min/movescript.lua +++ b/min/movescript.lua @@ -1 +1 @@ -a="0.0.1"local d=turtle local e={}e.defaultSettings={debug=false,safe=true,destructive=false,fuels={"minecraft:coal","minecraft:charcoal"}}local function f(t,u)if u and u.debug then print("[MS] "..t)end end function d.digBack(t)d.turnRight()d.turnRight()d.dig(t)d.turnRight()d.turnRight()end function d.detectBack()d.turnRight()d.turnRight()local t=d.detect()d.turnRight()d.turnRight()return t end local function g(t,u,v,w)w=w or e.defaultSettings b=w.safe or e.defaultSettings.safe c=w.destructive or e.defaultSettings.destructive local x=t()if not b then return end while not x do f("Unable to move.",w)if c and v()then f("Detected a block in the way; attempting to remove it.",w)u()end x=t()end end local function h(t)f("Moving up.",t)g(d.up,d.digUp,d.detectUp,t)end local function j(t)f("Moving down.",t)g(d.down,d.digDown,d.detectDown,t)end local function k(t)f("Moving forward.",t)g(d.forward,d.dig,d.detect,t)end local function l(t)f("Moving back.",t)g(d.back,d.digBack,d.detectBack,t)end local function m(t)f("Turning right.",t)d.turnRight()end local function n(t)f("Turning left.",t)d.turnLeft()end local o={["U"]={f=h,needsFuel=true},["D"]={f=j,needsFuel=true},["L"]={f=n,needsFuel=false},["R"]={f=m,needsFuel=false},["F"]={f=k,needsFuel=true},["B"]={f=l,needsFuel=true},["P"]={f=d.place,needsFuel=false},["Pu"]={f=d.placeUp,needsFuel=false},["Pd"]={f=d.placeDown,needsFuel=false},["A"]={f=d.attack,needsFuel=false},["Au"]={f=d.attackUp,needsFuel=false},["Ad"]={f=d.attackDown,needsFuel=false}}local function p(t)f("Refueling...",t)local u=t.fuels or e.defaultSettings.fuels local v=false for w=1,16 do local x=d.getItemDetail(w)if x~=nil then for y,z in pairs(u)do if x.name==z then d.select(i)if d.refuel(x.count)then v=true end break end end end end return v end local function q(t,u)p(u)while d.getFuelLeveld.getFuelLevel()))if w then local x=t.count q(x,u)end for x=1,t.count do v.f()end end end local function s(t,u)local v={}for w in string.gfind(t,"%W*(%d*%u%l*)%W*")do local x,y=string.find(w,"%d+")local z,A=string.find(w,"%u%l*")local B=1 if x~=nil then B=tonumber(string.sub(w,x,y))end local C=string.sub(w,z,A)if B<1 or B>d.getFuelLimit()then error("Instruction at index "..z.." has an invalid count of "..B..". It should be >= 1 and <= "..d.getFuelLimit())end if o[C]==nil then error("Instruction at index "..z..", \""..C.."\", does not refer to a valid action.")end table.insert(v,{action=C,count=B})f("Parsed instruction: "..w,u)end return v end function e.run(t,u)u=u or e.defaultSettings t=t or""f("Executing script: "..t,u)local v=s(t,u)for w,x in pairs(v)do r(x,u)end end function e.runFile(t,u)local v=fs.open(t,"r")local w=v.readAll()v.close()e.run(w,u)end return e \ No newline at end of file +a="0.0.1"local d=turtle local e={}e.defaultSettings={debug=false,safe=true,destructive=false,fuels={"minecraft:coal","minecraft:charcoal"}}local function f(t,u)if u and u.debug then print("[MS] "..t)end end function d.digBack(t)d.turnRight()d.turnRight()d.dig(t)d.turnRight()d.turnRight()end function d.detectBack()d.turnRight()d.turnRight()local t=d.detect()d.turnRight()d.turnRight()return t end local function g(t,u,v,w)w=w or e.defaultSettings b=w.safe or e.defaultSettings.safe c=w.destructive or e.defaultSettings.destructive local x=t()if not b then return end while not x do f("Unable to move.",w)if c and v()then f("Detected a block in the way; attempting to remove it.",w)u()end x=t()end end local function h(t)f("Moving up.",t)g(d.up,d.digUp,d.detectUp,t)end local function j(t)f("Moving down.",t)g(d.down,d.digDown,d.detectDown,t)end local function k(t)f("Moving forward.",t)g(d.forward,d.dig,d.detect,t)end local function l(t)f("Moving back.",t)g(d.back,d.digBack,d.detectBack,t)end local function m(t)f("Turning right.",t)d.turnRight()end local function n(t)f("Turning left.",t)d.turnLeft()end local o={["U"]={f=h,needsFuel=true},["D"]={f=j,needsFuel=true},["L"]={f=n,needsFuel=false},["R"]={f=m,needsFuel=false},["F"]={f=k,needsFuel=true},["B"]={f=l,needsFuel=true},["P"]={f=d.place,needsFuel=false},["Pu"]={f=d.placeUp,needsFuel=false},["Pd"]={f=d.placeDown,needsFuel=false},["A"]={f=d.attack,needsFuel=false},["Au"]={f=d.attackUp,needsFuel=false},["Ad"]={f=d.attackDown,needsFuel=false}}local function p(t)f("Refueling...",t)local u=t.fuels or e.defaultSettings.fuels local v=false for w=1,16 do local x=d.getItemDetail(w)for y,z in pairs(u)do if x.name==z then d.select(i)if d.refuel(x.count)then v=true end break end end end return v end local function q(t,u)p(u)while d.getFuelLeveld.getFuelLevel()))if w then local x=t.count q(x,u)end for x=1,t.count do v.f()end end end local function s(t,u)local v={}for w in string.gfind(t,"%W*(%d*%u%l*)%W*")do local x,y=string.find(w,"%d+")local z,A=string.find(w,"%u%l*")local B=1 if x~=nil then B=tonumber(string.sub(w,x,y))end local C=string.sub(w,z,A)if B<1 or B>d.getFuelLimit()then error("Instruction at index "..z.." has an invalid count of "..B..". It should be >= 1 and <= "..d.getFuelLimit())end if o[C]==nil then error("Instruction at index "..z..", \""..C.."\", does not refer to a valid action.")end table.insert(v,{action=C,count=B})f("Parsed instruction: "..w,u)end return v end function e.run(t,u)u=u or e.defaultSettings t=t or""f("Executing script: "..t,u)local v=s(t,u)for w,x in pairs(v)do r(x,u)end end return e \ No newline at end of file diff --git a/minify.lua b/minify.lua.1 similarity index 100% rename from minify.lua rename to minify.lua.1 diff --git a/src/itemscript.lua b/src/itemscript.lua index 545fdb7..4ec60de 100644 --- a/src/itemscript.lua +++ b/src/itemscript.lua @@ -12,9 +12,7 @@ local t = turtle -- The itemscript module. Functions defined within this table are exported. local itemscript = {} --- Determines if an item stack matches the given name. --- If fuzzy, then the item name will be matched against the given name. -local function stackMatches(itemStack, name, fuzzy) +local function itemStackMatches(itemStack, name, fuzzy) return itemStack ~= nil and ( (not fuzzy and itemStack.name == name) or @@ -22,101 +20,14 @@ local function stackMatches(itemStack, name, fuzzy) ) end -local function notFilter(filter) - return function(item) - return not filter(item) - end -end - -local function andFilter(filters) - return function(item) - for _, filter in pairs(filters) do - if not filter(item) then - return false - end - end - return true - end -end - -local function orFilter(filters) - return function(item) - for _, filter in pairs(filters) do - if filter(item) then - return true - end - end - return false - end -end - --- Parses a filter expression string and returns a filter that implements it. ---[[ - Item Filter Expressions: - - A filter expression is a way to define a complex method of matching item - stacks. - - Prepending ! will match any item stack whose name does not match. - Prepending # will do a fuzzy match using string.find. -]]-- -local function parseItemFilterExpression(expr) - local prefixIdx, prefixIdxEnd = string.find(expr, "^[!#]+") - local fuzzy = false - local negated = false - if prefixIdx ~= nil then - for i = prefixIdx, prefixIdxEnd do - local char = string.sub(expr, i, i) - if char == "!" then - negated = true - elseif char == "#" then - fuzzy = true - end - end - expr = string.sub(expr, prefixIdxEnd + 1, string.len(expr)) - end - local namespaceSeparatorIdx = string.find(expr, ":") - if namespaceSeparatorIdx == nil and not fuzzy then - expr = "minecraft:" .. expr - end - return function(item) - if item == nil then return false end - local matches = stackMatches(item, expr, fuzzy) - if negated then - matches = not matches - end - return matches - end -end - --- Converts an arbitrary variable into a filter; useful for any function that's public, so users can supply any filter. --- It converts the following: --- filter function tables directly. --- strings and lists of strings are translated into an item names filter. --- Functions are added with default fuzzy and whitelist parameters. -local function convertToFilter(var) - if type(var) == "table" and #var > 0 and type(var[1]) == "string" then - local filters = {} - for _, expr in pairs(var) do - table.insert(filters, parseItemFilterExpression(expr)) - end - return orFilter(filters) - elseif type(var) == "string" then - return parseItemFilterExpression(var) - elseif type(var) == "function" then - return var - else - error("Unsupported filter type: " .. type(var)) - end -end - --- Gets the total number of items in the turtle's inventory that match the given expression. -function itemscript.totalCount(filterExpr) - local filter = convertToFilter(filterExpr) +-- Gets the total number of items of a certain type in the turtle's inventory. +-- If fuzzy is set as true, then it'll match substrings matching the given name. +function itemscript.totalCount(name, fuzzy) + fuzzy = fuzzy or false local count = 0 for i = 1, 16 do local item = t.getItemDetail(i) - if filter(item) then + if itemStackMatches(item, name, fuzzy) then count = count + item.count end end @@ -125,11 +36,11 @@ end -- Selects a slot containing at least one of the given item type. -- Returns a boolean indicating whether we could find and select the item. -function itemscript.select(filterExpr) - local filter = convertToFilter(filterExpr) +function itemscript.select(name, fuzzy) + fuzzy = fuzzy or false for i = 1, 16 do local item = t.getItemDetail(i) - if filter(item) then + if itemStackMatches(item, name, fuzzy) then t.select(i) return true end @@ -137,27 +48,43 @@ function itemscript.select(filterExpr) return false end --- Helper function to drop items in a flexible way, using a drop function and filtering function. -local function dropFiltered(dropFunction, filter) +local function itemMatchesFilter(item, name, fuzzy) + fuzzy = fuzzy or false + return (not fuzzy and item.name == name) or string.find(item.name, name) +end + +local function itemNotMatchesFilter(item, name, fuzzy) + return not itemMatchesFilter(item, name, fuzzy) +end + +local function dropFiltered(name, fuzzy, dropFunction, filterFunction) for i = 1, 16 do local item = t.getItemDetail(i) - if filter(item) then + if filterFunction(item, name, fuzzy) then t.select(i) dropFunction() end end end -function itemscript.dropAll(filterExpr) - dropFiltered(t.drop, convertToFilter(filterExpr)) +function itemscript.dropAll(name, fuzzy) + dropFiltered(name, fuzzy or false, t.drop, itemMatchesFilter) end -function itemscript.dropAllDown(filterExpr) - dropFiltered(t.dropDown, convertToFilter(filterExpr)) +function itemscript.dropAllDown(name, fuzzy) + dropFiltered(name, fuzzy or false, t.dropDown, itemMatchesFilter) end -function itemscript.dropAllUp(filterExpr) - dropFiltered(t.dropUp, convertToFilter(filterExpr)) +function itemscript.dropAllUp(name, fuzzy) + dropFiltered(name, fuzzy or false, t.dropUp, itemMatchesFilter) +end + +function itemscript.dropAllExcept(name, fuzzy) + dropFiltered(name, fuzzy or false, t.drop, itemNotMatchesFilter) +end + +function itemscript.dropAllDownExcept(name, fuzzy) + dropFiltered(name, fuzzy or false, t.dropDown, itemNotMatchesFilter) end -- Cleans up the turtle's inventory by compacting all stacks of items. diff --git a/src/movescript.lua b/src/movescript.lua index 534e2a1..21d8661 100644 --- a/src/movescript.lua +++ b/src/movescript.lua @@ -115,13 +115,11 @@ local function refuelAll(settings) local refueled = false for slot = 1, 16 do local item = t.getItemDetail(slot) - if item ~= nil then - for _, fuelName in pairs(fuels) do - if item.name == fuelName then - t.select(i) - if t.refuel(item.count) then refueled = true end - break - end + for _, fuelName in pairs(fuels) do + if item.name == fuelName then + t.select(i) + if t.refuel(item.count) then refueled = true end + break end end end @@ -200,11 +198,4 @@ function movescript.run(script, settings) end end -function movescript.runFile(filename, settings) - local f = fs.open(filename, "r") - local script = f.readAll() - f.close() - movescript.run(script, settings) -end - return movescript