summaryrefslogtreecommitdiff
path: root/lib/clod.lua
blob: d25048b64725fae92ebe1cd08435cf35693fc0f0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
-- lib/clod.lua
--
-- Configuration Language Organised (by) Dots
--
-- Copyright 2012 Daniel Silverstone <dsilvers@digital-scurf.org>
--

local type = type
local getinfo = debug.getinfo
local pcall = pcall
local loadstring = loadstring
local tconcat = table.concat
local setmetatable = setmetatable
local setfenv = setfenv

-- metatable for clod config operations
local clod_mt = {}
-- metadata for clod config instances
local metadata = setmetatable({}, {__mode = "k"})

-- Methods for clod instances
local methods = {}

function methods:serialise()
   local entries = metadata[self].entries
   local retstr = {}
   local function serialise_entry(entry)
      local key, value, line = entry.key, entry.value, ""
      if entry.key then
	 local vtype = type(value)
	 if vtype == "string" then
	    line = ("%s %q"):format(key, value)
	 elseif vtype == "number" then
	    line = ("%s = %d"):format(key, value)
	 elseif vtype == "boolean" then
	    line = ("%s = %s"):format(key, value and "true" or "false")
	 else
	    line = ("-- %s was a %s.  Cannot serialise"):format(key, vtype)
	 end
      end
      retstr[#retstr+1] = line
   end
   while entries do
      serialise_entry(entries)
      entries = entries.next
   end
   serialise_entry({})
   return tconcat(retstr, "\n")
end

-- Metamethods for clod instances
function clod_mt:__index(key)
   if key == "settings" then
      return gen_settings(self)
   elseif methods[key] then
      return methods[key]
   end
end

local function parse_config(conf, confname)
   local ret = {}
   local settings = {}
   local last_entry = {lineno = 0}
   local front_entry = last_entry
   local keys = {}
   local parse_mt = {}
   local function gen_hook(key)
      local ret = setmetatable({}, parse_mt)
      keys[ret] = key
      return ret
   end
   function parse_mt:__index(key)
      local prefix = keys[self]
      if not prefix then
	 -- This is a global indexing, so return a fresh entry
	 return gen_hook(key)
      end
      -- A 'local' indexing, so combine with the key
      return gen_hook(("%s.%s"):format(prefix, key))
   end
   function parse_mt:__setindex(key, value)
      -- This is the equivalent of 'foo = "bar"' instead of 'foo "bar"'
      return self[key](value)
   end
   function parse_mt:__call(value)
      local key = assert(keys[self])
      local curline = getinfo(2, "Snlf").currentline
      local entry = { key = key, value = value, lineno = curline }
      while last_entry.lineno < (curline - 1) do
	 local empty = { 
	    lineno = last_entry.lineno + 1,
	    prev = last_entry
	 }
	 last_entry.next = empty
	 last_entry = empty
      end
      last_entry.next = entry
      entry.prev = last_entry
      last_entry = entry
      settings[key] = entry
   end
   local func, msg = loadstring(conf, ("@%s"):format(confname or "clod-config"))
   if not func then
      return nil, msg
   end
   local globs = setmetatable({}, parse_mt)
   setfenv(func, globs)
   local ok, err = pcall(func)
   if not ok then
      return nil, err
   end
   -- Successfully loaded the settings, they're in settings and front_entry
   -- points to line zero which we can now detach
   front_entry.next.prev = nil
   front_entry = front_entry.next
   -- Construct a return object ready for magic
   local ret = setmetatable({}, clod_mt)
   metadata[ret] = {
      settings = settings,
      entries = front_entry,
   }
   return ret
end

return {
   parse = parse_config,
}