It is currently October 9th, 2024, 3:26 pm
Tips & Tricks Thread
-
- Rainmeter Sage
- Posts: 889
- Joined: September 6th, 2011, 6:34 am
Re: Tips & Tricks Thread
What would you do with that?
Have more fun creating skins with Sublime Text 2 and the Rainmeter Package!
-
- Posts: 446
- Joined: August 7th, 2012, 9:18 pm
Re: Tips & Tricks Thread
Generating readable tables or have you ever triedMerlinTheRed wrote:What would you do with that?
print(table)
in Lua?
Powered by Sublime Text 3 with Rainmeter-Package
-
- Posts: 446
- Joined: August 7th, 2012, 9:18 pm
Re: Tips & Tricks Thread
Some time ago (like 2 years ago) I found a very usefull usage of metatables on this forum here which enables the creator a very easy way to manipulate the meters, measures and variables. It was just kinda buggy or more missing parts which where more corner cases.
I added some comments about maybe confusing parts and have decrypted most parts
Usage Examples:
you might want to compact it back again into 4 rows, because it pretty much offers just as an interface
€ Updated
Usage Meters, Measures, Variables = dofile(SKIN:GetVariable('@').."Scripts\\libs\\InterfaceOOPAccess.lua")(SKIN)
InterfaceOOPAccess.lua
I added some comments about maybe confusing parts and have decrypted most parts
Usage Examples:
- meters.meterExample.Text resolves to SKIN:GetMeter('meterExample'):GetOption('Text')
- meters.meterExample.Text = 'Example' >SKIN:Bang('!SetOption', 'meterExample', 'Text', 'Example', '#CURRENTCONFIG#')
- measures.measureExample.UpdateMeasure() > SKIN:Bang('!UpdateMeasure', 'measureExample', '#CURRENTCONFIG#')
- variables.CURRENTCONFIG > SKIN:GetVariables('CURRENTCONFIG')
you might want to compact it back again into 4 rows, because it pretty much offers just as an interface
€ Updated
Usage Meters, Measures, Variables = dofile(SKIN:GetVariable('@').."Scripts\\libs\\InterfaceOOPAccess.lua")(SKIN)
InterfaceOOPAccess.lua
Code: Select all
return function(SKIN)
local ms = {
__index = function(table,key)
-- catch recursive call
if key == '__section' and #table < 2 then
return
elseif key == '__sectionname' and #table < 2 then
return
-- catch update()
elseif key == 'update' and SKIN:GetMeasure(table.__sectionname) then
return function() SKIN:Bang('!UpdateMeasure',table.__sectionname, SKIN:GetVariable('CURRENTCONFIG')) end
-- catch update()
elseif key == 'update' and SKIN:GetMeter(table.__sectionname) then
return function() SKIN:Bang('!UpdateMeter',table.__sectionname, SKIN:GetVariable('CURRENTCONFIG')) end
-- catch isMeter()
elseif key == 'isMeter' then
return function() return SKIN:GetMeter(table.__sectionname) and true or false end
-- catch isMeter()
elseif key == 'isMeasure' then
return function() return SKIN:GetMeasure(table.__sectionname) and true or false end
-- catch hide()
elseif key == 'hide' and SKIN:GetMeter(table.__sectionname) then
return function() SKIN:Bang('!HideMeter',table.__sectionname, SKIN:GetVariable('CURRENTCONFIG')) end
-- catch show()
elseif key == 'show' and SKIN:GetMeter(table.__sectionname) then
return function() SKIN:Bang('!ShowMeter',table.__sectionname, SKIN:GetVariable('CURRENTCONFIG')) end
-- catch Rainmeter Native Build-In functions
-- Show, Hide, SetXYWH, GetXYWH, GetName, GetOption (though special case), Enable, Disable, GetValueRange, GetRelativeValue, GetMaxValue,
elseif table.__section[key] then
return function(...) return table.__section[key](table.__section,...) end
-- catch meter options
elseif table.__section.GetOption then
return table.__section:GetOption(key)
-- catch measure options
elseif table.__section.GetNumberOption then
return (table.__section.GetNumberOption and table.__section:GetNumberOption(key,nil))
-- unknown case
else
print('Unkown Section Case: ' .. key)
return
end
end,
__newindex = function(table,key,value) SKIN:Bang('!SetOption',table.__sectionname,key,value,SKIN:GetVariable('CURRENTCONFIG')) end
}
local sections = {
__index = function(table,key)
if key == 'redraw' then
return function() SKIN:Bang('!Redraw', SKIN:GetVariable('CURRENTCONFIG')) end
else
sections[key] = {}
-- store meter/measure
sections[key].__section = SKIN:GetMeasure(key) or SKIN:GetMeter(key)
-- store meter/measurename
sections[key].__sectionname = key
setmetatable(sections[key],ms)
return sections[key]
end
end,
isMeter = function(meter)
return SKIN:GetMeter(meter) and true or false
end,
isMeasure = function(measure)
return SKIN:GetMeasure(measure) and true or false
end,
toggleGroup = function(meterGroup)
SKIN:Bang('!ToggleMeterGroup', meterGroup,SKIN:GetVariable('CURRENTCONFIG'))
end
}
local variables = {
__index = function(table,key)
-- catch variables.ReplaceVariables()
if key == 'ReplaceVariables' then
return function(param) return SKIN:ReplaceVariables(param) end
-- catch variable
elseif SKIN:GetVariable(key) then
return SKIN:GetVariable(key)
-- unknown case
else
print('Unkown Variable Case: ' .. key)
return
end
end,
__newindex = function(table,key,value) SKIN:Bang('!SetVariable',key,value, SKIN:GetVariable('CURRENTCONFIG')) end
}
setmetatable(variables,variables)
setmetatable(sections,sections)
return sections, sections, variables
end
You do not have the required permissions to view the files attached to this post.
Powered by Sublime Text 3 with Rainmeter-Package
-
- Posts: 34
- Joined: August 6th, 2012, 9:56 pm
- Location: Lurking
Re: Tips & Tricks Thread
thatsIch wrote:Code: Select all
return function(SKIN) local ms = { __index = function(table,key) -- catch recursive call if key == '__section' and #table < 2 then return elseif key == '__sectionname' and #table < 2 then return -- catch update() elseif key == 'update' and SKIN:GetMeasure(table.__sectionname) then return function() SKIN:Bang('!UpdateMeasure',table.__sectionname, SKIN:GetVariable('CURRENTCONFIG')) end -- catch update() elseif key == 'update' and SKIN:GetMeter(table.__sectionname) then return function() SKIN:Bang('!UpdateMeter',table.__sectionname, SKIN:GetVariable('CURRENTCONFIG')) end -- catch isMeter() elseif key == 'isMeter' then return function() return SKIN:GetMeter(table.__sectionname) and true or false end -- catch isMeter() elseif key == 'isMeasure' then return function() return SKIN:GetMeasure(table.__sectionname) and true or false end -- catch hide() elseif key == 'hide' and SKIN:GetMeter(table.__sectionname) then return function() SKIN:Bang('!HideMeter',table.__sectionname, SKIN:GetVariable('CURRENTCONFIG')) end -- catch show() elseif key == 'show' and SKIN:GetMeter(table.__sectionname) then return function() SKIN:Bang('!ShowMeter',table.__sectionname, SKIN:GetVariable('CURRENTCONFIG')) end -- catch Rainmeter Native Build-In functions -- Show, Hide, SetXYWH, GetXYWH, GetName, GetOption (though special case), Enable, Disable, GetValueRange, GetRelativeValue, GetMaxValue, elseif table.__section[key] then return function(...) return table.__section[key](table.__section,...) end -- catch meter options elseif table.__section.GetOption then return table.__section:GetOption(key) -- catch measure options elseif table.__section.GetNumberOption then return (table.__section.GetNumberOption and table.__section:GetNumberOption(key,nil)) -- unknown case else print('Unkown Section Case: ' .. key) return end end, __newindex = function(table,key,value) SKIN:Bang('!SetOption',table.__sectionname,key,value,SKIN:GetVariable('CURRENTCONFIG')) end } local sections = { __index = function(table,key) if key == 'redraw' then return function() SKIN:Bang('!Redraw', SKIN:GetVariable('CURRENTCONFIG')) end else sections[key] = {} -- store meter/measure sections[key].__section = SKIN:GetMeasure(key) or SKIN:GetMeter(key) -- store meter/measurename sections[key].__sectionname = key setmetatable(sections[key],ms) return sections[key] end end, isMeter = function(meter) return SKIN:GetMeter(meter) and true or false end, isMeasure = function(measure) return SKIN:GetMeasure(measure) and true or false end, toggleGroup = function(meterGroup) SKIN:Bang('!ToggleMeterGroup', meterGroup,SKIN:GetVariable('CURRENTCONFIG')) end } local variables = { __index = function(table,key) -- catch variables.ReplaceVariables() if key == 'ReplaceVariables' then return function(param) return SKIN:ReplaceVariables(param) end -- catch variable elseif SKIN:GetVariable(key) then return SKIN:GetVariable(key) -- unknown case else print('Unkown Variable Case: ' .. key) return end end, __newindex = function(table,key,value) SKIN:Bang('!SetVariable',key,value, SKIN:GetVariable('CURRENTCONFIG')) end } setmetatable(variables,variables) setmetatable(sections,sections) return sections, sections, variables end
Code: Select all
function meta()
local ms = {__index = function(tb,key) if key == 'UpdateMeasure' then return function() SKIN:Bang('!UpdateMeasure',tb.__sectionname) end elseif tb.__section[key] then return function(...) return tb.__section[key](tb.__section,...) end else return (tb.__section.GetNumberOption and tb.__section:GetNumberOption(key,nil)) or tb.__section:GetOption(key) end end, __newindex = function(tb,key,value) SKIN:Bang('!SetOption',tb.__sectionname,key,value) end}
sections,variables = {__index = function(tb,key) sections[key]={} sections[key].__section, sections[key].__sectionname = SKIN:GetMeasure(key), key setmetatable(sections[key],ms) return sections[key] end},{__index = function(tb,key) return SKIN:GetVariable(key) end, __newindex = function(tb,key,value) SKIN:Bang('!SetVariable',key,value) end}
setmetatable(variables,variables)
setmetatable(sections,sections)
meters, measures = sections, sections
end
That looks.... a little too familiar. Sounds like at least someone got some usage from my 4 line function I wrote ages ago. Kind of goes to show what parts of the lua API I didn't touch considering what cases I missed, especially since I've still been using the metatable func in my latest skins. (UpdateMeter, Redraw, ToggleMeterGroup)
Kind of glad someone else could make sense of that cryptic mess though, especially ( function(...) return table.__section[key](table.__section,...) ) If I hadn't been working with lua so long, don't think I would have ever done something so unreadable.
-
- Posts: 13
- Joined: March 22nd, 2017, 12:14 am
io.popen replacement to get the contents of a folder
Rainmeter doesn't support the io.popen command in lua to get the contents of a folder. That's why I came up with a workaround that solves this task.
In short the workaround consists of a powershell script that writes the filepaths into a text file. The text file with its file paths can then be read into an array in lua.
Powershell is necessary to solve this task since it includes a StreamWriter that can write an output file in a fraction of a second. I first tried to create this file with a simple CMD command but CMD needs around 10 seconds to complete this write task and since powershell comes pre installed since Windows 7 there should be no reason to avoid its use.
I created a test skin where every step is explained with comments to illustrate the functionality of the workaround. The skin searches for a random text file inside one of three folders inside the @Resources folder of the skin. It then displays the names of all files from the folder where the text file was found.
There are a few things to consider:
First the output file, in this skin called OutputFile.txt, should already exist before the skin is shipped. It's not a big problem otherwise but the skin only seems to work after the second refresh if this file doesn't exist already.
Second is the way the RunCommand measure is called. If there are variables or measure references inside the RunCommand measure it first has to be updated and then called as Brian explained to me in this forum. This happens with the line OnUpdateAction=[!UpdateMeasure MeasureRunFolderContent][!CommandMeasure MeasureRunFolderContent "Run"].
Third is that the paths that are handed to the RunCommandMeasure have to be tested if they include an apostrophe ' . If that's the case the apostrophe has to be escaped with another apostrophe. Substitute="'":"''" takes care of that.
Fourth is that the meters that are affected by the script should be updated and redrawn.
Fifth is that a single RunCommandMeasure should probably not be called multiple times during one update cycle. Otherwise the RunCommand plugin will return a 101 error, saying that the script is still running and can't be called again in the moment. In this skin the measure that calls RunCommand only gets updated every 10 seconds for example after the QuotePlugin had returned another file. This is taken care of by using UpdateDivider=-1 and by calling that measure manually.
Sixth is how files are treated that have unicode characters (e.g тس流हिंが) in their file paths. This article from the Rainmeter site and this forum post by jsmorley explain how to deal with this problem in detail. Here it's necessary to encode the ini-file of the skin with UCS-2 LE BOM by using Notepad++ for example. On top of that the lua files have to be encoded in UTF-16 LE. This can be done by opening the lua files inside the standard windows editor and saving them with unicode encoding as explained in this post. This should be done last after all testing is done since the lua files with unicode encoding may can't be displayed inside your Lua IDE anymore. On top of that the output file of the powershell script should be encoded in UTF-8 with BOM which is already taken care of by defining the encoding inside the script.
Here is the powershell script that writes the contents of a folder into an output file. It offers two extra arguments that dictate if the folder content should be listed recursively and whether the output should be appended to the existing output file. All arguments are explained inside the ini file above if you scroll to [MeasureRunFolderContent].
Here is the basic form of the powershell script that can be updated with anything the Get-ChildItem command and powershell has to offer.
Get-ChildItem Documentation
At last I also want to add the lua script which reads the output file of the powershell script into an array and creates an output string.
And that's how you can get a list of files from a folder to work with in lua. The example skin is attached below.
Edit: Updated this skin and post to make this procedure work with unicode.
In short the workaround consists of a powershell script that writes the filepaths into a text file. The text file with its file paths can then be read into an array in lua.
Powershell is necessary to solve this task since it includes a StreamWriter that can write an output file in a fraction of a second. I first tried to create this file with a simple CMD command but CMD needs around 10 seconds to complete this write task and since powershell comes pre installed since Windows 7 there should be no reason to avoid its use.
I created a test skin where every step is explained with comments to illustrate the functionality of the workaround. The skin searches for a random text file inside one of three folders inside the @Resources folder of the skin. It then displays the names of all files from the folder where the text file was found.
There are a few things to consider:
First the output file, in this skin called OutputFile.txt, should already exist before the skin is shipped. It's not a big problem otherwise but the skin only seems to work after the second refresh if this file doesn't exist already.
Second is the way the RunCommand measure is called. If there are variables or measure references inside the RunCommand measure it first has to be updated and then called as Brian explained to me in this forum. This happens with the line OnUpdateAction=[!UpdateMeasure MeasureRunFolderContent][!CommandMeasure MeasureRunFolderContent "Run"].
Third is that the paths that are handed to the RunCommandMeasure have to be tested if they include an apostrophe ' . If that's the case the apostrophe has to be escaped with another apostrophe. Substitute="'":"''" takes care of that.
Fourth is that the meters that are affected by the script should be updated and redrawn.
Fifth is that a single RunCommandMeasure should probably not be called multiple times during one update cycle. Otherwise the RunCommand plugin will return a 101 error, saying that the script is still running and can't be called again in the moment. In this skin the measure that calls RunCommand only gets updated every 10 seconds for example after the QuotePlugin had returned another file. This is taken care of by using UpdateDivider=-1 and by calling that measure manually.
Sixth is how files are treated that have unicode characters (e.g тس流हिंが) in their file paths. This article from the Rainmeter site and this forum post by jsmorley explain how to deal with this problem in detail. Here it's necessary to encode the ini-file of the skin with UCS-2 LE BOM by using Notepad++ for example. On top of that the lua files have to be encoded in UTF-16 LE. This can be done by opening the lua files inside the standard windows editor and saving them with unicode encoding as explained in this post. This should be done last after all testing is done since the lua files with unicode encoding may can't be displayed inside your Lua IDE anymore. On top of that the output file of the powershell script should be encoded in UTF-8 with BOM which is already taken care of by defining the encoding inside the script.
Code: Select all
[Rainmeter]
Update=1000
;Encoded in UCS-2 LE BOM with Notepad++
[Metadata]
Name=FolderContent
Author=kounger
Information=A test skin which shows a replacement for io.popen in lua.
Version=1.0
License=CC BY-NC-SA 3.0
;Every 10 seconds this measure returns a new random text file from the folder #@#TestFolders
[MeasureRandomTextFile]
Measure=Plugin
Plugin=QuotePlugin
PathName=#@#TestFolders
Subfolders=1
FileFilter=*.txt
UpdateDivider=10
OnUpdateAction=[!UpdateMeasure "MeasureTextFileFolder"]
DynamicVariables=1
;This measure gets the path to the folder of the file [MeasureRandomTextFile]
;and then calls [MeasureRunFolderContent] to run.
;"'":"''" to escape single quotes ' for cmd
[MeasureTextFileFolder]
Measure=String
String=[MeasureRandomTextFile]
RegExpSubstitute=1
Substitute="\\[^\\]*$":"","'":"''"
UpdateDivider=-1
DynamicVariables=1
OnUpdateAction=[!UpdateMeasure MeasureRunFolderContent][!CommandMeasure MeasureRunFolderContent "Run"]
;This measure uses a powershell script to create a text file which contains the paths of all files that can be found inside the folder [MeasureTextFileFolder].
;1st Argument: Path to the powershell script.
;2nd Argument: Path(s) to the folder(s) whose files should be listed. Multiple folder paths have to be listed with a comma and no spaces. 'Path1','Path2'
;3rd Argument: 'true' for recursive listing of files. 'false' to only list the files from this one folder.
;4th Argument: 'true' to append the output to the output file. 'false' to overwrite the output file.
;5th Argument: Path to the output file.
;OutputType=ANSI: ANSI is necessary to be able to read error meassages inside the rainmeter logs which the script may returns.
[MeasureRunFolderContent]
Measure=Plugin
Plugin=RunCommand
Program=PowerShell.exe
Parameter=-NoProfile -ExecutionPolicy Bypass -Command "& '#@#\FolderContent.ps1' '[MeasureTextFileFolder]' 'false' 'false' '#@#\OutputFile.txt'"
OutputType=ANSI
State=Hide
FinishAction=[!UpdateMeasure MeasureLuaFileList]
DynamicVariables=1
;This measure runs a lua script that returns a list of filenames
;by using a list of file paths from the text file #@#\OutputFile.txt.
[MeasureLuaFileList]
Measure=Script
ScriptFile=#@#ListFiles.lua
TableName=NoPath
UpdateDivider=-1
OnUpdateAction=[!UpdateMeter MeterTextFileList][!Redraw]
[MeterTextFileList]
Meter=String
MeasureName=MeasureLuaFileList
FontFace=Trebuchet MS
StringEffect=Border
StringAlign=LEFT
FontColor=255,255,255,255
FontSize=10
AntiAlias=1
X=1
Y=1
Code: Select all
$inputFolder = $args[0]
$recursive = $args[1]
$writeMode = $args[2]
$outputPath = $args[3]
if ($recursive -eq 'true'){
$recursive = $true
}
elseif ($recursive -eq 'false') {
$recursive = $false
}
#Hashtable with Get-ChildItem parameters
$parameters = @{
LiteralPath = $inputFolder
Recurse = $recursive
}
if ($writeMode -eq 'true'){
$writeMode = 'Append'
}
elseif ($writeMode -eq 'false') {
$writeMode = 'Create'
}
#Array with FileStream parameters
$fileStreamParas = $outputPath , $writeMode, 'Write', 'Read'
try
{
$fileStream = New-Object IO.FileStream $fileStreamParas
$Utf8BomEncoding = New-Object System.Text.UTF8Encoding $True
$streamWriter = New-Object System.IO.StreamWriter($fileStream, $Utf8BomEncoding)
Get-ChildItem @parameters | ForEach-Object { $streamWriter.WriteLine($_.FullName) }
}
finally
{
$streamWriter.close()
}
Get-ChildItem Documentation
Code: Select all
$inputFolder = $args[0]
$outputPath = $args[1]
#Array with FileStream parameters
$fileStreamParas = $outputPath , 'Create', 'Write', 'Read'
try
{
$fileStream = New-Object IO.FileStream $fileStreamParas
$Utf8BomEncoding = New-Object System.Text.UTF8Encoding $True
$streamWriter = New-Object System.IO.StreamWriter($fileStream, $Utf8BomEncoding)
Get-ChildItem -LiteralPath $inputFolder | ForEach-Object { $streamWriter.WriteLine($_.FullName) }
}
finally
{
$streamWriter.close()
}
Code: Select all
function Initialize()
TempFilePath = SKIN:MakePathAbsolute('@Resources\\OutputFile.txt')
end
function Update()
local array = ReadFileLines(TempFilePath)
return FilesToString(array)
end
--function takes a path to a text file which contains
--full paths to directories and files. It writes all
--file paths line by line into an array.
function ReadFileLines(FilePath)
--Open File
local File = io.open(FilePath)
--Handle Error opening the file
if not File then
print('ReadFile: unable to open file at ' .. FilePath)
return
end
local Contents = {}
--Read File and write into array 'Contents'
for Line in File:lines() do
table.insert(Contents, Line)
end
--Close File
File:close()
--Delete file content
--File = io.open(FilePath, "w")
--File:write("")
--File:close()
return Contents
end
--function takes an array with file paths and returns
--a string which contains the filenames of the files
--inside the array divided with a comma
function FilesToString(array)
local returnString = ""
--for every file inside the array
for i, file in ipairs(array) do
--get file name from path
fileName = string.match(file, '[^\\/]+$')
--append to returnString
if(i == 1) then
returnString = fileName
elseif (i > 1) then
returnString = returnString..', '..fileName
end
end
return returnString
end
Edit: Updated this skin and post to make this procedure work with unicode.
You do not have the required permissions to view the files attached to this post.