How do I serialize a variable in VimScript? - file

I wish to save a random Vim dictionnary, let's say:
let dico = {'a' : [[1,2], [3]], 'b' : {'in': "str", 'out' : 51}}
to a file. Is there a clever way to do this? Something I could use like:
call SaveVariable(dico, "safe.vimData")
let recover = ReadVariable("safe.vimData")
Or should I build something myself with only textfiles?

You can put to good use the :string() function. Test these:
let g:dico = {'a' : [[1,2], [3]], 'b' : {'in': "str", 'out' : 51}}
let str_dico = 'let g:dico_copy = ' . string(dico)
echo str_dico
execute str_dico
echo g:dico_copy
... so you can save the str_dico string as a line of a vimscript file (e.g. using writefile()), and then source the vim file directly.

Thanks to VanLaser (cheers), I've been able to implement these functions using string, writefile and readfile. This is not binary serialization but it works well :)
function! SaveVariable(var, file)
" turn the var to a string that vimscript understands
let serialized = string(a:var)
" dump this string to a file
call writefile([serialized], a:file)
endfun
function! ReadVariable(file)
" retrieve string from the file
let serialized = readfile(a:file)[0]
" turn it back to a vimscript variable
execute "let result = " . serialized
return result
endfun
Use them this way:
call SaveVariable(anyvar, "safe.vimData")
let restore = ReadVariable("safe.vimData")
Enjoy!

I used #iago-lito's answer in a script I wrote a few years ago. Yesterday I spent some time improving on it. The vim dictionary is very similar to a JSON object, but:
when I open the file and set filetype=json, the linter complains about the single quotes around the strings, and
the JSON formatter splits the text into multiple lines, and indents them to make a pretty file. As a result, reading only the 0'th line of text doesn't give a complete dictionary object.
Here are my modifications to fix both issues.
function! SaveVariable(var, file)
" Change all single quotes to double quotes.
" {'x':'O''Toole','y':['A',2,'d']} -> {"x":"O""Toole","y":["A",2,"d"]}
let serialized = substitute(string(a:var),"'",'"','g')
" Change all escaped double quotes back to apostrophes.
" {"x":"O""Toole","y":["A",2,"d"]} -> {"x":"O'Toole","y":["A",2,"d"]}
let serialized = substitute(serialized,'""', "'",'g')
call writefile([serialized], a:file)
endfunction
function! ReadVariable(file)
execute 'let result = [' . join(readfile(a:file),'') . ']'
return result[0]
endfunction
This seems to work well for all kinds of data. I tested it with an object, a list, and number and string scalar values.
STRANGER, DANGER!
Here is a word of warning that goes along with this and any other dynamically-generated code. Both #iago-lito's and my solution are vulnerable to code injection, and if you are reading files that are out of your control, bad things can happen to your machine. For example, if someone sneaks this into the file:
42|call system('rmdir /s /q c:\')|call system('rm -rf /')
calling #iago-lito's ReadVariable() will return 42, but your computer will be toast, whether it's a Windows, Mac, or Linux machine. My version also fails, albeit with a more complex version of the statement:
42]|call system('rmdir /s /q c:\')|call system('rm -rf /')|let x=[
A proper solution would be to parse the text, looking for the end of the actual data, and dumping everything after it. This means you lose the simplicity of this approach. Vim and Neovim have come a long way in recent years. lua and python are, from what I've read, easier than ever to integrate into vimscript. I wouldn't be surprised if either of those languages has a built-in answer to this question.

Related

error in looping over files, -fs- command

I'm trying to split some datasets in two parts, running a loop over files like this:
cd C:\Users\Macrina\Documents\exports
qui fs *
foreach f in `r(files)' {
use `r(files)'
keep id adv*
save adv_spa*.dta
clear
use `r(files)'
drop adv*
save fin_spa*.dta
}
I don't know whether what is inside the loop is correctly written but the point is that I get the error:
invalid '"e2.dta'
where e2.dta is the second file in the folder. Does this message refer to the loop or maybe what is inside the loop? Where is the mistake?
You want lines like
use "`f'"
not
use `r(files)'
given that fs (installed from SSC, as you should explain) returns r(files) as a list of all the files whereas you want to use each one in turn (not all at once).
The error message was informative: use is puzzled by the second filename it sees (as only one filename makes sense). The other filenames are ignored: use fails as soon as something is evidently wrong.
Incidentally, note that putting "" around filenames remains essential if any includes spaces.

Opening Japanese-named files in Lua

I have bunch of XML files named in Japanese. I use Lua to read them and put the necessary informations into tables. I could open files named only in a single kanji like 名.xml, but for multiple kanjis like 名前.xml it was contrawise. Before I ran the Lua file, I set the command line's code page to 65001 (as UTF-8). And to read the files I need to encode the filename using WinAPI library from ACP (ASCII code page?) to UTF-8, but this encoding only works for the single kanjis. I've tried several suggestions across internet, using short path to the file, etc. but none of them worked. I tried to use the short path by running Lua as administrator--as stated in other similar question that you need administrator previleges to use the short path--but no luck.
...
for fn in io.popen("DIR xml /B /AA"):lines() do
...
local f = assert(io.open("xml\\" .. winapi.encode(winapi.CP_UTF8, winapi.CP_ACP, fn), "rb"))
...
end
...
But my code produced "Invalid argument" error. I searched this error but none of them are Lua-related, so I opened the C/C++-related ones, but what I got was only 'use _wfopen' or something like that. It's not implemented in Lua and neither I want to implement it myself. So anyone have any idea how to solve this? For more information just be sure to let me know. Thanks!
I don't know why your program does not work, but try this workaround:
local pipe = io.popen([[for %G in (xml\*) do #(type "%G" & echo #FILENAMEMARKER#%G)]], "rb")
local all_files = pipe:read"*a"
pipe:close()
for filecontent, filename in all_files:gmatch"(.-)#FILENAMEMARKER#(.-)\r?\n" do
-- process your file here
print('===== This is your file name:')
print(filename)
print('== This is your file content:')
print(filecontent)
print('== End of file')
end
I think you can use the Japanese alphabet in a table like
local jaAlphbet={"一","|","丶","ノ","乙","亅","<","二","亠","人","⺅","𠆢","儿","入","ハ","丷","冂","冖","冫","几","凵","刀","⺉","力","勹","匕","匚","十","卜","卩","厂","厶","又","マ","九","ユ","乃","𠂉","⻌","口","囗","土","士","夂","夕","大","女","子","宀","寸","小","⺌","尢","尸","屮","山","川","巛","工","已","巾","干","幺","广","廴,"廾","弋","弓","ヨ","彑","彡","彳","⺖","⺘","⺡","⺨","⺾","⻏","⻖","也","亡","及","久","⺹","心","戈","戸","手","支","攵","文","斗","斤","方","无","日","曰","月","木","欠","止","歹","殳","比","毛","氏","气","水","火","⺣","爪","父","爻","爿","片","牛","犬","⺭","王","元","井","勿","尤","五","屯","巴","毋","玄","瓦","甘","生","用","田","疋","疒","癶","白","皮","皿","目","矛","矢","石","示","禸","禾","穴","立","⻂","世","巨","冊","母","⺲","牙","瓜","竹","米","糸","缶","羊","羽","而","耒","耳","聿","肉","自","至","臼","舌","舟","艮","色","虍","虫","血","行","衣","西","臣","見","角","言","谷","豆","豕","豸","貝","赤","走","足","身","車","辛","辰","酉","釆","里","舛","麦","金","長","門","隶","隹","雨","青","非","奄","岡","免","斉","面","革","韭","音","頁","風","飛","食","首","香","品","馬","骨","高","髟","鬥","鬯","鬲","鬼","竜","韋","魚","鳥","鹵","鹿","麻","亀","啇","黄","黒","黍","黹","無","歯","黽","鼎","鼓","鼠","鼻","齊","龠"}
print(jaAlphbet[1])--and you can call the letters, letter by letter
sorry but thats all i know about the subject you are talking about but i hope this helps

How to run same code on multiple files, or all files in directory

so I am very new to coding and recently wrote a little program that involved R and sox. It looked like this
file <- "test.mp3"
testSox = paste("sox ",file," -n spectrogram -o ",file,".png stats",sep='')
sox = system(testSox, intern = TRUE)
print(sox)
Now, instead of assigning the one file manually within the code, I would just like to have this code read through all the mp3s in a folder automatically. Is this possible? Any help would be greatly appreciated. Thanks!
EDIT: Actually, I should add that I tried list.files, but when it comes to running the system() command, I get
"Error in system(command, as.integer(flag), f, stdout, stderr) :
character string expected as first argument"
Here's the list.files code I tried:
> temp = list.files(path = ".", pattern=".mp3")
>
> file <- temp
>
> firstSox = paste("sox ",file," -n spectrogram -o ",file,".png stats",sep='')
> sox = system(firstSox, intern = TRUE)
Error in system(command, as.integer(flag), f, stdout, stderr) :
character string expected as first argument
> print(sox)
I'm guessing this is not the correct route to go? Because I basically need to replace 'file' in the firstSox line with each mp3 that's in the temp array. So instead of running:
file <- "test.mp3"
...I would just like to have it re-assign each time for every file in the folder..., so it runs through as test.mp3, then 1.mp3, then 2.mp3, then 3.mp, etc.
I've scoured the net, and just feel like I've hit a brick wall. As stated in the comments, I've read up on loops, but for some reason I can't wrap my head around how to incorporate it into what I have written. I feel like I just need someone to show me at least the way, or maybe even write me an example so I can wrap my head around it. Would greatly appreciate help and any tips on what I'm doing wrong and could correct. Thanks.
Try the below code. I am using dir() instead of list.files, just because I find it easier. Remember there are many ways to do the same thing in R.
files <- dir(path = ".",pattern = ".mp3") #Get all the mp3 files
for(f in files) { #Loop over the mp3 files one at a time
firstSox = paste("sox ",f," -n spectrogram -o ",f,".png stats",sep='')
sox = system(firstSox, intern = TRUE)
print(sox)
}
Your firstSox variable will be a vector of commands to run (paste will generate a vector, one string for each element of file). So now you just need to run each command through system
One way to do this and capture the output is to use the lapply or sapply function:
sox <- lapply( firstSox, function(x) system(x, intern=TRUE) )
In this code lapply will run the function for each element of firstSox one at a time, the function just takes the current element (in x) and passes that to system. Then lapply gathers all the outputs together and combines them into a list that it puts into sox.
If the results of each run give the same shape of results (single number or vector of same length) then you can use sapply instead and it will simplify the return into a vector or matrix.

Lua 5.2.1 - Edit and save variable in file

I have a file which is part of a game I'm making, and I am trying to manipulate it with code.
Here is the file:
tech =
{
weaponstech = 1.5,
armortech = 1.8,
shieldstech = 2
}
I am trying to open the file like this
local file = io.open("tech")
and then try to change the value of the variable 'shieldstech' to 2.2.
I need this to happen automatically every time I run a function.
I usually use single variable files such as this:
v = 1
but that just gives me a clutter of files which is unmanageable.
so now I store variables the way I wrote my tech file.
This is how I used to edit these single-variable files:
local file = io.open("file", "w")
file:write("v = "..var)
file.close()
but it is just too much work to rewrite the whole file in a single line or code, so I want to just change and save the variable, something like this:
local file = io.open("tech", "w")
shieldstech = 2.2
file:close()
but it won't work like that, and I know why. I'm not telling the program to edit the file, I'm telling it to edit the variable in that instance of the program. All I'm doing to the file is opening it and then closing it.
Any of you guys know a way to do this?
Thx,
Brendan
My suggestion would be to use something designed for that task already. Here is an example: https://github.com/2ion/ini.lua That will allow you to read in the data, make as many or as few changes to it as you want, and then write it back out.
EDIT: This has a dependency on this: https://github.com/stevedonovan/Penlight/blob/master/lua/pl/path.lua
Might want to try inih instead (although it's written C, so integration will require a bit more knowledge): http://luarocks.org/repositories/rocks/#lua-inih
This will rewrite the whole file each time, which is not very performing, but it will work. Consider using a sqlite database.
local file = io.open("tech", "w")
file:write("tech = {")
for p,v in pairs(tech) do file:write(p .. " = " .. v .. "," ) end
file:write("}")
file:close()

Find string in file, replace everything after "="?

I would like to read a file, find some strings and replace everything that is after the symbol "=" in this line.
Lets say I have a textfile like this:
name=whatever
age=150
id.from.system=10298092_42_42
path=D:\name\somewhere
whatever_A= WHATEVER
Lets say I want to change path. At first I have to find the string "path" and then replace everything after "=" somehow. Any ideas? I know I could easily read the file line by line something like this:
val source = io.Source.fromFile("C:/myfile.txt)
val lines = source.mkString
source.close()
But this is maybe not the best idea, because its not that performant to read the whole file (maybe the file got 10000000 lines, and the string is already at line 2, but my program would read the whole file. That would be unnecessary).
And there is maybe another problem: if Im searching for specific strings, like here for "name" but these strings are there several times. I want to make sure that its only valid is after the string there is an "=". Maybe I could search always for something with an "=" at the end, that could solve the problem. But I have no idea how to write this in a nice scala code.
You can use an iterator to only iterate until you find the line you're looking for.
val source = io.Source.fromFile("somePath").getLines
val line = source.find(_.startsWith("path="))
line will contain the first line that starts with "path=".
If your C:/myfile.txt contains the line path=D:\name\somewhere, you can replace D:\name\somewhere with the following code:
val lines = fromString("path=D:\\name\\somewhere").getLines // use fromFile here
for { in <- lines
out <- if (in startsWith("path=")) "path=D:\\my\\path" else in
} yield out
This example will return the string
path=D:\my\path
You would need to use fromFile to get the lines and write the lines out to a new file.
Here's another approach that accomplishes the same thing:
val lines = fromString("path=D:\\name\\somewhere").getLines
lines.map(in => if (in startsWith("path=")) "path=D:\\my\\path" else in)

Resources