lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


My obsessive compulsive disorder thanks you for feeding it‎. Open source software is not free, I pay for it with the hours the normal people use to sleep. To the computer!


Sent from my BlackBerry 10 smartphone on the Virgin Mobile network.
  Original Message  
From: Sean Conner
Sent: Wednesday, September 28, 2016 5:42 PM
To: Lua mailing list
Reply To: Lua mailing list
Subject: Re: Reading a conf file

It was thus said that the Great Russell Haley once stated:
> Okay all you smarty pants (tee hee)! I need to be able to replace the
> value of a line item in said conf files. 

Once you have the values in a Lua table, couldn't you just write the table
back out as Lua code with the change made?

conf[item] = value

> Any ideas are welcome. I haven't gotten to look at Jeromes code yet
> and the answer could be in there...

Well, here's an LPeg solution. I'll walk through it in the hopes you
understand it.

-- ******************************************************
-- Loads the LPeg module. Should be self explanitory.
-- ******************************************************

local lpeg = require "lpeg"

-- *****************************************************
-- Cache some LPeg functions as locals. This is primarily because I'm too
-- lazy to type 'lpeg.C' and 'lpeg.P' all over the place, leading to less
-- code clutter. I'm only including the functions I'm using.
-- *****************************************************

local Carg = lpeg.Carg -- returns argument to pattern:match() as capture
local Cmt = lpeg.Cmt -- runs a function at match time over the captured data
local Cs = lpeg.Cs -- returns a capture with substitued data
local C = lpeg.C -- simple capture
local S = lpeg.S -- define a set of characters to match
local P = lpeg.R -- another way to define a set of characters to match using ranges
local R = lpeg.P -- literal string match

-- ********************************************************
-- Some simple patterns. 
-- SP will match spaces or tabs (as a set) zero or more times.
-- EQ is a literal '=' surrounded by optional whitespace.
-- LF is the end-of-line marker. It will match an optional CR character
-- (used on Windows, not used at all on Linux or MacOSX) and a LF
-- character (used by all).
-- ********************************************************

local SP = S" \t"^0 -- optional white space
local EQ = SP * P"=" * SP -- literal '='
local LF = P"\r"^-1 * P"\n" -- end of line, OS neutral

-- *********************************************************
-- 'id' is a pattern to match a value name. I'm being liberal in assuming
-- value names are made up of letters, digits, underscore and dashes. Yes,
-- not only will this catch names like:
--	item
--	fred
--	a_name_for-something_
-- but also
--	3
--	23-skidoo
-- 'val' is defined as ASCII characters from space to ~ (the entire graphic
-- ASCII range) plus the tab character.
-- *********************************************************

local id = R("09","AZ","az","__","--")^1 -- name of var
local val = R("\t\t"," ~")^0 -- rest of line

-- *********************************************************
-- Capture a name/value pair as two distinct values.
-- *********************************************************

local pair = C(id) * EQ * C(val)

-- *********************************************************
-- The meat of the function. When a pair is found, we also include the
-- first argument of the match function as a capture (basically, it contans
-- the name of the item and the new value---more on this below). When the
-- match is found, immediately call the given function with our three
-- captures (the 'subject' and 'pos' argument are always passed---'subject'
-- being the entire string being matched, 'pos' just past the last character
-- matched). We compre the id with the one we're looking for, and if it
-- matches, we return 'pos' (telling Cmt() that we succeeded) and a new
-- string containing 'item=newvalue'; otherwise, we didn't find what we were
-- looking for, and just return 'pos'.
-- We then check to make sure the line ends with a LF sequence.
-- *********************************************************

local line = Cmt(pair * Carg(1),function(subject,pos,name,val,arg)
if name == arg.item then
return pos,string.format("%s=%s",name,arg.value)
return pos
end) * LF

-- *********************************************************
-- The other half of the work. we repeat looking for name/value pairs over
-- the entire input:
--	(line + P(1))^1
-- In case we don't match a name/value pair, we still match anything so we
-- don't error out. This ensures we go over all input.
-- This loop is wrapped in the Cs() function, which causes some matches to
-- be substituted with a new value. The function we passed to Cmt() returns
-- a new value when we find what we are looking for, otherwise it doesn't. 
-- Any input not substituted will remain as-is.
-- *********************************************************

local conf = Cs((line + P(1))^1)

-- *********************************************************
-- A simple function to scan the given data, changing item to have the new
-- value. We call conf:match() with an additional table (retreived by the
-- Carg() function) with the name of the item and the new value. We then
-- print the old data and the new data to show it changing.
-- *********************************************************

local function SetItem(data,item,value)
local x = conf:match(data,1,{ item = item , value = value })

-- *********************************************************
-- Some sample data and a test run.
-- *********************************************************

local contents = [[
one = two
; comment---this should come out okay
This_is_a_header=some values go here
item = old_value
other_header_name=this that the other

SetItem(contents,'item','new value')

I'll leave it to the reader to run the code.