i'm currently using a lua script by taran van hemert,where it save every keypress on text file and autohotkey will detect every keypress based on that text file, and i'm just wondering how i can make shift work on other keys, i mean when i press shift+2 it will display "#" at the text file. does anyone know how to do it?
-- note that some of the code has changed since then (it works better now!)
-- Though, I have since abandoned luamacros, in favor of Interception... which i will abandon in favor of QMK.
-- get luamacros HERE: http://www.hidmacros.eu/forum/viewtopic.php?f=10&t=241#p794
-- plug in your 2nd keyboard, load this script into LUAmacros, and press the triangle PLAY button.
-- Then, press any key on that keyboard to assign logical name ('MACROS') to macro keyboard
clear() --clear the console from last run
local keyboardIdentifier = '0000AAA'
--You need to get the identifier code for the keyboard with name "MACROS"
--This appears about halfway through the SystemID item and looks like 1BB382AF or some other alphanumeric combo.
-- It's usually 7 or 8 characters long.
--Once you have this identifier, replace the value of keyboardIdentifier with it
--Don't ask for keyboard assignment help if the user has manually entered a keyboard identifier
if keyboardIdentifier == '0000AAA' then
lmc_assign_keyboard('MACROS');
else lmc_device_set_name('MACROS', keyboardIdentifier);
end
--This lists connected keyboards
dev = lmc_get_devices()
for key,value in pairs(dev) do
print(key..':')
for key2,value2 in pairs(value) do print(' '..key2..' = '..value2) end
end
print('You need to get the identifier code for the keyboard with name "MACROS"')
print('Then replace the first 0000AAA value in the code with it. This will prevent having to manually identify keyboard every time.')
-- Hide window to tray to keep taskbar tidy
lmc.minimizeToTray = true
--lmc_minimize()
--Start Script
sendToAHK = function (key)
--print('It was assigned string: ' .. key)
local file = io.open("C:\\Users\\Jhon Ryven\\Desktop\\2nd keyboard macros\\keypressed.txt", "w") -- writing this string to a text file on disk is probably NOT the best method. Feel free to program something better!
--If you didn't put your AutoHotKey scripts into C:/AHK, Make sure to substitute the path that leads to your own "keypressed.txt" file, using the double backslashes.
--print("we are inside the text file")
file:write(key)
file:flush() --"flush" means "save." Lol.
file:close()
lmc_send_keys('{F24}') -- This presses F24. Using the F24 key to trigger AutoHotKey is probably NOT the best method. Feel free to program something better!
end
local config = {
[45] = "insert",
[36] = "home",
[33] = "pageup",
[46] = "delete",
[35] = "end",
[34] = "pagedown",
[27] = "escape",
[112] = "F1",
[113] = "F2",
[114] = "F3",
[115] = "F4",
[116] = "F5",
[117] = "F6",
[118] = "F7",
[119] = "F8",
[120] = "F9",
[121] = "F10",
[122] = "F11",
[123] = "F12",
[8] = "backspace",
[220] = "backslash",
[13] = "enter",
[16] = "rShift",
[17] = "rCtrl",
[38] = "up",
[37] = "left",
[40] = "down",
[39] = "right",
[32] = "space",
[186] = "semicolon",
[222] = "singlequote",
[190] = "period",
[191] = "slash",
[188] = "comma",
[219] = "leftbracket",
[221] = "rightbracket",
[189] = "minus",
[187] = "equals",
[96] = "num0",
[97] = "num1",
[98] = "num2",
[99] = "num3",
[100] = "num4",
[101] = "num5",
[102] = "num6",
[103] = "num7",
[104] = "num8",
[105] = "num9",
[106] = "numMult",
[107] = "numPlus",
[108] = "numEnter", --sometimes this is different, check your keyboard
[109] = "numMinus",
[110] = "numDelete",
[111] = "numDiv",
[144] = "numLock", --probably it is best to avoid this key. I keep numlock ON, or it has unexpected effects
[192] = "Sc029", --this is the tilde key just before the number row
[9] = "tab",
[20] = "capslock",
[18] = "alt",
[91] = "winkey",
[string.byte('Q')] = "q",
[string.byte('W')] = "w",
[string.byte('E')] = "e",
[string.byte('R')] = "r",
[string.byte('T')] = "t",
[string.byte('Y')] = "y",
[string.byte('U')] = "u",
[string.byte('I')] = "i",
[string.byte('O')] = "o",
[string.byte('P')] = "p",
[string.byte('A')] = "a",
[string.byte('S')] = "s",
[string.byte('D')] = "d",
[string.byte('F')] = "f",
[string.byte('G')] = "g",
[string.byte('H')] = "h",
[string.byte('J')] = "j",
[string.byte('K')] = "k",
[string.byte('L')] = "l",
[string.byte('Z')] = "z",
[string.byte('X')] = "x",
[string.byte('C')] = "c",
[string.byte('V')] = "v",
[string.byte('B')] = "b",
[string.byte('N')] = "n",
[string.byte('M')] = "m",
[string.byte('0')] = "0",
[string.byte('1')] = "1",
[string.byte('2')] = "2",
[string.byte('3')] = "3",
[string.byte('4')] = "4",
[string.byte('5')] = "5",
[string.byte('6')] = "6",
[string.byte('7')] = "7",
[string.byte('8')] = "8",
[string.byte('9')] = "9",
[255+44] = "printscreen",
[145] = "scrolllock",
}
-- define callback for whole device
lmc_set_handler('MACROS', function(button, direction)
--Ignoring upstrokes ensures keystrokes are not registered twice, but activates faster than ignoring downstrokes. It also allows press and hold behaviour
if (direction == 0) then return end -- ignore key upstrokes.
if type(config[button]) == "string" then
print(' ')
print('Your key ID number is: ' .. button)
print('It was assigned string: ' .. config[button])
sendToAHK(config[button])
else
print(' ')
print('Not yet assigned: ' .. button)
end
end) ```
According to what I have found online there should be a 4th parameter flags that gives you information about modifier keys.
log_handler = function(button, direction, ts, flags)
print('Callback for device: button ' .. button .. ', direction '..direction..', ts '..ts..', flags '..flags)
end
Alternatively you remember if shift is pressed. You handle its downstroke so you know that it is pressed right?
Edit:
Modify the code like so, to get access to the flags parameter.
For further information I'd refer you to the Lua users manual. You cannot modify what you do not understand. Sorry.
lmc_set_handler('MACROS', function(button, direction, ts, flags)
print(flags)
--Ignoring upstrokes ensures keystrokes are not registered twice, but activates faster than ignoring downstrokes. It also allows press and hold behaviour
if (direction == 0) then return end -- ignore key upstrokes.
if type(config[button]) == "string" then
print(' ')
print('Your key ID number is: ' .. button)
print('It was assigned string: ' .. config[button])
sendToAHK(config[button])
else
print(' ')
print('Not yet assigned: ' .. button)
end
end)
Related
Ok, so I am trying to create a text-based adventure game. I am currently creating the attack system. Here is the code
start = io.read()
if start == "start" then
ma1 = "jab your opponent"
ma2 = "hook your oppenent"
fa1 = "kick your oppenent"
ha1 = "headbutt your oppenent"
--Melee weapon
mw1 = "fist1"
mw2 = "fist2"
foot = "Kick"
head = "headbutt"
--Magic attacks
ms1 = "none"
ms2 = "none"
ms3 = "none"
ms4 = "none"
--Gadgets
bow = "none"
gun = "none"
sg = "none"
function atk()
io.write("Do you use melee(1), magic(2), or tech(3)?", "\n")
ac = io.read()
if ac == "1" then
io.write("Do you", ma1,",", ma2, ",",fa1,",or",ha1"?", "\n")
end
end
end
print(atk())
Every time I run this it outputs this:
lua: jdoodle.lua:25: global 'ha1' is not callable (a string value)
stack traceback:
jdoodle.lua:25: in function 'atk'
jdoodle.lua:29: in main chunk
[C]: in ?
So if you have any solutions thank you.
You missed a comma:
start = io.read()
if start == "start" then
--body
if ac == "1" then
io.write("Do you", ma1, ", ", ma2, ", ", fa1, ", or", ha1, "?", "\n")
end
end
print(atk())
If someone could please help, that would be great! I'm fairly new to coding, but am trying to create a basic automated trading strategy in Indicore 3, which uses LUA script. Even if you don't know much about trading, you could probably still recognize where I've gone wrong in this code.
Basically I'm trying to automate a buy into a trade when price, is above the FastEMA, the FastEMA is above the MedEMA, and the MedEMA is above the SlowEMA. And a sell trade in the exactly opposite fashion. I'm then wanting to close the trade when, in a buy situation, the Fast EMA is no longer above the MedEMA, and for a sell, the FastEMA rises above the MedEMA.
Plus, if the currency is already in a trade, I don't want it to put another one on. Hence the idea around the HaveTrades function.
Please if you have any idea, give me a shout. Like I say I'm a beginner, however I feel like this is a pretty basic code.
Picture is to illustrate idea. Blue line is FastEMA, Green line is MedEMA, and Red line is SlowEMA.
I used the original code off a 3EMA Indicore tutorial, and have tried to simplify it to what I need, so if there's inconsistencies with the code, please highlight them to me.
function Init()
strategy:name("3EMA test AMI");
strategy:description("No description");
strategy:requiredSource(core.Bar);
strategy:type(core.both);
strategy.parameters:addInteger("FastEMA", "Fast EMA", "No description", 5);
strategy.parameters:addInteger("MedEMA", "Medium Ema", "No description", 20);
strategy.parameters:addInteger("SlowEMA", "Slow EMA", "No description", 50);
strategy.parameters:addGroup("Trading Parameters");
strategy.parameters:addBoolean("AllowTrade", "Allow strategy to trade", "", false);
strategy.parameters:setFlag("AllowTrade", core.FLAG_ALLOW_TRADE);
strategy.parameters:addString("Account", "Account to trade on", "", "");
strategy.parameters:setFlag("Account", core.FLAG_ACCOUNT);
strategy.parameters:addInteger("Amount", "Trade Amount in Lots", "", 1, 1, 100);
strategy.parameters:addBoolean("SetLimit", "Set Limit Orders", "", false);
strategy.parameters:addInteger("Limit", "Limit Order in pips", "", 30, 1, 10000);
strategy.parameters:addBoolean("SetStop", "Set Stop Orders", "", false);
strategy.parameters:addInteger("Stop", "Stop Order in pips", "", 30, 1, 10000);
strategy.parameters:addBoolean("TrailingStop", "Trailing stop order", "", false);
strategy.parameters:addGroup("Notification");
strategy.parameters:addBoolean("ShowAlert", "Show Alert", "", true);
strategy.parameters:addBoolean("PlaySound", "Play Sound", "", false);
strategy.parameters:addBoolean("RecurrentSound", "Recurrent Sound", "", false);
strategy.parameters:addFile("SoundFile", "Sound File", "", "");
strategy.parameters:setFlag("SoundFile", core.FLAG_SOUND);
strategy.parameters:addBoolean("SendEmail", "Send Email", "", false);
strategy.parameters:addString("Email", "Email", "", "");
strategy.parameters:setFlag("Email", core.FLAG_EMAIL);
end
local FastEMA;
local MedEMA;
local SlowEMA;
local EnableStage1TimeRange;
local TimeRangeStart;
local TimeRangeEnd;
local gSource = nil; -- the source stream
local PlaySound;
local RecurrentSound;
local SoundFile;
local Email;
local SendEmail;
local AllowTrade;
local Account;
local Amount;
local BaseSize;
local SetLimit;
local Limit;
local SetStop;
local Stop;
local TrailingStop;
local Offer;
local CanClose;
local iEMAFast;
local iEMAMed;
local iEMASlow;
function Prepare(nameOnly)
FastEMA = instance.parameters.FastEMA;
MedEMA = instance.parameters.MedEMA;
SlowEMA = instance.parameters.SlowEMA;
LookBack = instance.parameters.LookBack;
EnableStage1TimeRange = instance.parameters.EnableStage1TimeRange;
TimeRangeStart = instance.parameters.TimeRangeStart;
TimeRangeEnd = instance.parameters.TimeRangeEnd;
local name = profile:id() .. "(" .. instance.bid:instrument() .. ", " .. tostring(FastEMA) .. ", " .. tostring(MedEMA) .. ", " .. tostring(SlowEMA) .. ", " .. tostring(LookBack).. ", " .. tostring(EnableStage1TimeRange) .. ")";
instance:name(name);
if nameOnly then
return ;
end
ShowAlert = instance.parameters.ShowAlert;
PlaySound = instance.parameters.PlaySound;
if PlaySound then
RecurrentSound = instance.parameters.RecurrentSound;
SoundFile = instance.parameters.SoundFile;
else
SoundFile = nil;
end
assert(not(PlaySound) or (PlaySound and SoundFile ~= ""), "Sound file must be specified");
SendEmail = instance.parameters.SendEmail;
if SendEmail then
Email = instance.parameters.Email;
else
Email = nil;
end
assert(not(SendEmail) or (SendEmail and Email ~= ""), "E-mail address must be specified");
AllowTrade = instance.parameters.AllowTrade;
if AllowTrade then
Account = instance.parameters.Account;
Amount = instance.parameters.Amount;
BaseSize = core.host:execute("getTradingProperty", "baseUnitSize", instance.bid:instrument(), Account);
Offer = core.host:findTable("offers"):find("Instrument", instance.bid:instrument()).OfferID;
CanClose = core.host:execute("getTradingProperty", "canCreateMarketClose", instance.bid:instrument(), Account);
SetLimit = instance.parameters.SetLimit;
Limit = instance.parameters.Limit * instance.bid:pipSize();
SetStop = instance.parameters.SetStop;
Stop = instance.parameters.Stop * instance.bid:pipSize();
TrailingStop = instance.parameters.TrailingStop;
end
gSource = ExtSubscribe(1, nil, instance.parameters.TF, true, "bar");
tSource = ExtSubscribe(2, nil, "t1", true, "bar");
--TODO: Find indicator's profile, intialize parameters, and create indicator's instance (if needed)
iEMAFast = core.indicators:create("EMA", gSource.close, FastEMA)
iEMAMed = core.indicators:create("EMA", gSource.close, MedEMA)
iEMASlow = core.indicators:create("EMA", gSource.close, SlowEMA)
end
local Stage1Status = "Neutral"; -- possible values are "Neutral", "Buy", "Sell"
-- update indicators
iEMAFast:update(core.UpdateLast);
iEMAMed:update(core.UpdateLast);
iEMASlow:update(core.UpdateLast);
-- check for data
--if not iEMAFast.DATA:hasData(period) or not iEMAMed.DATA:hasData(period) or not
iEMAMed.DATA:hasData(period) then
--core.host:trace("Not Enough Data. Checking Next Bar...")
--return;
--end
-- close of bar, Stage 1, making sure EMAs are lined up.
if not haveTrades() then
-- buy setup, if Price > Fast > Med > Slow
if gSource.close[period] > iEMAFast.DATA[period] and
iEMAFast.DATA[period] > iEMAMed.DATA[period] and
iEMAMed.DATA[period] > iEMASlow.DATA[period] then
Status="Buy"
enter("B")
end
-- sell setup, if Price < Fast < Med < Slow
if gSource.close[period] < iEMAFast.DATA[period] and
iEMAFast.DATA[period] < iEMAMed.DATA[period] and
iEMAMed.DATA[period] < iEMASlow.DATA[period] then
Status="Sell"
enter("S")
end
end
end
-- open positions in direction BuySell
function enter(BuySell)
local valuemap, success, msg;
valuemap = core.valuemap();
valuemap.OrderType = "OM";
valuemap.OfferID = Offer;
valuemap.AcctID = Account;
valuemap.Quantity = Amount * BaseSize;
valuemap.BuySell = BuySell;
valuemap.GTC = "GTC";
if SetLimit then
-- set limit order
valuemap.PegTypeLimit = "O";
if BuySell == "B" then
valuemap.PegPriceOffsetPipsLimit = Limit/instance.bid:pipSize();
else
valuemap.PegPriceOffsetPipsLimit = -Limit/instance.bid:pipSize();
end
end
if SetStop then
-- set stop order
valuemap.PegTypeStop = "O";
if BuySell == "B" then
valuemap.PegPriceOffsetPipsStop = -Stop/instance.bid:pipSize();
else
valuemap.PegPriceOffsetPipsStop = Stop/instance.bid:pipSize();
end
if TrailingStop then
valuemap.TrailStepStop = 1;
end
end
if (not CanClose) and (StopLoss > 0 or TakeProfit > 0) then
valuemap.EntryLimitStop = "Y"
end
success, msg = terminal:execute(100, valuemap);
if not(success) then
terminal:alertMessage(instance.bid:instrument(), instance.bid[instance.bid:size() - 1], "open order failure: " .. msg, instance.bid:date(instance.bid:size() - 1));
return false;
end
return true;
end
-- return true if trade is found (can check single side as well)
function haveTrades(BuySell)
local enum, row;
local found = false;
enum = core.host:findTable("trades"):enumerator();
row = enum:next();
while (not found) and (row ~= nil) do
if row.AccountID == Account and
row.OfferID == Offer and
(row.BS == BuySell or BuySell == nil) then
found = true;
end
row = enum:next();
end
return found;
end
-- Close trigger
function close(CanClose)
if HaveTrades and (Status == "Buy") and FastEMA<MedEMA then
Status = "close"
end
if HaveTrades and (Status == "Sell") and FastEMA>MedEMA then
Status = "close"
end
end
I am trying to create a shortcut where I store a set of text templates by using hs.chooser. And, the user can paste this by clicking on the drop down from hs.chooser.
I use the below code which displays my template but doesn't paste the text.
Can someone point me what I am doing wrong?
hs.hotkey.bind({"Q"}, "W", function()
local current = hs.application.frontmostApplication()
local chooser = hs.chooser.new(function(choice)
if not choice then focusLastFocused(); return end
hs.pasteboard.setContents(choice["chars"])
focusLastFocused()
hs.eventtap.keyStrokes(hs.pasteboard.getContents())
end)
chooser:queryChangedCallback(function(string)
local choices = {
{
["text"] = "Testing",
["subText"] = "Testing my text"
}
}
chooser:choices(choices)
end)
chooser:searchSubText(true)
chooser:show()
end)
I figured out the answer
-- Focus the last used window.
local function focusLastFocused()
local wf = hs.window.filter
local lastFocused = wf.defaultCurrentSpace:getWindows(wf.sortByFocusedLast)
if #lastFocused > 0 then lastFocused[1]:focus() end
end
-- On selection, copy the text and type it into the focused application.
local chooser = hs.chooser.new(function(choice)
if not choice then focusLastFocused(); return end
hs.pasteboard.setContents(choice["subText"])
focusLastFocused()
hs.eventtap.keyStrokes(hs.pasteboard.getContents())
end)
chooser:choices({
{
["text"] = "Browser\n",
["subText"] = "I used these browsers",
},
{
["text"] = "Device\n",
["subText"] = "I used these devices",
},
})
hs.hotkey.bind({"E"}, "E", function() chooser:show() end)
I have been following the tutorials on corona website and everything has been going well so far.
But in the tutorial "Displaying and saving score" i seem to have the following runtime error.
attempt to call field setPreferences' (a nil value)
This is the code of score.lua
local M = {}
M.score = 0
function M.init( options )
local customOptions = options or {} -- nice use of "or" operator
local opt = {}
opt.fontSize = customOptions.fontSize or 24
opt.font = customOptions.font or native.systemFont
opt.x = customOptions.x or display.contentCenterX
opt.y = customOptions.y or opt.fontSize*0.5 -- such that the score is positioned at the top, half of its font size.
opt.maxDigits = customOptions.maxDigits or 6
opt.leadingZeros = customOptions.leadingZeros or false
local prefix = ""
if ( opt.leadingZeros ) then
prefix = "0"
end
M.format = "%" .. prefix .. opt.maxDigits .. "d" -- so that its accesible in other modules.
-- Create the score display object
M.scoreText = display.newText( string.format( M.format, 0 ), opt.x, opt.y, opt.font, opt.fontSize ) -- string.format() works like printf and scanf statements
M.scoreText:setFillColor(1,0,0)
return M.scoreText
end
function M.set( value )
M.score = tonumber(value)
M.scoreText.text = string.format( M.format, M.score )
end
function M.get()
return M.score
end
function M.add( amount )
M.score = M.score + tonumber(amount)
M.scoreText.text = string.format( M.format, M.score )
end
function M.save()
print (" the score is " .. M.score)
local saved = system.setPreferences( "app", { currentScore=M.score } )
if ( saved == false) then
print ( "ERROR: could not save score" )
end
end
function M.load()
local score = system.getPreference( "app", "currentScore", "number" )
if ( score ) then
return tonumber(score)
else
print( "ERROR: could not load score (score may not exist in storage)" )
end
end
return M
This is the code of main.lua
local score = require( "score" )
local scoreText = score.init(
{
fontSize = 20,
font = "CoolCustomFont.ttf",
x = display.contentCenterX,
y = 30,
maxDigits = 7,
leadingZeros = true
})
local savedScore = score.load()
score.set( 1000 ) -- Sets the score to value
score.save()
I am aware there are other ways of keeping score, but I want to know what the problem is in my code. I googled everywhere but couldn't come up with any solution. Maybe I have made a mistake somewhere that I'm not able to identify.
Even tried a build on my smart phone, but ended up getting the same error.
From corona docs
Syntax
system.setPreferences( category, preferences )
category (required)
String. Indicates which set of preferences should be accessed on the system. Currently, only the "app" category is supported — this is the application's custom preferences defined by the Corona app developer.
preferences (required)
Table. Table of preferences to be written to storage. This table should contain key-value pairs where the key is the unique name of the preference and its value is either a boolean, number, or string.
if your M.score is nil then you might get an error
try this
local saved = system.setPreferences( "app", { currentScore=M.score or 0 } )
In Lua parlance, is there any syntactic sugar for turning an iterator function into an array (repeated invocations with results stored in ascending indices), perhaps something in the standard library ?
I'm tokenizing a string belonging to a protocol and need to to have positional access to elements at the start of the string, and the end of the string is a variant collection.
The code (specific to my use-case) is as follows, I find it hard to believe that it isn't in the standard library :d
local array_tokenise = function (line)
local i = 1;
local array = {};
for item in string.gmatch(line,"%w+") do
array[i] = item;
i = i +1
end
return array
end
There's no standard library function for it. But really, it's pretty trivial to write:
function BuildArray(...)
local arr = {}
for v in ... do
arr[#arr + 1] = v
end
return arr
end
local myArr = BuildArray(<iterator function call>)
This will only work if your iterator function returns single elements. If it returns multiple elements, you'd have to do something different.
As Nicol Bolas said, there is no standard library function that performs the action you desire.
Here is a utility function that extends the table library:
function table.build(build_fn, iterator_fn, state, ...)
build_fn = (
build_fn
or function(arg)
return arg
end
)
local res, res_i = {}, 1
local vars = {...}
while true do
vars = {iterator_fn(state, vars[1])}
if vars[1] == nil then break end
--build_fn(unpack(vars)) -- see https://web.archive.org/web/20120708033619/http://trac.caspring.org/wiki/LuaPerformance : TEST 3
res[res_i] = build_fn(vars)
res_i = res_i+1
end
return res
end
Here is some example code demonstrating usage:
require"stringify"
local t1 = {4, 5, 6, {"crazy cake!"}}
local t2 = {a = "x", b = "y", c = "z"}
print(stringify(table.build(nil, pairs(t1))))
print(stringify(table.build(nil, pairs(t2))))
print(stringify(table.build(
function(arg) -- arg[1] = k, arg[2] = v
return tostring(arg[1]).." = "..tostring(arg[2])
end
, pairs(t1)
)))
local poetry = [[
Roses are red, violets are blue.
I like trains, and so do you!
By the way, oranges are orange.
Also! Geez, I almost forgot...
Lemons are yellow.
]]
print(stringify(table.build(
function(arg) -- arg[1] == plant, arg[2] == colour
return (
string.upper(string.sub(arg[1], 1, 1))..string.lower(string.sub(arg[1], 2))
.. " is "
.. string.upper(arg[2]).."!"
)
end
, string.gmatch(poetry, "(%a+)s are (%a+)")
)))
Output:
{
[1] = {
[1] = 1,
[2] = 4,
},
[2] = {
[1] = 2,
[2] = 5,
},
[3] = {
[1] = 3,
[2] = 6,
},
[4] = {
[1] = 4,
[2] = {
[1] = "crazy cake!",
},
},
}
{
[1] = {
[1] = "a",
[2] = "x",
},
[2] = {
[1] = "c",
[2] = "z",
},
[3] = {
[1] = "b",
[2] = "y",
},
}
{
[1] = "1 = 4",
[2] = "2 = 5",
[3] = "3 = 6",
[4] = "4 = table: 00450BE8",
}
{
[1] = "Rose is RED!",
[2] = "Violet is BLUE!",
[3] = "Orange is ORANGE!",
[4] = "Lemon is YELLOW!",
}
stringify.lua can be found here
if you are just looking to auto-increment the table key for each data element, you can use table.insert(collection, item) - this will append the item to the collection and sets the key to the collection count + 1