It is currently April 19th, 2024, 4:27 am

Some useful function in lua

Discuss the use of Lua in Script measures.
User avatar
death.crafter
Rainmeter Sage
Posts: 1399
Joined: April 24th, 2021, 8:13 pm

Some useful function in lua

Post by death.crafter »

I created a lua include file with some useful functions. The codes are a collection of others works as well as mine.

The file includes info about usage and returns of the functions.

Code: Select all

--[[ Functions provided in this file:

Get.
    Variable(name[, defValue]) : Returns a variable as a string, defValue or '' if variable doesn't exist.
    NumberVariable(name[, defValue]) : Returns a variable as a number, defValue or 0 if variable doesn't exist.
    Option(name, option[, defValue]) : Returns an option as string from a section(measure or meter), defValue or ''
    NumberOption(name, option[, defValue]) : Returns an option as number from a section, defValue or 0
    Value(name[, defValue]) : Returns number value of a measure, defValue or 0
    StringValue(name[, defValue]) : Returns string value of a measure, defValue or ''

TableContains.
    Key(table, key) : Returns true if 'table' contains 'key' or false
    Value(table value) : Returns true if 'table' contains 'value' or false

ConvertColor.
    RGB(string[, outputType]) : Converts rgba to outputType or hex | format ('r,g,b,a')
    HSV(string[, outputType]) : Converts hsva to outputType or rgba | format ('h,s,v,a')
    HSL(string[, outputType]) : Converts hsla to outputType or rgba | format ('h,s,l,a')
    HEX(string[, outputType]) : Converts hex to outputType or rgba | format ('xxxxxxxx')
    outputTypes : 'rgb', 'hex', 'hsv', 'hsl'

Log(string[, level])
    level : 1|'Debug', 2|'Notice' (default), 3|'Warning', 4|'Error' || Use number or string

Delim(string[, Separator])
    Separator: Delimiter by which string is to be separated, default is '|'

TruncWhiteSpace(string) : Removes white space from both ends of a string

Multitype(input, types) : Returns true if 'input' is one of the given 'types' | format (variableName, 'datatype1|datatype2|datatype3|...')
    input : any variable
    types : string made up of data types separated by '|'

ReadINI(inputFile) : Returns a table formatted as below ('returnTable' is used to show the outcome, not a way to access the values)
    returnTable['INI'] : This table contains all the sections and their respective keys and values in the format returntable['INI'][section][key]=value
    returnTable['SectionOrder'] : This table indexes the order of sections in the ini file, starting from 1.
            Can be used to iterate through sections in order.
    returnTable['KeyOrder'] : This table indexes the order of keys in each section in the format returnTable['KeyOrder'][section]={indexed keys}
            Can be used to iterate through options of each section.

--]]

Get = {
    Variable=function(name, defValue)
        return SKIN:GetVariable(name) or defValue or ''
    end,

    NumberVariable=function(name, defValue)
        return tonumber(SKIN:GetVariable(name)) or defValue or 0
    end,

    Option=function(name, option, defValue)
        local section=SKIN:GetMeasure(name) or SKIN:GetMeter(name)
        if not section then 
            Log(string.format("Section %s doesn't exist", name), 4)
        end
        return section:GetOption(option) or defValue or ''
    end,

    NumberOption=function(name, option, defValue)
        local section=SKIN:GetMeasure(name) or SKIN:GetMeter(name)
        if not section then 
            Log(string.format("Section %s doesn't exist", name), 4)
        end
        return tonumber(section:GetOption(option)) or defValue or 0
    end,

    Value=function(name, defValue)
        return tonumber(SKIN:GetMeasure(name):GetValue()) or defValue or 0
    end,

    StringValue=function(name, defValue)
        return SKIN:GetMeasure(name):GetValue() or defValue or ''
    end
}

TableContains={
    Key=function(table, key)
        if type(table)~='table' then
            Log(string.format('TableContains: Table expected, got %s.', type(table)), 4)
            return false
        end
        if table[key]~=nil then
            return true
        end
        return false
    end,

    Value=function(table, value)
        if type(table)~='table' then
            Log(string.format('TableContains: Table expected, got %s.', type(table)), 4)
            return
        end
        for k,v in pairs(table) do
            if v==value then
                return true
            end
        end
        return false
    end
}

ConvertColor={
    RGB=function(string, outputType)
        local rgb=Delim(string, ',')

        if table.maxn(rgb)==3 then
            table.insert(rgb, 255)
        end

        local t=outputType:lower() or 'hex'
        if t=='hsv' then
            return table.concat(RGBtoHSV(rgb[1], rgb[2], rgb[3], rgb[4]), ',')
        elseif t=='hsl' then
            return table.concat(RGBtoHSL(rgb[1], rgb[2], rgb[3], rgb[4]), ',')
        else
            return table.concat(RGBtoHEX(rgb[1], rgb[2], rgb[3], rgb[4]))
        end
    end,

    HSV=function(string, outputType)
        local hsv=Delim(string, ',')

        if table.maxn(hsv)==3 then
            table.insert(hsv, 1)
        end
        local t=outputType:lower() or 'rgb'

        local rgb=table.concat(HSVtoRGB(hsv[1], hsv[2], hsv[3], hsv[4]), ',')
        
        if string.find('hex|hsl', t) then
            return Convert.Color.RGB(rgb, t)
        else
            return rgb
        end
    end,

    HSL=function(string, outputType)
        local hsl = Delim(string, ',')

        if table.maxn(hsl)==3 then
            table.insert(hsl, 1)
        end
        local t=outputType:lower() or 'rgb'

        local rgb=table.concat(HSLtoRGB(hsl[1], hsl[2], hsl[3], hsl[4]), ',')
        
        if string.find('hsv|hex', t) then
            return Convert.Color.RGB(rgb, t)
        else
            return rgb
        end
    end,

    HEX=function(string, outputType)
        local t=outputType:lower() or 'rgb'
        local rgb=table.concat(HEXtoRGB(string), ',')

        if string.find('hsv|hsl', t) then
            return Convert.Color.RGB(rgb, t)
        else
            return rgb
        end
    end
}

function Log(string, level)
    if type(string)~='string' then
        SKIN:Bang('!Log', string.format('function Log(string): String expected, got %s', type(string)), 'Error')
        return
    end
    if type(level)=='number' then
        local t={'debug', 'notice', 'warning', 'error'}
        level=level<=4 and t[level] or 'notice'
    elseif type(level)~='string' then
        level='notice'
    else
        level=level:upper()
    end
    SKIN:Bang('!Log', string, level)
end

-- code from https://github.com/EmmanuelOga/columns/blob/master/utils/color.lua
-- {
function HSVtoRGB(h, s, v, a)
    local r, g, b
  
    local i = Math.floor(h * 6);
    local f = h * 6 - i;
    local p = v * (1 - s);
    local q = v * (1 - f * s);
    local t = v * (1 - (1 - f) * s);
  
    i = i % 6
  
    if i == 0 then r, g, b = v, t, p
    elseif i == 1 then r, g, b = q, v, p
    elseif i == 2 then r, g, b = p, v, t
    elseif i == 3 then r, g, b = p, q, v
    elseif i == 4 then r, g, b = t, p, v
    elseif i == 5 then r, g, b = v, p, q
    end
    a=a or 1
    return {r * 255, g * 255, b * 255, a * 255}
end
function RGBtoHSV(r, g, b, a)
    r, g, b, a = r / 255, g / 255, b / 255, a / 255
    local max, min = math.max(r, g, b), math.min(r, g, b)
    local h, s, v
    v = max
  
    local d = max - min
    if max == 0 then s = 0 else s = d / max end
  
    if max == min then
      h = 0 -- achromatic
    else
      if max == r then
      h = (g - b) / d
      if g < b then h = h + 6 end
      elseif max == g then h = (b - r) / d + 2
      elseif max == b then h = (r - g) / d + 4
      end
      h = h / 6
    end
  
    return {h, s, v, a or 255}
end
function HSLtoRGB(h, s, l, a)
    local r, g, b
  
    if s == 0 then
      r, g, b = l, l, l -- achromatic
    else
      function hue2rgb(p, q, t)
        if t < 0   then t = t + 1 end
        if t > 1   then t = t - 1 end
        if t < 1/6 then return p + (q - p) * 6 * t end
        if t < 1/2 then return q end
        if t < 2/3 then return p + (q - p) * (2/3 - t) * 6 end
        return p
      end
  
      local q
      if l < 0.5 then q = l * (1 + s) else q = l + s - l * s end
      local p = 2 * l - q
  
      r = hue2rgb(p, q, h + 1/3)
      g = hue2rgb(p, q, h)
      b = hue2rgb(p, q, h - 1/3)
    end
    a=a or 1
    return {r * 255, g * 255, b * 255, a * 255}
end
function RGBtoHSL(r, g, b, a)
    r, g, b = r / 255, g / 255, b / 255
  
    local max, min = math.max(r, g, b), math.min(r, g, b)
    local h, s, l
  
    l = (max + min) / 2
  
    if max == min then
      h, s = 0, 0 -- achromatic
    else
      local d = max - min
      local s
      if l > 0.5 then s = d / (2 - max - min) else s = d / (max + min) end
      if max == r then
        h = (g - b) / d
        if g < b then h = h + 6 end
      elseif max == g then h = (b - r) / d + 2
      elseif max == b then h = (r - g) / d + 4
      end
      h = h / 6
    end
  
    return {h, s, l, a or 255}
end
-- }

-- From https://docs.rainmeter.net/snippets/colors/
-- {
function RGBtoHEX(r,g,b,a)
	local hex = {}
    local color={r,g,b,a}
	for _, v in pairs(color)  do
		table.insert(hex, ('%02X'):format(tonumber(v)))
	end
	return hex
end

function HEXtoRGB(color)
    local rgb = {}
	for hex in color:gmatch('..') do
		table.insert(rgb, tonumber(hex, 16))
	end
	return rgb
end
-- }

-- By smurfier, for Lua Calendar
-- {
function Delim(input, Separator) -- Separates an input string by a delimiter | table
	
	local tbl = {}
	if type(input) == 'string' then
		if not MultiType(Separator, 'nil|string') then
			Log(string.format('Input #2 must be a string. Received %s instead. Using default value.', type(Separator)), 3)
			Separator = '|'
		end
		
		local MatchPattern = string.format('[^%s]+', Separator or '|')
		
		for word in string.gmatch(input, MatchPattern) do
			table.insert(tbl, word:match('^%s*(.-)%s*$'))
		end
	else
		Log(string.format('Input must be a string. Received %s instead', type(input)), 4)
	end

	return tbl
end -- Delim

function MultiType(input, types) -- Test an input against multiple types
	return not not types:find(type(input))
end -- MultiType
-- }

function TruncWhiteSpace(string)
    return string:gsub('^%s*(.-)%s*$', '%1')
end

function ReadIni(inputfile)
	local file = assert(io.open(inputfile, 'r'), 'Unable to open ' .. inputfile)
	local tbl, section = {}
	local sectionReadOrder, keyReadOrder = {}, {}
	local num = 0
	for line in file:lines() do
        --print(line)
		num = num + 1
		if not line:match('^%s-;') then
			local key, command = line:match('^([^=]+)=(.+)')
			if line:match('^%s-%[.+') then
				section = line:match('^%s-%[([^%]]+)')
				if not tbl[section] then
                    tbl[section]={}
                    table.insert(sectionReadOrder, section)
                    if not keyReadOrder[section] then keyReadOrder[section]={} end
                end
			elseif key and command and section then
				tbl[section][key:match('^%s*(%S*)%s*$')] = command:match('^%s*(.-)%s*$')
                table.insert(keyReadOrder[section], key)
                --print(keyReadOrder[section][table.maxn(keyReadOrder[section])])
			elseif #line > 0 and section and not key or command then
				print(num .. ': Invalid property or value.')
			end
		end
	end
	file:close()

	if not section then print('No sections found in ' .. inputfile) return end

	local finalTable={}
	finalTable['INI']=tbl
	finalTable['SectionOrder']=sectionReadOrder
	finalTable['KeyOrder']=keyReadOrder
	return finalTable
end
Functions.zip
A little request: If you think you have a useful snippet that should be shared, please share it here so I can add it the functions.

Thank you,
death.crafter
You do not have the required permissions to view the files attached to this post.
from the Realm of Death
User avatar
Yincognito
Rainmeter Sage
Posts: 7125
Joined: February 27th, 2015, 2:38 pm
Location: Terra Yincognita

Re: Some useful function in lua

Post by Yincognito »

death.crafter wrote: August 20th, 2021, 11:47 am I created a lua include file with some useful functions. The codes are a collection of others works as well as mine.

The file includes info about usage and returns of the functions.

A little request: If you think you have a useful snippet that should be shared, please share it here so I can add it the functions.

Thank you,
death.crafter
Hehe, just one day and 54 downloads, looks like we have a winner here. ;-)
I had roughly the same idea some time ago when I started to work with Lua, some kind of "library" with the most used functions as a skin designer, but for my limited Lua usage, it wasn't on my top priorities - I'm glad you came up with this though, it may prove to be very useful in the future. :thumbup:

Regarding the request, I guess some file exists / doesn't exist or, in general, some file operation functions (including or not dynamically writing to an INI or INC) would be useful here, in their most general / flexible form, since that's something you can hardly do from native Rainmeter, bar the RunCommand route. Other than that, I'll let you decide whether either yours or my version of a current section index would be good to have or not.

Nice work, again - hopefully it won't become miles and miles long in the (bright) future, and will stay compact, efficient and comfortable as it develops. :great:
Profiles: Rainmeter ProfileDeviantArt ProfileSuites: MYiniMeterSkins: Earth
User avatar
death.crafter
Rainmeter Sage
Posts: 1399
Joined: April 24th, 2021, 8:13 pm

Re: Some useful function in lua

Post by death.crafter »

Yincognito wrote: August 21st, 2021, 9:59 pm Hehe, just one day and 54 downloads, looks like we have a winner here. ;-)
I didn't notice lol. Wish my skins were downloaded at this pace :rofl:
Yincognito wrote: August 21st, 2021, 9:59 pm Regarding the request, I guess some file exists / doesn't exist or, in general, some file operation functions (including or not dynamically writing to an INI or INC) would be useful here, in their most general / flexible form, since that's something you can hardly do from native Rainmeter, bar the RunCommand route.
I don't think lua can handle files and directories, since it's not really in to Windows.
Yincognito wrote: August 21st, 2021, 9:59 pm Other than that, I'll let you decide whether either yours or my version of a current section index would be good to have or not.
Now that you mention it, can you give the whole code here with usage? Mine us just bare minimum to work with simple ones. If I had make meter groups like Group5Meter1 etc, I would prefer yours.
Yincognito wrote: August 21st, 2021, 9:59 pm Nice work, again - hopefully it won't become miles and miles long in the (bright) future, and will stay compact, efficient and comfortable as it develops. :great:
Hope so. As long as folks contribute. ;-)
from the Realm of Death
User avatar
Yincognito
Rainmeter Sage
Posts: 7125
Joined: February 27th, 2015, 2:38 pm
Location: Terra Yincognita

Re: Some useful function in lua

Post by Yincognito »

death.crafter wrote: August 21st, 2021, 11:59 pmI didn't notice lol. Wish my skins were downloaded at this pace :rofl:
Yeah, indeed. Me, I'm a bit oblivious to that, though having high numbers certainly feels good. But then, high numbers mean more requests, more features wanted, and I kinda like doing things once and for good, so I guess lower has its benefits too... :lol:
death.crafter wrote: August 21st, 2021, 11:59 pmI don't think lua can handle files and directories, since it's not really in to Windows.
This or this seem to disagree... :???:

death.crafter wrote: August 21st, 2021, 11:59 pmNow that you mention it, can you give the whole code here with usage? Mine us just bare minimum to work with simple ones. If I had make meter groups like Group5Meter1 etc, I would prefer yours.
Well, this IS the whole code, really (at least when it comes to Lua):

Code: Select all

function SectionIndex(section, occurrence, formula)
  local indexes = {}
  for index in section:gmatch('%d+') do table.insert(indexes, index) end
  if occurrence == 'first' then occurrence = 1 elseif occurrence == 'last' then occurrence = #indexes end
  return tostring(SKIN:ParseFormula(string.gsub(formula, '<x>', tostring(indexes[tonumber(occurrence)] or '0')))) or '0'
end
Usage is nothing complicated:

Code: Select all

; Inline Lua Usage: [&Script:SectionIndex(SomeSection,SomeOccurrence,SomeFormula)]
; SomeSection     = the name of any section, enclosed by ' (apostrophes), like: 'Measure1' or 'Meter1'
; SomeOccurrence  = the desired occurrence of an index, like: 3 or '5' or 'first' or 'last'
; SomeFormula     = the Rainmeter formula applied to the original <x> index, like: '(<x>)' or '(<x>+1)' or '(Sqrt(<x>))'
; Section variables need to use their nested form, like: [&Script:SectionIndex('[#Section]',[#Occurrence],'[#Formula]')]

; Applicability In:
; - Simulation of a CURRENTSECTIONINDEX "built-in" variable, "last" index scenario, to avoid repeating inline Lua syntax
CURRENTSECTIONINDEX=[&Script:SectionIndex('[#*CURRENTSECTION*]','last','(<x>)')]
; - Alternative equivalent using inline Lua in the skin code: [&Script:SectionIndex('[#CURRENTSECTION]','last','(<x>)')]
where Script is naturally a simple script measure like:

Code: Select all

[Script]
Measure=Script
ScriptFile=#@#Scripts\Script.lua
UpdateDivider=-1
Profiles: Rainmeter ProfileDeviantArt ProfileSuites: MYiniMeterSkins: Earth
User avatar
jsmorley
Developer
Posts: 22629
Joined: April 19th, 2009, 11:02 pm
Location: Fort Hunt, Virginia, USA

Re: Some useful function in lua

Post by jsmorley »

Including the "toolkit" .lua file in your script might be useful.

https://docs.rainmeter.net/manual/lua-scripting/#dofile
User avatar
Yincognito
Rainmeter Sage
Posts: 7125
Joined: February 27th, 2015, 2:38 pm
Location: Terra Yincognita

Re: Some useful function in lua

Post by Yincognito »

jsmorley wrote: August 22nd, 2021, 12:35 am Including the "toolkit" .lua file in your script might be useful.

https://docs.rainmeter.net/manual/lua-scripting/#dofile
Is this addressed to me or to death.crafter? Cause if the former, if it'll be included in his "toolkit" .lua file (up to him), shouldn't the function already have "access" to the other functions there? :???:
Profiles: Rainmeter ProfileDeviantArt ProfileSuites: MYiniMeterSkins: Earth
User avatar
jsmorley
Developer
Posts: 22629
Joined: April 19th, 2009, 11:02 pm
Location: Fort Hunt, Virginia, USA

Re: Some useful function in lua

Post by jsmorley »

Yincognito wrote: August 22nd, 2021, 12:57 am Is this addressed to me or to death.crafter? Cause if the former, if it'll be included in his "toolkit" .lua file (up to him), shouldn't the function already have "access" to the other functions there? :???:
All I'm saying is that you might have a simple Lua script that does some particular thing, and you want to use one of the functions from the toolkit in your code.

Seems to me it makes more sense to .dofile the toolkit into your Lua at runtime, rather than pasting the entire thing into your code. Seem like this will be simpler to read, less open to accidental changing of some toolkit code, and means you can share the toolkit between any number of scripts.
User avatar
Yincognito
Rainmeter Sage
Posts: 7125
Joined: February 27th, 2015, 2:38 pm
Location: Terra Yincognita

Re: Some useful function in lua

Post by Yincognito »

jsmorley wrote: August 22nd, 2021, 1:06 am All I'm saying is that you might have a simple Lua script that does some particular thing, and you want to use one of the functions from the toolkit in your code.

Seems to me it makes more sense to .dofile the toolkit into your Lua at runtime, rather than pasting the entire thing into your code. Seem like this will be simpler to read, less open to accidental changing of some toolkit code, and means you can share the toolkit between any number of scripts.
Indeed. So it should be something like:

Code: Select all

function Initialize()
  dofile(SKIN:GetVariable('@')..'Scripts\\Functions.lua')
end

function SectionIndex(section, occurrence, formula)
  local indexes = {}
  for index in section:gmatch('%d+') do table.insert(indexes, index) end
  if occurrence == 'first' then occurrence = 1 elseif occurrence == 'last' then occurrence = #indexes end
  return tostring(SKIN:ParseFormula(string.gsub(formula, '<x>', tostring(indexes[tonumber(occurrence)] or '0')))) or '0'
end
Right?
Profiles: Rainmeter ProfileDeviantArt ProfileSuites: MYiniMeterSkins: Earth
User avatar
jsmorley
Developer
Posts: 22629
Joined: April 19th, 2009, 11:02 pm
Location: Fort Hunt, Virginia, USA

Re: Some useful function in lua

Post by jsmorley »

Yincognito wrote: August 22nd, 2021, 1:20 am Indeed. So it should be something like:

Code: Select all

function Initialize()
  dofile(SKIN:GetVariable('@')..'Scripts\\Functions.lua')
end

function SectionIndex(section, occurrence, formula)
  local indexes = {}
  for index in section:gmatch('%d+') do table.insert(indexes, index) end
  if occurrence == 'first' then occurrence = 1 elseif occurrence == 'last' then occurrence = #indexes end
  return tostring(SKIN:ParseFormula(string.gsub(formula, '<x>', tostring(indexes[tonumber(occurrence)] or '0')))) or '0'
end
Right?
I imagine so. I have not really looked closely at his toolkit. In general though, you can just .dofile the toolkit into the active script, and then just call the functions as you need from the toolkit in the script. No need to duplicate any of the code from the toolkit into the active script.
User avatar
death.crafter
Rainmeter Sage
Posts: 1399
Joined: April 24th, 2021, 8:13 pm

Re: Some useful function in lua

Post by death.crafter »

jsmorley wrote: August 22nd, 2021, 12:35 am Including the "toolkit" .lua file in your script might be useful.

https://docs.rainmeter.net/manual/lua-scripting/#dofile
Yup that's the idea.

Actually I went through smurfier's Lua Calender's lua and thought it might be a good idea to centralize some really useful functions that are used a lot. I liked especially generalizing the getting variables and options idea. Also there was a bunch of color conversion functions that I used before( and using now for a new skin).

I could include Rainmeter docs functions too I guess. Just one dofile and you are ready for some serious automation, without going over to the docs again and again
from the Realm of Death