1 : <?php
2 : /**
3 : * This file is part of NoiseLabs-PHP-ToolKit
4 : *
5 : * NoiseLabs-PHP-ToolKit is free software; you can redistribute it
6 : * and/or modify it under the terms of the GNU Lesser General Public
7 : * License as published by the Free Software Foundation; either
8 : * version 3 of the License, or (at your option) any later version.
9 : *
10 : * NoiseLabs-PHP-ToolKit is distributed in the hope that it will be
11 : * useful, but WITHOUT ANY WARRANTY; without even the implied warranty
12 : * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 : * Lesser General Public License for more details.
14 : *
15 : * You should have received a copy of the GNU Lesser General Public
16 : * License along with NoiseLabs-PHP-ToolKit; if not, see
17 : * <http://www.gnu.org/licenses/>.
18 : *
19 : * Copyright (C) 2011 Vítor Brandão <noisebleed@noiselabs.org>
20 : *
21 : *
22 : * @category NoiseLabs
23 : * @package ConfigParser
24 : * @version 0.1.1
25 : * @author Vítor Brandão <noisebleed@noiselabs.org>
26 : * @copyright (C) 2011 Vítor Brandão <noisebleed@noiselabs.org>
27 : */
28 :
29 : namespace NoiseLabs\ToolKit\ConfigParser;
30 :
31 : use NoiseLabs\ToolKit\ConfigParser\BaseConfigParser;
32 : use NoiseLabs\ToolKit\ConfigParser\File;
33 : use NoiseLabs\ToolKit\ConfigParser\Exception\DuplicateSectionException;
34 : use NoiseLabs\ToolKit\ConfigParser\Exception\NoSectionException;
35 : use NoiseLabs\ToolKit\ConfigParser\Exception\NoOptionException;
36 :
37 : /**
38 : * The ConfigParser class implements a basic configuration language which
39 : * provides a structure similar to what’s found in Microsoft Windows INI
40 : * files. You can use this to write PHP programs which can be customized by
41 : * end users easily.
42 : *
43 : * DISCLAIMER:
44 : * Every docblock was shameless copied or at least adapted from Python's
45 : * configparser documentation page (version 3.0). See
46 : * http://docs.python.org/dev/library/configparser.html
47 : *
48 : * @note This class does not interpret or write the value-type prefixes
49 : * used in the Windows Registry extended version of INI syntax.
50 : *
51 : * @author Vítor Brandão <noisebleed@noiselabs.org>
52 : */
53 1 : class ConfigParser extends BaseConfigParser implements ConfigParserInterface
54 : {
55 : const DEFAULT_SECTION = 'DEFAULT';
56 : const HAS_SECTIONS = true;
57 :
58 : /**
59 : * Return a list of the sections available; the default section is not
60 : * included in the list.
61 : */
62 : public function sections()
63 : {
64 0 : return array_keys($this->_sections);
65 : }
66 :
67 : /**
68 : * Add a section named section to the instance. If a section by the given
69 : * name already exists, DuplicateSectionException is raised. If the
70 : * default section name is passed, InvalidArgumentException is raised.
71 : * The name of the section must be a string; if not,
72 : * InvalidArgumentException is raised too.
73 : */
74 : public function addSection($section)
75 : {
76 : // Raise InvalidArgumentException if the name of the section is not
77 : // a string
78 3 : if (!is_string($section)) {
79 1 : throw new \InvalidArgumentException('Invalid type: expecting a string');
80 : }
81 :
82 : // Raise InvalidArgumentException if name is DEFAULT or any of it's
83 : // case-insensitive variants.
84 2 : if (strtolower($section) == 'default') {
85 1 : throw new \InvalidArgumentException('Invalid section name: '.$section);
86 : }
87 :
88 1 : if (false === $this->hasSection($section)) {
89 0 : $this->_sections[(string) $section] = array();
90 0 : }
91 : else {
92 1 : throw new DuplicateSectionException($section);
93 : }
94 0 : }
95 :
96 : /**
97 : * Indicates whether the named section is present in the configuration.
98 : * The default section is not acknowledged.
99 : */
100 : public function hasSection($section)
101 : {
102 2 : return (isset($this->_sections[$section]) && is_array($this->_sections[$section]));
103 : }
104 :
105 : /**
106 : * Return a list of options available in the specified section.
107 : */
108 : public function options($section)
109 : {
110 0 : if (true === $this->hasSection($section)) {
111 0 : return array_keys($this->_sections[$section]);
112 : }
113 : else {
114 0 : throw new NoSectionException($section);
115 : }
116 : }
117 :
118 : /**
119 : * If the given section exists, and contains the given option, return
120 : * TRUE; otherwise return FALSE. If the specified section is NULL or an
121 : * empty string, DEFAULT is assumed.
122 : */
123 : public function hasOption($section, $option)
124 : {
125 1 : if (($section === null) || ($section == '')) {
126 1 : return isset($this->_defaults[$option]);
127 : }
128 : else {
129 1 : return isset($this->_sections[$section][$option]);
130 : }
131 : }
132 :
133 : /**
134 : * @throws NoSectionException if section doesn't exist
135 : */
136 : public function setOptions($section, array $options = array())
137 : {
138 0 : if ($this->hasSection($section)) {
139 0 : $this->_sections[$section] = $options;
140 0 : }
141 : else {
142 0 : if ($this->_throwExceptions()) {
143 0 : throw new NoSectionException($section);
144 : }
145 : else {
146 0 : $this->log("Section '".$section."' doesn't exist");
147 0 : return null;
148 : }
149 : }
150 0 : }
151 :
152 : public function read($filenames = array())
153 : {
154 5 : parent::read($filenames);
155 :
156 : // move the DEFAULT section to $defaults
157 5 : if (isset($this->_sections[static::DEFAULT_SECTION])) {
158 5 : $this->_defaults = array_replace(
159 5 : $this->_defaults,
160 5 : $this->_sections[static::DEFAULT_SECTION]
161 5 : );
162 5 : unset($this->_sections[static::DEFAULT_SECTION]);
163 :
164 5 : var_dump($this->_defaults);
165 5 : }
166 5 : }
167 :
168 : /**
169 : * Get an option value for the named section.
170 : * If the option doesn't exist in the configuration $defaults is used.
171 : * If $defaults doesn't have this option too then we look for the
172 : * $fallback parameter.
173 : * If everything fails throw a NoOptionException.
174 : *
175 : * @param $section Section name
176 : * @param $option Option name
177 : * @param $fallback A fallback value to use if the option isn't found in
178 : * the configuration and $defaults.
179 : *
180 : * @return Option value (if available)
181 : * @throws NoOptionException Couldn't find the desired option in the
182 : * configuration, $defaults or as a fallback value.
183 : */
184 : public function get($section, $option, $fallback = null)
185 : {
186 0 : if ($this->hasOption($section, $option)) {
187 0 : return $this->_sections[$section][$option];
188 : }
189 : // try $defaults
190 0 : elseif (isset($this->_defaults[$option])) {
191 0 : return $this->_defaults[$option];
192 : }
193 : // try $fallback
194 0 : elseif (isset($fallback)) {
195 0 : return $fallback;
196 : }
197 : else {
198 0 : if ($this->_throwExceptions()) {
199 0 : throw new NoOptionException($section, $option);
200 : }
201 : else {
202 0 : $this->log("Option '".$option."' doesn't exist in section '".$section."'");
203 0 : return null;
204 : }
205 : }
206 : }
207 :
208 : /**
209 : * A convenience method which coerces the option in the specified section
210 : * to an integer.
211 : */
212 : public function getInt($section, $option, $fallback = null)
213 : {
214 0 : return (int) $this->get($section, $option);
215 : }
216 :
217 : /**
218 : * A convenience method which coerces the option in the specified section
219 : * to a floating point number.
220 : */
221 : public function getFloat($section, $option, $fallback = null)
222 : {
223 0 : return (float) $this->get($section, $option);
224 : }
225 :
226 : /**
227 : * A convenience method which coerces the option in the specified section
228 : * to a Boolean value. Note that the accepted values for the option are
229 : * '1', 'yes', 'true', and 'on', which cause this method to return TRUE,
230 : * and '0', 'no', 'false', and 'off', which cause it to return FALSE.
231 : * These string values are checked in a case-insensitive manner. Any
232 : * other value will cause it to raise ValueException.
233 : */
234 : public function getBoolean($section, $option, $fallback = null)
235 : {
236 0 : if (is_string($value = $this->get($section, $option))) {
237 0 : $value = strtolower($value);
238 0 : }
239 :
240 0 : if (in_array($value, $this->_boolean_states)) {
241 0 : return $this->_boolean_states[$value];
242 : }
243 : else {
244 0 : $errmsg = "Option '".$option."' in section '".$section."' is not a boolean";
245 0 : if ($this->_throwExceptions()) {
246 0 : throw new \UnexpectedValueException($errmsg);
247 : }
248 : else {
249 0 : $this->log($errmsg);
250 0 : return null;
251 : }
252 : }
253 : }
254 :
255 : /**
256 : * If the given section exists, set the given option to the specified
257 : * value; otherwise raise NoSectionException.
258 : *
259 : * @todo Option and value must be strings; if not, TypeException is raised.
260 : */
261 : public function set($section, $option, $value)
262 : {
263 0 : if (true === $this->hasSection($section)) {
264 0 : $this->_sections[$section][$option] = (string) $value;
265 0 : }
266 : else {
267 0 : if ($this->_throwExceptions()) {
268 0 : throw new NoSectionException($section);
269 : }
270 : else {
271 0 : $this->log("Section '".$section."' wasn't found.");
272 0 : return null;
273 : }
274 : }
275 :
276 0 : return $this;
277 : }
278 :
279 : protected function _buildOptionValueLine($key, $value)
280 : {
281 : // option name
282 0 : $line = $key;
283 : // space before delimiter?
284 0 : if ($this->settings->get('space_around_delimiters') &&
285 0 : $this->settings->get('delimiter') != ':') {
286 0 : $line .= ' ';
287 0 : }
288 : // insert delimiter
289 0 : $line .= $this->settings->get('delimiter');
290 : // space after delimiter?
291 0 : if ($this->settings->get('space_around_delimiters')) {
292 0 : $line .= ' ';
293 0 : }
294 : // and finally, option value
295 0 : $line .= $value;
296 : // record it for eternity
297 0 : return $line.$this->settings->get('linebreak');
298 : }
299 :
300 : protected function _buildOutputString()
301 : {
302 0 : $output = '';
303 :
304 : // TODO: write default section first
305 0 : if (!empty($this->_defaults)) {
306 0 : $output .= sprintf("[%s]\n", static::DEFAULT_SECTION);
307 0 : foreach ($this->_defaults as $key => $value) {
308 0 : $output .= $this->_buildOptionValueLine($key, $value);
309 0 : }
310 0 : $output .= $this->settings->get('linebreak');
311 0 : }
312 :
313 0 : foreach ($this->sections() as $section) {
314 0 : if (!is_array($this->_sections[$section])) {
315 0 : continue;
316 : }
317 : // write header tag
318 0 : $output .= sprintf("[%s]\n", $section);
319 : // and then all options in this section
320 0 : foreach ($this->_sections[$section] as $key => $value) {
321 0 : $output .= $this->_buildOptionValueLine($key, $value);
322 0 : }
323 0 : $output .= $this->settings->get('linebreak');
324 0 : }
325 :
326 0 : return $output;
327 : }
328 :
329 : /**
330 : * Remove the specified option from the specified section. If the section
331 : * does not exist, raise NoSectionException. If the option existed to be
332 : * removed, return TRUE; otherwise return FALSE.
333 : */
334 : public function removeOption($section, $option)
335 : {
336 0 : if (true === $this->hasSection($section)) {
337 0 : if (isset($this->_sections[$section][$option])) {
338 0 : unset($this->_sections[$section][$option]);
339 0 : return true;
340 : }
341 : else {
342 0 : return false;
343 : }
344 : }
345 : else {
346 0 : if ($this->_throwExceptions()) {
347 0 : throw new NoSectionException($section);
348 : }
349 : else {
350 0 : $this->log("Section '".$section."' wasn't found.");
351 0 : return null;
352 : }
353 : }
354 : }
355 : }
356 :
|