mlandauer / rblib

Web Application component for Open Australia (rblib module)

This URL has Read+Write access

rblib / config.rb
100644 188 lines (166 sloc) 6.779 kb
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# config.rb:
# Very simple config parser. Our config files are sort of cod-PHP.
#
# Copyright (c) 2007 UK Citizens Online Democracy. All rights reserved.
# Email: francis@mysociety.org; WWW: http://www.mysociety.org/
#
# $Id: config.rb,v 1.3 2007/10/24 19:13:07 francis Exp $
 
module MySociety
    module Config
 
        # Parse config files (written in a sort of cod-php, using
        # define(OPTION_VALUE_NAME, "value of option");
        # to define individual elements.
        #
        # Example use:
        # MySociety::Config.set_file('../conf/general')
        # opt = MySociety::Config.get('CONFIG_VARIABLE', DEFAULT_VALUE)
 
 
        # find_php() -> php_binary
        # Try to locate the PHP binary in various sensible places.
        def Config.find_php()
            path = ENV["PATH"] or '/bin:/usr/bin'
            paths = path.split(':')
            for dir in path.split(':') +
                ['/usr/local/bin', '/usr/bin', '/software/bin', '/opt/bin', '/opt/php/bin']
                for name in ['php4', 'php', 'php4-cgi', 'php-cgi']
                    if File.exists?(File.join(dir, name))
                        return File.join(dir, name)
                    end
                end
            end
            raise "unable to locate PHP binary, needed to read config file"
        end
 
# Fork a process to the php interpreter
def Config.fork_php
            # We need to find the PHP binary.
            if @php_path.nil?
                @php_path = find_php()
            end
 
            # Delete everything from the environment other than our special variable to
            # give PHP the config file name. We don't want PHP to pick up other
            # information from our environment and turn into an FCGI server or
            # something.
 
            # An assignment or a .clone didn't seem to truly copy ENV
            store_environ = {}
            for k,v in ENV
                store_environ[k] = v
            end
            ENV.clear()
 
            IO.popen(@php_path, "w+") do |child|
yield child
            end
            ENV.clear()
            ENV.update(store_environ)
 
            # check that php exited successfully
            if not $?.success?
                raise "#{@php_path}: failed status #{$?.to_s}"
            end
end
 
        # read_config(FILE) ->
        # Read configuration from FILE, which should be the name of a PHP config
        # file. This is parsed by PHP, and any defines are extracted as config
        # values. "OPTION_" is removed from any names beginning with that.
        attr :php_path
        def Config.read_config(f)
            buf = nil
fork_php do |child|
                child.print("<?php $b = get_defined_constants(); require('#{f}');")
child.print('''
$a = array_diff_assoc(get_defined_constants(), $b);
print "start_of_options\n";
foreach ($a as $k => $v) {
print preg_replace("/^OPTION_/", "", $k); /* strip off "OPTION_" if there */
print "\0";
print $v;
print "\0";
}
?>''')
                child.close_write()
 
                # skip any header material
                line = true
                while line
                    line = child.readline()
                    if line == "start_of_options\n"
                        break
                    else
                        raise "#{@php_path}: #{f}: failed to read options"
                    end
                end
 
                # read remainder
                buf = child.read()
            end
            
            # parse out config values
            vals = buf.split(/\0/) # option values may be empty
            if (vals.size % 2) != 0
                raise "#{@php_path}: #{f}: bad option output from subprocess"
            end
 
            config = {}
            for i in 0..(vals.size / 2 - 1)
                config[vals[i*2]] = vals[i*2+1]
            end
            config["CONFIG_FILE_NAME"] = f
            return config
        end
 
        # set_file FILENAME [IGNORE_MISSING_FILE]
        # Sets the default configuration file, used by mySociety::Config.get.
        # IGNORE_MISSING_FILE if set means will not error if the file is missing,
        # but instead return default values.
        attr :main_config_filename
        attr :ignore_missing_file
        def Config.set_file(filename, ignore_missing_file = false)
            @main_config_filename = filename
            @ignore_missing_file = ignore_missing_file
        end
 
        # load_default
        # Loads and caches default config file, as set with set_file. This
        # function is implicitly called by get and get_all.
        attr :cached_configs
        def Config.load_default()
            filename = @main_config_filename
            if not filename
                raise "Please call MySociety::Config.set_file to specify config file"
            end
            if not File.exists?(filename)
                if @ignore_missing_file
                    return {"CONFIG_FILE_NAME" => filename + " (missing file)"}
                else
                    raise "File missing '" + filename + "'"
                end
            end
 
            if @cached_configs.nil?
                @cached_configs = {}
            end
            if not @cached_configs.include?(filename)
                @cached_configs[filename] = read_config(filename)
            end
            return @cached_configs[filename]
        end
 
        # get KEY [DEFAULT]
        # Returns the constants set for KEY from the configuration file specified
        # in set_config_file. The file is automatically loaded and cached. An
        # exception is thrown if the value isn't present and no DEFAULT is
        # specified.
        def Config.get (key, default = nil)
            config = load_default()
            
            if config.include?(key)
                return config[key]
            elsif not default.nil?
                return default
            else
                raise "No value for '#{key}' in '#{config['CONFIG_FILE_NAME']}', and no default specified"
            end
        end
 
        # getbool KEY [DEFAULT]
        # Returns the constants for the give KEY, defaulting to DEFAULT,
        # and casts it from 1 / 0 to true or false. This is needed as, unlike
        # PHP, Perl and Python for which the config format was initially used,
        # Ruby treats 0 as true.
        def Config.getbool (key, default = nil)
            if default == false
                default = 0
            elsif default == true
                default = 1
            end
            return get(key, default).to_i > 0
        end
    end
end