3 |
4 | This file is part of Configrity.
5 |
6 | Configrity is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU Lesser General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | Configrity is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU Lesser General Public License for more details.
15 |
16 | You should have received a copy of the GNU Lesser General Public License
17 | along with Configrity. If not, see .
18 | */
19 |
20 | package org.streum.configrity
21 |
22 | import converter.ValueConverter
23 | import io._
24 | import scala.io.Source
25 |
26 | /**
27 | * A Configuration class stores and allow access to configuration data. Although
28 | * immutable, several methods allow to easily change configuration data, returning
29 | * a new Configuration instance.
30 | */
31 | case class Configuration( data: Map[String,String], prefix: Option[String] ) {
32 |
33 | /**
34 | * Constructs a Configuration with the given data and no prefix
35 | */
36 | def this( data: Map[String,String] ) = this(data, None)
37 |
38 | /**
39 | * Returns true if some value is associated with the
40 | * given key, else false.
41 | */
42 | def contains( key: String ) = data contains key
43 |
44 | /**
45 | * Retrieve and convert configuration data in the wanted type. Throws
46 | * an NoSuchElementException if the data is not defined. The conversion
47 | * is done by a ValueConverter instance which should be provided or
48 | * implicitly defined elsewhere.
49 | */
50 | def apply[A]( key: String )( implicit converter: ValueConverter[A] ) =
51 | converter( data get key ) getOrElse ( throw new NoSuchElementException( key ) )
52 |
53 | /**
54 | * Retrieve and convert configuration data in the wanted type. Returns None
55 | * if the data is not defined and Some(x) else. The conversion is done by
56 | * a ValueConverter instance which should be provided or implicitly defined
57 | * elsewhere.
58 | */
59 | def get[A]( key: String )( implicit converter: ValueConverter[A] ) =
60 | converter( data get key )
61 |
62 | /**
63 | * Retrieve and convert configuration data in the wanted type. The user
64 | * must supply a default value, returned is the key is not found.
65 | */
66 | def apply[A]( key: String, default: A )( implicit converter: ValueConverter[A] ) =
67 | converter( data get key ) getOrElse default
68 |
69 | /**
70 | * Sets a new configuration value. If the key already exists, previous value
71 | * is replaced.
72 | */
73 | def set[A]( key: String, a: A ) =
74 | Configuration( data + ( key -> a.toString ) )
75 |
76 | /**
77 | * Sets a new configuration list value. If the key already exists,
78 | * previous value is replaced.
79 | */
80 | def set[A]( key: String, as: List[A] ) = {
81 | val str = io.Utils.sanitize(as).mkString( "[", ",", "]" )
82 | Configuration( data + ( key -> str ) )
83 | }
84 |
85 | /**
86 | * Removes a configuration value. No effect if the value was not previously
87 | * defined.
88 | */
89 | def clear[A]( key: String ) =
90 | if( data contains key )
91 | Configuration( data - key )
92 | else
93 | this
94 |
95 | /**
96 | * Convert the map in a string using a provided export format.
97 | * By default, FlatFormat is used.
98 | */
99 | def format( fmt: ExportFormat = Configuration.defaultFormat ) =
100 | fmt.toText( this )
101 |
102 | /** Saves the configuration to a file */
103 | def save(
104 | file: java.io.File,
105 | fmt: ExportFormat = Configuration.defaultFormat
106 | ) {
107 | val out = new java.io.PrintWriter( file )
108 | out.println( format(fmt) )
109 | out.close
110 | }
111 |
112 | /** Saves the configuration to a file */
113 | def save( filename: String, fmt: ExportFormat ): Unit =
114 | save( new java.io.File( filename ), fmt )
115 |
116 | /** Saves the configuration to a file */
117 | def save( filename: String ): Unit =
118 | save( new java.io.File( filename ) )
119 |
120 | /** Attach a configuration as a sub block. Existing entries with
121 | * same keys will be replaced. Prefix should not end with a 'dot'.
122 | */
123 | def attach( prefix: String, block: Configuration ) = {
124 | require(
125 | prefix.substring( prefix.length-1) != ".",
126 | "Prefix should not end with a dot"
127 | )
128 | var nextData = Map[String,String]()
129 | for( (k,v) <- block.data ) {
130 | val nextKey = prefix + "." + k
131 | nextData += nextKey -> v
132 | }
133 | Configuration( data ++ nextData )
134 | }
135 |
136 | /** Detach all values whose keys have the given prefix as a new configuration.
137 | * The initial configuration is not modified. The prefix is removed in the
138 | * resulting configuration. An important property:
139 | *
140 | * val c2 = c1.attach(prefix, c1.detach( prefix )
141 | *
142 | * The resulting configuration c2 should be equal to c1.
143 | */
144 | def detach( prefix: String ) = {
145 | require(
146 | prefix.substring( prefix.length-1) != ".",
147 | "Prefix should not end with a dot"
148 | )
149 | val regexp = (prefix + """\.(.+)""" ).r
150 | var nextData = Map[String,String]()
151 | for( (k,v) <- data ) k match {
152 | case regexp( subkey ) => nextData += subkey -> v
153 | case _ => {}
154 | }
155 | Configuration( nextData, Some(prefix) )
156 | }
157 |
158 | /** Detach all values whose keys have a common prefix as a new configuration,
159 | * and repeat this for the entire set of unique prefixes.
160 | *
161 | * The initial configuration is not modified. The prefix is removed in the
162 | * resulting configuration.
163 | */
164 | def detachAll: Map[String, Configuration] = prefixes.map { prefix =>
165 | prefix -> detach(prefix)
166 | }.toMap
167 |
168 | /** Return the set of unique prefixes in this configuration. */
169 | def prefixes = data.keySet.filter( _.contains('.') ).map( _.split('.').head )
170 |
171 | /**
172 | * Adds another configuration values providing entries
173 | * which are not present in the present one. Useful
174 | * for defaulting to another Configuration
175 | */
176 | def include( config: Configuration ) = Configuration( config.data ++ data )
177 |
178 | /**
179 | * Adds another configuration. The configuration passed in argument
180 | * will override values.
181 | */
182 | def ++( config: Configuration ) = config include this
183 |
184 |
185 | }
186 |
187 |
188 | /** Configuration companion object */
189 |
190 | object Configuration {
191 |
192 | /** By default, all conversions are done with BlockFormat */
193 | val defaultFormat = BlockFormat
194 |
195 | /** Creates a configuration with the given data and no prefix */
196 | def apply( data: Map[String,String] ): Configuration =
197 | Configuration(data, None)
198 |
199 | /** Creates a configuration from tuples of key,value */
200 | def apply( entries:(String,Any)* ):Configuration =
201 | Configuration(
202 | entries.map {
203 | case (k,v) => v match {
204 | case l: List[_] =>
205 | (k, io.Utils.sanitize(l).mkString("[",",","]") )
206 | case _ => (k,v.toString)
207 | }
208 | }.toMap
209 | )
210 |
211 |
212 | /** Returns the environement variables as a Configuration */
213 | def environment = Configuration( sys.env )
214 |
215 | /** Returns the system properties as a Configuration */
216 | def systemProperties = Configuration( sys.props.toMap )
217 |
218 | /** Instanciates a configuration file from a string using
219 | * eventually a specified format. By default, the FlatFormat
220 | * will be used.
221 | */
222 | def parse( s: String, fmt: ImportFormat = defaultFormat ) =
223 | fmt.fromText( s )
224 |
225 | /**
226 | * Load a configuration from a scala.io.Source. An optional
227 | * format can be passed.
228 | */
229 | def load( source: Source,
230 | fmt: ImportFormat = defaultFormat ): Configuration =
231 | fmt.fromText( source.mkString )
232 |
233 | /**
234 | * Load a configuration from a file specified by a filename.
235 | */
236 | def load( fileName: String )
237 | (implicit codec: scala.io.Codec): Configuration =
238 | load( Source.fromFile( fileName ) )
239 |
240 | /**
241 | * Load a configuration from a file specified by a filename and
242 | * using a given format.
243 | */
244 | def load( fileName: String, fmt: ImportFormat )
245 | (implicit codec: scala.io.Codec): Configuration =
246 | load( Source.fromFile( fileName ), fmt )
247 |
248 | /**
249 | * Load a configuration as a resource from the classpath. An optional
250 | * format can be passed.
251 | */
252 | def loadResource( fileName: String, fmt: ImportFormat = defaultFormat ) = {
253 | val inputStream = getClass.getResourceAsStream( fileName )
254 | if (inputStream == null) {
255 | throw new java.io.FileNotFoundException(fileName)
256 | }
257 | val src = Source.fromInputStream(inputStream)
258 | load( src, fmt )
259 | }
260 |
261 |
262 | }
263 |
--------------------------------------------------------------------------------
/core/src/JProperties.scala:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2011, Paradigmatic
3 |
4 | This file is part of Configrity.
5 |
6 | Configrity is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU Lesser General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | Configrity is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU Lesser General Public License for more details.
15 |
16 | You should have received a copy of the GNU Lesser General Public License
17 | along with Configrity. If not, see .
18 | */
19 |
20 | package org.streum.configrity
21 |
22 | import java.util.Properties
23 |
24 | /**
25 | * Provides method for converting a Configuration in a java.util.Properties
26 | * instance and the opposite. Both methods are defined as implicit conversions.
27 | */
28 | object JProperties {
29 |
30 | /**
31 | * java.util.Properties plain text format.
32 | */
33 | val format = io.PropertiesFormat
34 |
35 | /**
36 | * Converts a java.util.Properties instance into a Configuration.
37 | */
38 | implicit def propertiesToConfiguration( props: Properties ) = {
39 | var values = Map[String,String]()
40 | val it = props.keySet.iterator
41 | while( it.hasNext ) {
42 | val key = it.next.asInstanceOf[String]
43 | values += key -> props.get(key).asInstanceOf[String]
44 | }
45 | Configuration( values )
46 | }
47 |
48 | /**
49 | * Converts a Configuration into a java.util.Properties instance.
50 | */
51 | implicit def configurationToProperties( config: Configuration ) = {
52 | val props = new Properties
53 | for( (k,v) <- config.data ) {
54 | props.put(k,v)
55 | }
56 | props
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/core/src/Reader.scala:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2011, Paradigmatic
3 |
4 | This file is part of Configrity.
5 |
6 | Configrity is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU Lesser General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | Configrity is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU Lesser General Public License for more details.
15 |
16 | You should have received a copy of the GNU Lesser General Public License
17 | along with Configrity. If not, see .
18 | */
19 |
20 | package org.streum.configrity
21 |
22 | import converter.ValueConverter
23 |
24 | /** Monadic Reader */
25 | trait Reader[A] {
26 |
27 | /** Produce a value from a Configuration */
28 | def apply( c: Configuration ): A
29 |
30 | def map[B]( f: A => B ) = {
31 | val parent = this
32 | new Reader[B] {
33 | def apply( c: Configuration ) = f( parent(c) )
34 | }
35 | }
36 | def flatMap[B]( f: A => Reader[B] ) = {
37 | val parent = this
38 | new Reader[B] {
39 | def apply( c: Configuration ) = f( parent(c) )(c)
40 | }
41 | }
42 |
43 | }
44 |
45 | case class ConfigurationReader[A: ValueConverter](
46 | key: String,
47 | default: Option[A]
48 | ) extends Reader[A] {
49 | def apply( c: Configuration ) = if( default.isDefined ) {
50 | c.get[A](key).getOrElse( default.get )
51 | } else {
52 | c[A](key)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/core/src/converter/Extra.scala:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2011, Paradigmatic
3 |
4 | This file is part of Configrity.
5 |
6 | Configrity is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU Lesser General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | Configrity is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU Lesser General Public License for more details.
15 |
16 | You should have received a copy of the GNU Lesser General Public License
17 | along with Configrity. If not, see .
18 | */
19 |
20 | package org.streum.configrity.converter
21 |
22 | import java.io.File
23 | import java.awt.Color
24 | import java.net.URL
25 | import java.net.URI
26 |
27 | /**
28 | * Extra value converters.
29 | */
30 | trait Extra {
31 |
32 | /**
33 | * Converts a string to a java.io.File object.
34 | */
35 | implicit val fileConverter = ValueConverter[File]( new File(_) )
36 |
37 | /**
38 | * Converts a string to a java.awt.Color object. The string must be an hexadecimal
39 | * representation of the color (for instance #ffa0a0). The initial hash ('#') is
40 | * optional. The hexdigits must be exactly 6 and may be in upper or lower case.
41 | */
42 | implicit object ColorConverter extends ValueConverter[Color] {
43 |
44 | val HexColor = """#?([a-fA-F0-9]{6})""".r
45 |
46 | def parse( s: String ) = s match {
47 | case HexColor(c) => {
48 | val rgb = Integer.parseInt( c.toLowerCase, 16 )
49 | new Color( rgb )
50 | }
51 | }
52 |
53 | }
54 |
55 | /**
56 | * Converts a string to an URI.
57 | */
58 | implicit val URIConverter = ValueConverter[URI]( new URI(_) )
59 |
60 | /**
61 | * Converts a string to an URL.
62 | */
63 | implicit val URLConverter = URIConverter map ( _.toURL )
64 |
65 | }
66 |
67 |
68 | /*
69 | * Extra value converters.
70 | */
71 | object Extra extends Extra
72 |
--------------------------------------------------------------------------------
/core/src/converter/ListConverter.scala:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2011, Paradigmatic
3 |
4 | This file is part of Configrity.
5 |
6 | Configrity is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU Lesser General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | Configrity is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU Lesser General Public License for more details.
15 |
16 | You should have received a copy of the GNU Lesser General Public License
17 | along with Configrity. If not, see .
18 | */
19 |
20 | package org.streum.configrity.converter
21 |
22 | import scala.util.parsing.combinator._
23 |
24 | /**
25 | * Parse a list of type A. The string representation must be surrounded
26 | * by square brackets: '[...]'. Elements are separed by comma: 'elem1, elem2'.
27 | * Elements are either a single word without reserved characters, or any characters
28 | * surrounded by double quotes '"'.
29 | */
30 | class ListConverter[A]( implicit val valueConverter: ValueConverter[A] )
31 | extends ValueConverter[List[A]] {
32 |
33 | def parse( s: String ) = {
34 | ListConverter.Parser parse s map valueConverter.parse
35 | }
36 |
37 | }
38 |
39 | object ListConverter {
40 |
41 | object Parser extends RegexParsers {
42 |
43 | private def unquote( s: String ) = s.substring( 1, s.size - 1 )
44 |
45 | override val whiteSpace = """(\s+|#[^\n]*\n)+""".r
46 |
47 | val lineSep = "\n"
48 | def word = """([^=\s\n#\{\}\"\[\],])+""".r
49 | def quoted = """"([^"]*)"""".r /*"*/ ^^ { unquote }
50 |
51 | def item = word | quoted
52 |
53 | def items = repsep( item, "," )
54 |
55 | def list = "[" ~ items ~ "]" ^^ {
56 | case _ ~ lst ~ _ => lst
57 | }
58 |
59 | def parse( in: String ): List[String] = {
60 | parseAll(list, in) match {
61 | case Success( lst , _ ) => lst
62 | case x: NoSuccess => throw new IllegalArgumentException( x.toString )
63 | }
64 | }
65 |
66 | }
67 |
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/core/src/converter/ValueConverter.scala:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2011, Paradigmatic
3 |
4 | This file is part of Configrity.
5 |
6 | Configrity is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU Lesser General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | Configrity is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU Lesser General Public License for more details.
15 |
16 | You should have received a copy of the GNU Lesser General Public License
17 | along with Configrity. If not, see .
18 | */
19 |
20 | package org.streum.configrity.converter
21 |
22 | /**
23 | * Parse and convert an Option[String] into an Option[A].
24 | */
25 |
26 | trait ValueConverter[A] { self =>
27 | /**
28 | * Defines how to parse the string.
29 | */
30 | def parse( s: String ): A
31 |
32 | /**
33 | * Parse the string if defined or return None.
34 | */
35 | def apply( s: Option[String] ) = s map parse
36 |
37 | /**
38 | * Creates a new ValueConverter by mapping the result of this converter
39 | * to another one.
40 | */
41 | def map[B]( f: A => B ) = new ValueConverter[B] {
42 | def parse( s: String ) = f( self parse s )
43 | }
44 |
45 |
46 | }
47 |
48 | /**
49 | * Ease the creation of a value converter. See the ValueConverter.scala source
50 | * file for examples.
51 | */
52 | object ValueConverter {
53 | def apply[A]( f: String => A ) = new ValueConverter[A] {
54 | def parse( s: String ) = f(s)
55 | }
56 | }
57 |
58 | /**
59 | * Several predefined value converters for basic types: Int, Double, Boolean, etc.
60 | */
61 | trait DefaultConverters {
62 |
63 | /**
64 | * Converts a string, to itself... Well just an identity converter.
65 | */
66 | implicit val stringConverter = ValueConverter[String]( s => s )
67 |
68 | /**
69 | * Convert strings to bytes.
70 | */
71 | implicit val byteConverter = ValueConverter[Byte](
72 | s => java.lang.Byte.parseByte(s)
73 | )
74 |
75 | /**
76 | * Convert strings to shorts.
77 | */
78 | implicit val shortConverter = ValueConverter[Short](
79 | s => java.lang.Short.parseShort(s)
80 | )
81 |
82 | /**
83 | * Convert strings to ints.
84 | */
85 | implicit val intConverter = ValueConverter[Int](
86 | s => java.lang.Integer.parseInt(s)
87 | )
88 |
89 | /**
90 | * Convert strings to longs.
91 | */
92 | implicit val longConverter = ValueConverter[Long](
93 | s => java.lang.Long.parseLong(s)
94 | )
95 |
96 | /**
97 | * Convert strings to floats.
98 | */
99 | implicit val floatConverter = ValueConverter[Float](
100 | s => java.lang.Float.parseFloat(s)
101 | )
102 |
103 | /**
104 | * Convert strings to doubles.
105 | */
106 | implicit val doubleConverter = ValueConverter[Double](
107 | s => java.lang.Double.parseDouble(s)
108 | )
109 |
110 | /**
111 | * Convert strings to Booleans. The strings values: "T", "true", "yes" and "on"
112 | * will be converted to true and the strings: "F", "false", "no" and "off" will
113 | * be converted to false.
114 | */
115 | implicit val booleanConverter = BooleanConverter
116 |
117 | object BooleanConverter extends ValueConverter[Boolean] {
118 | val trues = Set("T", "true", "yes", "on")
119 | val falses = Set("F", "false", "no", "off")
120 | def parse( s: String ) = {
121 | if( trues contains s ) true
122 | else if ( falses contains s ) false
123 | else throw new IllegalArgumentException(
124 | s + " could not be converted in to a Boolean"
125 | )
126 | }
127 | }
128 |
129 | /**
130 | * Converts string to Lists on arbitrary element type A. A value
131 | * converter for element type must be available.
132 | */
133 | implicit def listConverter[A: ValueConverter]: ValueConverter[List[A]] =
134 | new ListConverter[A]
135 |
136 | }
137 |
--------------------------------------------------------------------------------
/core/src/io/BlockFormat.scala:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2011, Paradigmatic
3 |
4 | This file is part of Configrity.
5 |
6 | Configrity is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU Lesser General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | Configrity is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU Lesser General Public License for more details.
15 |
16 | You should have received a copy of the GNU Lesser General Public License
17 | along with Configrity. If not, see .
18 | */
19 |
20 | package org.streum.configrity.io
21 |
22 | import org.streum.configrity.Configuration
23 | import scala.collection.mutable.StringBuilder
24 | import scala.util.parsing.combinator._
25 |
26 | /**
27 | * Encodes the default block format
28 | */
29 | object BlockFormat extends StandardFormat with HierarchyUtils {
30 |
31 | def toText( configuration: Configuration ) = writeHierarchy( configuration.data )
32 |
33 | def writeEntry(k:String,v:String,ind:Int,out:StringBuffer) {
34 | out.append( " " * ind )
35 | .append( k ).append(" = ").append(
36 | sanitizeEmpty( v )
37 | ).append("\n")
38 | }
39 | def writeBlockStart( k:String, ind:Int, out:StringBuffer ) =
40 | out.append(" "*ind).append(k).append(" {").append("\n")
41 |
42 | def writeBlockEnd( k:String, ind:Int, out:StringBuffer ) =
43 | out.append(" "*ind).append("}").append("\n")
44 |
45 | def parser = new BlockParser
46 |
47 | class BlockParser extends Parser {
48 |
49 | private var blocks = List[String]()
50 |
51 | private def addPrefix( config: Configuration ) =
52 | blocks match {
53 | case Nil => config
54 | case head :: _ => Configuration().attach( head, config )
55 | }
56 |
57 | val dot = "."
58 | val openBrace = "{"
59 | val closeBrace = "}"
60 |
61 | def blockStart: Parser[Unit] = key ~ openBrace ^^ {
62 | case k ~ _ => blocks ::= k
63 | }
64 |
65 | def emptyBlock = blockStart <~ closeBrace ^^ {
66 | case _ => {
67 | blocks = blocks.tail.tail
68 | Configuration()
69 | }
70 | }
71 |
72 | def block: Parser[Configuration] =
73 | blockStart ~ ( block | emptyBlock | entry ) ~ content ~ closeBrace ^^ {
74 | case _ ~ single ~ rest ~ _ => {
75 | val newLst = addPrefix( single ++ rest )
76 | blocks = blocks.tail
77 | newLst
78 | }
79 | }
80 |
81 | def content = rep( includeDirective | block | emptyBlock | entry ) ^^ { reduce }
82 | }
83 |
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/core/src/io/FlatFormat.scala:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2011, Paradigmatic
3 |
4 | This file is part of Configrity.
5 |
6 | Configrity is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU Lesser General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | Configrity is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU Lesser General Public License for more details.
15 |
16 | You should have received a copy of the GNU Lesser General Public License
17 | along with Configrity. If not, see .
18 | */
19 |
20 | package org.streum.configrity.io
21 |
22 | import org.streum.configrity.Configuration
23 |
24 | import scala.collection.mutable.StringBuilder
25 | import scala.util.parsing.combinator._
26 |
27 | /**
28 | * Encodes a simple flat text format. Composed of:
29 | *
30 | * key1 = value1
31 | * key2 = value2
32 | * ...
33 | *
34 | * where keys and values cannot contain an '=' character. Keys
35 | * cannot contain whitespaces, while values can.
36 | * Whitespaces elsewhere are ignored.
37 | * Comment should start with '#' and are ignored.
38 | */
39 | object FlatFormat extends StandardFormat {
40 |
41 | val sep = "\n"
42 |
43 | def toText( configuration: Configuration ) = {
44 | val out = new StringBuilder
45 | val data = configuration.data
46 | for( k <- data.keySet.toList.sorted ) {
47 | out.append(k).append(" = ").append(
48 | sanitizeEmpty( data(k) )
49 | ).append(sep)
50 | }
51 | out.toString
52 | }
53 |
54 | def parser = FlatParser
55 |
56 | /** Parser for FlatFormat */
57 | object FlatParser extends Parser {
58 | def content = rep( entry ) ^^ { reduce }
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/core/src/io/Format.scala:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2011, Paradigmatic
3 |
4 | This file is part of Configrity.
5 |
6 | Configrity is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU Lesser General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | Configrity is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU Lesser General Public License for more details.
15 |
16 | You should have received a copy of the GNU Lesser General Public License
17 | along with Configrity. If not, see .
18 | */
19 |
20 | package org.streum.configrity.io
21 |
22 | import org.streum.configrity.Configuration
23 |
24 | /**
25 | * Format for converting a String into a Configuration
26 | */
27 | trait ImportFormat {
28 |
29 | /**
30 | * Converts a string into a configuration
31 | */
32 | def fromText( s: String ): Configuration
33 |
34 | }
35 |
36 | /**
37 | * Format for converting a Configuration into a String.
38 | */
39 | trait ExportFormat {
40 |
41 | /**
42 | * Converts a configuration into a string.
43 | */
44 | def toText( configuration: Configuration ): String
45 |
46 | }
47 |
48 | /**
49 | * Format able to convert Configuration to String and
50 | * String to Configuration
51 | */
52 | trait Format extends ImportFormat with ExportFormat
53 |
54 |
--------------------------------------------------------------------------------
/core/src/io/HierarchyUtils.scala:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2011, Paradigmatic
3 |
4 | This file is part of Configrity.
5 |
6 | Configrity is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU Lesser General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | Configrity is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU Lesser General Public License for more details.
15 |
16 | You should have received a copy of the GNU Lesser General Public License
17 | along with Configrity. If not, see .
18 | */
19 |
20 | package org.streum.configrity.io
21 |
22 | /**
23 | * Useful methods for implementing hierarchical output formats.
24 | */
25 |
26 | trait HierarchyUtils {
27 |
28 | private def splitKey( s: String ) = s.split("""\.""").toList
29 | private def joinKey( ss: List[String] ) = ss.mkString(".")
30 |
31 | def writeEntry( k:String, v:String, ind:Int, out:StringBuffer ): Unit
32 | def writeBlockStart( k:String, ind:Int, out:StringBuffer ): Unit
33 | def writeBlockEnd( k:String, ind:Int, out:StringBuffer ): Unit
34 |
35 |
36 | def writeHierarchy(
37 | map: Map[String,String],
38 | indents: Int = 0
39 | ): String = {
40 | val out = new StringBuffer
41 | var blocks = Map[String,Map[String,String]]()
42 | for( (k,v) <- map ) {
43 | splitKey(k) match {
44 | case el :: Nil => {
45 | writeEntry(k,v,indents,out)
46 | }
47 | case first :: rest => {
48 | val subKey = joinKey(rest)
49 | blocks += first ->
50 | ( blocks.getOrElse( first, Map() ) + ( subKey -> v) )
51 | }
52 | case _ =>
53 | }
54 | }
55 | for( (k,block) <- blocks ) {
56 | writeBlockStart( k, indents, out )
57 | out.append( writeHierarchy( block, indents + 1 ) )
58 | writeBlockEnd( k, indents, out )
59 | }
60 | out.toString
61 | }
62 |
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/core/src/io/PropertiesFormat.scala:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2011, Paradigmatic
3 |
4 | This file is part of Configrity.
5 |
6 | Configrity is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU Lesser General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | Configrity is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU Lesser General Public License for more details.
15 |
16 | You should have received a copy of the GNU Lesser General Public License
17 | along with Configrity. If not, see .
18 | */
19 |
20 | package org.streum.configrity.io
21 |
22 |
23 | import org.streum.configrity.Configuration
24 | import org.streum.configrity.JProperties._
25 | import java.io.StringWriter
26 | import java.io.StringReader
27 | import java.util.Properties
28 |
29 | /**
30 | * Text format described by java.util.Properties javadoc
31 | */
32 | object PropertiesFormat extends Format {
33 |
34 | def toText( config: Configuration ) = {
35 | val out = new StringWriter
36 | config.store( out, "")
37 | out.close
38 | out.toString
39 | }
40 |
41 | def fromText( s: String ) = {
42 | val in = new StringReader( s )
43 | val props = new Properties
44 | props.load( in )
45 | in.close
46 | props: Configuration
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/core/src/io/StandardFormat.scala:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2011, Paradigmatic
3 |
4 | This file is part of Configrity.
5 |
6 | Configrity is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU Lesser General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | Configrity is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU Lesser General Public License for more details.
15 |
16 | You should have received a copy of the GNU Lesser General Public License
17 | along with Configrity. If not, see .
18 | */
19 |
20 | package org.streum.configrity.io
21 |
22 | import org.streum.configrity.Configuration
23 |
24 | import scala.collection.mutable.StringBuilder
25 |
26 | import scala.util.parsing.combinator._
27 |
28 | /**
29 | * Encodes the common behavior of standard formats
30 | */
31 | trait StandardFormat extends Format {
32 |
33 | def sanitizeEmpty( s: String ) = if ( s.isEmpty ) "\"\"" else s
34 |
35 | def parser: Parser
36 |
37 | def fromText( s: String ) = parser.parse( s )
38 |
39 | trait Parser extends RegexParsers {
40 |
41 | def reduce( lst: List[Configuration] ) =
42 | lst.foldLeft( Configuration() )( _ ++ _ )
43 |
44 | def unquote( s: String ) = s.substring( 1, s.size - 1 )
45 |
46 | def protect( s: String ) = word.findFirstIn(s) match {
47 | case Some(z) if s == z => s
48 | case _ => "\"" + s + "\""
49 | }
50 |
51 | override val whiteSpace = """(\s+|#[^\n]*\n)+""".r
52 | def key = """([^=\s])+""".r
53 | val lineSep = "\n"
54 | def word = """([^=\s\n#\{\}\"\[\],])+""".r
55 | def quoted = """"([^"]*)"""".r /*"*/ ^^ { unquote }
56 | val equals = "="
57 |
58 | def includeDirective = "include" ~ quoted ^^ {
59 | case _ ~ filename => Configuration.load( filename )
60 | }
61 |
62 | def item = word | quoted
63 |
64 | def items = repsep( item, "," )
65 | def list = "[" ~ items ~ "]" ^^ {
66 | case _ ~ lst ~ _ => lst.map( protect ).mkString("[ ", ", ", " ]")
67 | }
68 |
69 | def value = item | list
70 |
71 | def entry = key ~ equals ~ value ^^ {
72 | case k ~ _ ~ v => Configuration( k -> v )
73 | }
74 |
75 | def content: Parser[Configuration]
76 |
77 | def parse( in: String ) = {
78 | parseAll(content, in) match {
79 | case Success( config , _ ) => config
80 | case x: NoSuccess => throw StandardFormat.ParserException(x.toString)
81 | }
82 | }
83 |
84 | }
85 |
86 |
87 | }
88 |
89 | object StandardFormat{
90 |
91 | /** Parser exceptions */
92 | case class ParserException(s: String) extends Exception(s)
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/core/src/io/Utils.scala:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2012, Paradigmatic
3 |
4 | This file is part of Configrity.
5 |
6 | Configrity is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU Lesser General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | Configrity is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU Lesser General Public License for more details.
15 |
16 | You should have received a copy of the GNU Lesser General Public License
17 | along with Configrity. If not, see .
18 | */
19 |
20 | package org.streum.configrity.io
21 |
22 | object Utils {
23 |
24 | val Alphanum = """\w+""".r
25 |
26 | def sanitize( in: String ): String = in match {
27 | case Alphanum(out) => out
28 | case out => "\"" + out + "\""
29 | }
30 |
31 | def sanitize[A]( as: List[A] ): List[String] =
32 | as.map( a => sanitize( a.toString ) )
33 |
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/core/src/package.scala:
--------------------------------------------------------------------------------
1 | package org.streum.configrity
2 |
3 | import converter.{ValueConverter,DefaultConverters}
4 |
5 | /** Package object containing the default converters + default formats */
6 | object `package` extends DefaultConverters {
7 |
8 | val BlockFormat = io.BlockFormat
9 |
10 | val FlatFormat = io.FlatFormat
11 |
12 | /** Returns a reader which retrieves and converts a configuration
13 | * value. If the Configuration does not contain the value, an
14 | * Exception will be thrown.
15 | */
16 | def read[A: ValueConverter]( key: String ) =
17 | ConfigurationReader[A](key, None)
18 |
19 | /* Returns a reader which retrieves and converts a configuration
20 | * value. If the Configuration does not contain the value, the
21 | * provided default value will be returned.
22 | */
23 | def read[A: ValueConverter]( key: String, default: A ) =
24 | ConfigurationReader[A](key, Some(default) )
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/core/test-resources/test-config-comments.conf:
--------------------------------------------------------------------------------
1 | foo = true
2 | bar = 2
3 | baz = "hello world"
4 | # key =
5 |
6 |
--------------------------------------------------------------------------------
/core/test-resources/test-config.conf:
--------------------------------------------------------------------------------
1 | foo = true
2 | bar = 2
3 | baz = "hello world"
--------------------------------------------------------------------------------
/core/test/ConfigurationSpec.scala:
--------------------------------------------------------------------------------
1 | package org.streum.configrity.test
2 |
3 | import org.scalatest.FlatSpec
4 | import org.scalatest.Matchers._
5 | import org.streum.configrity.Configuration
6 | import org.streum.configrity.converter.DefaultConverters
7 | import org.streum.configrity.io._
8 |
9 |
10 | class ConfigurationSpec extends FlatSpec with DefaultConverters with io.IOHelper {
11 |
12 | val data = Map("foo"->"FOO", "bar"->"1234", "baz"->"on" )
13 | val config = Configuration( data )
14 |
15 | "A configuration" should "return none if it doesn't contain a key when 'getted'" in {
16 | config.get[Int]("buzz") should be (None)
17 | }
18 |
19 | it can "tell if a key is defined" in {
20 | config.contains("foo") should be (true)
21 | config.contains("bar") should be (true)
22 | config.contains("baz") should be (true)
23 | config.contains("buzz") should be (false)
24 | }
25 |
26 | it should "throw exception if it doesn't contain a key when 'applied'" in {
27 | intercept[java.util.NoSuchElementException] {
28 | config[Int]("buzz")
29 | }.getMessage should be ("buzz")
30 | }
31 |
32 | it should "be able to return converted values" in {
33 | config[String]("foo") should be ("FOO")
34 | config[Int]("bar") should be (1234)
35 | config[Boolean]("baz") should be (true)
36 | }
37 |
38 | it should "return a default value when asked" in {
39 | config[String]("foo", "HELLO") should be ("FOO")
40 | config[Int]("bar", 0) should be (1234)
41 | config[Boolean]("baz", false) should be (true)
42 | config[Int]("buzz", 12) should be (12)
43 | }
44 |
45 | it should "be able to add new key" in {
46 | val c2 = config.set("buzz", 0.5)
47 | c2[Double]("buzz") should be (0.5)
48 | config.get[Double]("buzz") should be (None)
49 | }
50 |
51 | it should "be able to replace existing key" in {
52 | val c2 = config.set("foo", 0.5)
53 | c2[Double]("foo") should be (0.5)
54 | config[String]("foo") should be ("FOO")
55 | }
56 |
57 | it should "be able to remove an existing key" in {
58 | val c2 = config.clear("foo")
59 | config[String]("foo") should be ("FOO")
60 | c2.get[String]("foo") should be (None)
61 | }
62 |
63 | it should "not complain when trying to remove an non-existent key" in {
64 | val c2 = config.clear("buzz")
65 | c2 should be (config)
66 | }
67 |
68 | it should "format lists correctly" in {
69 | val lst = List( 1, 2, 3, 5 )
70 | val c2 = config.set( "list", lst )
71 | val lst2 = c2[List[Int]]( "list" )
72 | lst should be (lst2)
73 | }
74 |
75 | it should "format empty lists correctly" in {
76 | val lst = List[Int]( )
77 | val c2 = config.set( "list", lst )
78 | val lst2 = c2[List[Int]]( "list" )
79 | lst should be (lst2)
80 | }
81 |
82 | it should "format lists with empty spaces in values correctly" in {
83 | val lst = List( "hello world" )
84 | val c2 = config.set( "list", lst )
85 | val lst2 = c2[List[String]]( "list" )
86 | lst should be (lst2)
87 | }
88 |
89 |
90 | it can "be nicely formatted" in {
91 | val out = new ExportFormat {
92 | def toText( c: Configuration ) = "FOOBAR"
93 | }
94 | config.format(out) should be ("FOOBAR")
95 | }
96 |
97 | it can "be saved to a file" in {
98 | val fn1 = "/tmp/configrity_configuration_spec_1.conf"
99 | val fn2 = "/tmp/configrity_configuration_spec_2.conf"
100 | autoFile( fn1 ){ file1 =>
101 | autoFile( fn2 ){ file2 =>
102 | config.save( file1 )
103 | config.save( file2, FlatFormat )
104 | val config2 = Configuration.load( file1.getAbsolutePath )
105 | val config3 = Configuration.load( file2.getAbsolutePath )
106 | config2 should be (config)
107 | config3 should be (config)
108 | }
109 | }
110 | }
111 |
112 | "A sub configuration" can "be attached at a given prefix" in {
113 | val data2 = Map( "one" -> "1", "two" -> "2" )
114 | val config2 = Configuration( data2 )
115 | val config3 = config attach ("nums", config2)
116 | config3[String]("foo") should be ("FOO")
117 | config3[Int]("nums.one") should be (1)
118 | }
119 |
120 |
121 | it can "will replace values when attached at an existing prefix" in {
122 | val data2 = Map( "one" -> "1", "two" -> "2" )
123 | val config2 = Configuration( data2 )
124 | val config3 = config attach ("nums", config2)
125 | val data4 = Map( "one" -> "I", "five" -> "V" )
126 | val config4 = Configuration( data4 )
127 | val config5 = config3 attach( "nums", config4 )
128 | config5[String]("foo") should be ("FOO")
129 | config5[String]("nums.one") should be ("I")
130 | config5[Int]("nums.two") should be (2)
131 | config5[String]("nums.five") should be ("V")
132 | }
133 |
134 | it can "be detach from a configuration" in {
135 | val data2 = Map( "one" -> "1", "two" -> "2" )
136 | val config2 = Configuration( data2 )
137 | val config3 = config attach ("nums", config2 )
138 | val config4 = config3 detach ("nums")
139 | config4.data should be (config2.data)
140 | }
141 |
142 | it can "include another configuration" in {
143 | val config2 = Configuration( "foo" -> "fu", "buzz" -> 122 )
144 | val config3 = config include config2
145 | config3[String]("foo") should be ("FOO")
146 | config3[Int]("bar") should be (1234)
147 | config3[Int]("buzz") should be (122)
148 | (config2 include config) should not be (config include config2)
149 | }
150 |
151 | it should "carry its original prefix" in {
152 | val sup = Configuration()
153 | val sub1 = Configuration()
154 | val sub2 = Configuration()
155 | val full = sup.attach("foo", sub1.attach("bar", sub2))
156 | full.detach("foo.bar").prefix should be (Some("foo.bar"))
157 | }
158 |
159 | "Sub configurations" can "be detached from a configuration" in {
160 | val sup = Configuration( "foo" -> "bar" )
161 | val sub1 = Configuration(Map( "one" -> "1", "two" -> "2" ))
162 | val sub2 = Configuration(Map( "first" -> "", "second" -> "b" ))
163 | val full = sup attach ("nums", sub1) attach ("letters", sub2)
164 | full.detachAll should be (Map(
165 | "nums" -> sub1.copy(prefix = Some("nums")),
166 | "letters" -> sub2.copy(prefix = Some("letters"))
167 | ))
168 | }
169 | }
170 |
171 | class ConfigurationObjectSpec extends FlatSpec with io.IOHelper {
172 |
173 | "A configuration" can "be created from the system properties" in {
174 | val config = Configuration.systemProperties
175 | config.get[String]("line.separator") should be ('defined)
176 | }
177 |
178 | it can "be created from environement variables" in {
179 | val config = Configuration.environment
180 | config.get[String]("HOME") should be ('defined)
181 | }
182 |
183 | it can "be created empty" in {
184 | val config = Configuration()
185 | config.data.size should be (0)
186 | }
187 |
188 | it can "be created with key value pairs" in {
189 | val config = Configuration("foo"->"bar", "bazz"->2)
190 | config[String]("foo") should be ("bar")
191 | config[Int]("bazz") should be (2)
192 | }
193 |
194 | it can "support lists directly with key value pairs" in {
195 | val config = Configuration(
196 | "foo"-> List(10),
197 | "bar"-> ("hello"::"world"::Nil)
198 | )
199 | config[List[Int]]("foo") should be (List(10))
200 | config[List[String]]("bar") should be (List("hello","world"))
201 | }
202 |
203 | it can "support lists directly with empty spaces in values" in {
204 | val config = Configuration(
205 | "foo"-> List(10),
206 | "bar"-> ("hello world"::Nil)
207 | )
208 | config[List[Int]]("foo") should be (List(10))
209 | config[List[String]]("bar") should be (List("hello world"))
210 | }
211 |
212 |
213 | it can "be created from a string using a given format" in {
214 | val s =
215 | """
216 | foo = true
217 | bar = 2
218 | baz = "hello world"
219 | """
220 | val config = Configuration.parse( s, FlatFormat )
221 | config.get[Boolean]("foo") should be (Some(true))
222 | config.get[Int]("bar") should be (Some(2))
223 | config.get[String]("baz") should be (Some("hello world"))
224 | }
225 |
226 | it can "be loaded from a file" in {
227 | val fmt = FlatFormat
228 | val s =
229 | """
230 | foo = true
231 | bar = 2
232 | baz = "hello world"
233 | """
234 | autoFile( s ){ file =>
235 | val fn = file.getAbsolutePath
236 | val config = Configuration.load(fn,fmt)
237 | config.get[Boolean]("foo") should be (Some(true))
238 | config.get[Int]("bar") should be (Some(2))
239 | config.get[String]("baz") should be (Some("hello world"))
240 | val config2 = Configuration.load(fn)
241 | config2 should be (config)
242 | }
243 | }
244 |
245 | it can "be loaded from the classpath" in {
246 | val fmt = FlatFormat
247 | val resName = "/test-config.conf"
248 | val config = Configuration.loadResource( resName, fmt )
249 | config.get[Boolean]("foo") should be (Some(true))
250 | config.get[Int]("bar") should be (Some(2))
251 | config.get[String]("baz") should be (Some("hello world"))
252 | val config2 = Configuration.loadResource( resName )
253 | config2 should be (config)
254 | }
255 |
256 | it must "throw FileNotFoundException when loading non-existing resource from the classpath" in {
257 | val resName = "/non-existing.conf"
258 | the [java.io.FileNotFoundException] thrownBy {
259 | Configuration.loadResource( resName )
260 | } should have message (resName)
261 | }
262 | }
263 |
264 |
--------------------------------------------------------------------------------
/core/test/ConverterHelper.scala:
--------------------------------------------------------------------------------
1 | package org.streum.configrity.test
2 |
3 | import org.streum.configrity.converter._
4 |
5 |
6 | object ConverterHelper {
7 |
8 | def convert[A]( opt: Option[String] )(implicit converter: ValueConverter[A] ) =
9 | converter(opt)
10 |
11 | def convert[A]( opt: String )(implicit converter: ValueConverter[A] ) =
12 | converter.parse(opt)
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/core/test/ExtraConverterSpec.scala:
--------------------------------------------------------------------------------
1 | package org.streum.configrity.test
2 |
3 | import org.scalatest.WordSpec
4 | import org.scalatest.Matchers._
5 | import org.streum.configrity.converter._
6 | import java.io.File
7 | import java.awt.Color
8 | import java.net.URI
9 | import java.net.URL
10 |
11 | class ExtraConverterSpec extends WordSpec with DefaultConverters{
12 |
13 | import ConverterHelper._
14 | import Extra._
15 |
16 | "The file converter" should {
17 | "parse a string to File" in {
18 | convert[File]("/tmp") should be (new File("/tmp"))
19 | convert[File]("") should be (new File(""))
20 | }
21 | }
22 |
23 | "The color converter" should {
24 | "parse a string to Color" in {
25 | convert[Color]("000000") should be (new Color(0))
26 | }
27 | "ignore preceding hash" in {
28 | convert[Color]("#000000") should be (new Color(0))
29 | convert[Color]("#ffffff") should be (new Color(255,255,255))
30 | }
31 | "accept a mix of both case" in {
32 | convert[Color]("#fFFfFF") should be (new Color(255,255,255))
33 | }
34 | "return a exception when the string has invalid chars" in {
35 | intercept[Exception] {
36 | convert[Color]("#00FFX2")
37 | }
38 | }
39 | "return a exception when the string has not 6 hex digits (except hash)" in {
40 | intercept[Exception] {
41 | convert[Color]("#fab")
42 | }
43 | }
44 | }
45 |
46 | "The URI converter" should {
47 | "parse a string to an URI" in {
48 | convert[URI]("http://www.example.com/hello") should
49 | be (new URI("http://www.example.com/hello"))
50 | convert[URI]("./hello") should
51 | be (new URI("./hello"))
52 | }
53 | }
54 |
55 | "The URL converter" should {
56 | "parse a string to an URI" in {
57 | convert[URL]("http://www.example.com/hello") should
58 | be (new URL("http://www.example.com/hello"))
59 | }
60 | "return a exception when the url is not aboslute" in {
61 | intercept[IllegalArgumentException] {
62 | convert[URL]("./hello")
63 | }
64 | }
65 | "return a exception when the string is empty" in {
66 | intercept[IllegalArgumentException] {
67 | convert[URL]("")
68 | }
69 | }
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/core/test/JPropertiesSpec.scala:
--------------------------------------------------------------------------------
1 | package org.streum.configrity.test
2 |
3 | import org.scalatest.FlatSpec
4 | import org.scalatest.Matchers._
5 | import org.streum.configrity._
6 | import org.streum.configrity.JProperties
7 | import org.streum.configrity.io._
8 | import java.util.Properties
9 |
10 | class JPropertiesSpec extends FlatSpec {
11 |
12 | val data = Map("foo"->"FOO", "bar"->"1234", "baz"->"on" )
13 | val config = Configuration( data )
14 |
15 | "A Configuration" can "be converted in java properties" in {
16 | val props: Properties =
17 | JProperties.configurationToProperties( config )
18 | val config2: Configuration =
19 | JProperties.propertiesToConfiguration( props )
20 | config2 should be (config)
21 | }
22 |
23 | it can "be saved and read in java properties format" in {
24 | val fmt = JProperties.format
25 | val s = config format fmt
26 | val config2 = Configuration.parse( s, fmt )
27 | config2 should be (config)
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/core/test/ReaderSpec.scala:
--------------------------------------------------------------------------------
1 | package org.streum.configrity.test
2 |
3 | import org.scalatest.FlatSpec
4 | import org.scalatest.Matchers._
5 | import org.streum.configrity._
6 | import converter.DefaultConverters
7 |
8 | class ReeaderSpec extends FlatSpec with DefaultConverters{
9 |
10 | val config = Configuration( "foo"->"FOO", "bar"->"1234", "baz"->"on" )
11 |
12 | "A configuration reader" can "be obtained out of a key" in {
13 | val bar = read[Int]("bar")
14 | bar(config) should be (1234)
15 | }
16 |
17 | it should "throw an exception if the key does not exists" in {
18 | val buzz = read[Int]("buzz")
19 | intercept[Exception] {
20 | buzz(config) should be (1234)
21 | }
22 | }
23 |
24 | it can "have a default value" in {
25 | val bar = read("bar", -101)
26 | val buzz = read("buzz", 12 )
27 | bar(config) should be (1234)
28 | buzz(config) should be (12)
29 | }
30 |
31 | def extract[A]( reader: Reader[A] ) = reader(config)
32 | def unit[A](a: A) = new Reader[A] {
33 | def apply( c: Configuration ) = a
34 | }
35 | def twice( i: Int ) = unit( 2*i )
36 | def mkString( i: Int) = unit( i.toString )
37 |
38 | "Readers" must "respect monad 1st axiom" in {
39 | extract( unit(12).flatMap( twice ) ) should be ( extract(twice(12)) )
40 | extract( unit(147).flatMap( unit ) ) should be ( extract(unit(147) ) )
41 | }
42 |
43 | it must "respect monad 2nd axiom" in {
44 | val m = unit( 41 )
45 | val readerA = m flatMap twice flatMap mkString
46 | val readerB = m flatMap { x => ( twice(x) flatMap mkString ) }
47 | extract( readerA ) should be (extract(readerB))
48 | }
49 |
50 | it can "be composed" in {
51 | val fooBarBaz = for( s <- read[String]("foo");
52 | i <- read[Int]("bar");
53 | b <- read[Boolean]("baz")
54 | ) yield { if( b ) i.toString else s }
55 |
56 | fooBarBaz( config ) should be ("1234")
57 | }
58 |
59 |
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/core/test/ValueConverterSpec.scala:
--------------------------------------------------------------------------------
1 | package org.streum.configrity.test
2 |
3 | import org.scalatest.WordSpec
4 | import org.scalatest.Matchers._
5 | import org.streum.configrity.converter._
6 |
7 | class ValueConverterSpec extends WordSpec with DefaultConverters{
8 |
9 | import ConverterHelper._
10 |
11 | "The string converter" should {
12 | "parse string without changing it" in {
13 | convert[String](None) should be (None)
14 | convert[String](Option("foo")) should be (Option("foo"))
15 | }
16 | }
17 |
18 | "The byte converter" should {
19 | "parse a string in Byte" in {
20 | convert[Byte](Option("12")) should be (Option(12:Byte))
21 | }
22 | "return a exception when the string cannot be parsed" in {
23 | intercept[Exception] {
24 | convert[Byte](Option("129"))
25 | }
26 | }
27 | }
28 |
29 | "The short converter" should {
30 | "parse a string in short" in {
31 | convert[Short](Option("1234")) should be (Option(1234:Short))
32 | }
33 | "return a exception when the string cannot be parsed" in {
34 | intercept[Exception] {
35 | convert[Short](Option("12341234"))
36 | }
37 | }
38 | }
39 |
40 | "The int converter" should {
41 | "parse a string in Int" in {
42 | convert[Int](Option("1234")) should be (Option(1234))
43 | }
44 | "return a exception when the string cannot be parsed" in {
45 | intercept[Exception] {
46 | convert[Int](Option("12.34"))
47 | }
48 | }
49 | }
50 |
51 | "The long converter" should {
52 | "parse a string in Long" in {
53 | convert[Long](Option("1234")) should be (Option(1234:Long))
54 | }
55 | "return a exception when the string cannot be parsed" in {
56 | intercept[Exception] {
57 | convert[Long](Option("12.34"))
58 | }
59 | }
60 | }
61 |
62 | "The float converter" should {
63 | "parse a string into a Float" in {
64 | convert[Float](Option("1234")) should be (Option(1234.0f))
65 | convert[Float](Option("1e-9")) should be (Option(1e-9f))
66 | convert[Float](Option(".1")) should be (Option(.1f))
67 | }
68 | "return a exception when the string cannot be parsed" in {
69 | intercept[Exception] {
70 | convert[Float](Option("1ef-9"))
71 | }
72 | }
73 | }
74 |
75 |
76 | "The double converter" should {
77 | "parse a string into a Double" in {
78 | convert[Double](Option("1234")) should be (Option(1234.0))
79 | convert[Double](Option("1e-9")) should be (Option(1e-9))
80 | convert[Double](Option(".1")) should be (Option(.1))
81 | }
82 | "return a exception when the string cannot be parsed" in {
83 | intercept[Exception] {
84 | convert[Double](Option("1ef-9"))
85 | }
86 | }
87 | }
88 |
89 | "The boolean converter" should {
90 | "parse a string with true/false into a Boolean" in {
91 | convert[Boolean](Option("false")) should be (Option(false))
92 | convert[Boolean](Option("true")) should be (Option(true))
93 | }
94 | "parse a string with T/F into a Boolean" in {
95 | convert[Boolean](Option("F")) should be (Option(false))
96 | convert[Boolean](Option("T")) should be (Option(true))
97 | }
98 | "parse a string with yes/no into a Boolean" in {
99 | convert[Boolean](Option("no")) should be (Option(false))
100 | convert[Boolean](Option("yes")) should be (Option(true))
101 | }
102 | "parse a string with on/off into a Boolean" in {
103 | convert[Boolean](Option("off")) should be (Option(false))
104 | convert[Boolean](Option("on")) should be (Option(true))
105 | }
106 | "return a exception when the string cannot be parsed" in {
107 | intercept[Exception] {
108 | convert[Boolean](Option("False"))
109 | }
110 | }
111 | }
112 |
113 | "The list converter" can {
114 | "parse a string into an Empty List" in {
115 | convert[List[String]](
116 | Some( "[]" )
117 | ) should be (Some( Nil ))
118 | }
119 | "parse a string into a List of String" in {
120 | convert[List[String]](
121 | Some( "[ hello, world ]" )
122 | ) should be (Some( List("hello", "world" ) ))
123 | }
124 | "parse a string into a List of booleans" in {
125 | convert[List[Boolean]](
126 | Some( "[on,off,on,off,off]" )
127 | ) should be (Some( List(true,false,true,false,false) ))
128 | }
129 | "return a exception when the string cannot be parsed" in {
130 | intercept[Exception] {
131 | convert[List[Boolean]](Option("on,off"))
132 | }
133 | }
134 | }
135 |
136 | }
137 |
--------------------------------------------------------------------------------
/core/test/io/BlockFormatSpec.scala:
--------------------------------------------------------------------------------
1 | package org.streum.configrity.test.io
2 |
3 | import org.scalatest.FlatSpec
4 | import org.scalatest.Matchers._
5 | import org.streum.configrity._
6 | import org.streum.configrity.io.BlockFormat
7 | import org.streum.configrity.io.BlockFormat._
8 | import org.streum.configrity.io.StandardFormat.ParserException
9 |
10 | class BlockFormatSpec extends FlatSpec {
11 |
12 | "The block format" can "write and read an empty Configuration" in {
13 | val config = Configuration( )
14 | fromText( toText( config ) ) should be (config)
15 | }
16 |
17 | it can "write and read a Configuration" in {
18 | val config = Configuration(
19 | Map("foo"->"FOO", "bar"->"1234", "baz"->"on", "empty"->"")
20 | )
21 | fromText( toText( config ) ) should be (config)
22 | }
23 |
24 | it can "write and read a Configuration with nested blocks" in {
25 | val config = Configuration(
26 | Map(
27 | "foo.gnats.gnits"->"FOO",
28 | "bar.buzz"->"1234",
29 | "bar.baz"->"on",
30 | "lst" -> """[ on, on, off, off ]"""
31 | )
32 | )
33 | fromText( toText( config ) ) should be (config)
34 | }
35 |
36 | }
37 |
38 | class BlockFormatParserSpec extends StandardParserSpec with IOHelper {
39 |
40 | def parse( s: String ) = BlockFormat.parser.parse(s)
41 | lazy val parserName = "BlockFormatParser"
42 |
43 | it can "parse nested blocks" in {
44 | val s =
45 | """
46 | # Example
47 | foo = true
48 | block {
49 | bar = 2
50 | sub {
51 | buzz = hello
52 | }
53 | baz = x
54 | }
55 | """
56 | val config = parse( s )
57 | config[Boolean]("foo") should be (true)
58 | config[Int]("block.bar") should be (2)
59 | config[String]("block.baz") should be ("x")
60 | config[String]("block.sub.buzz") should be ("hello")
61 | }
62 |
63 | it can "parse nested blocks mixed with flat notation" in {
64 | val s =
65 | """
66 | # Example
67 | foo = true
68 | block {
69 | bar = 2
70 | sub {
71 | buzz = hello
72 | }
73 | sub.blah = true
74 | }
75 | block.baz = x
76 | """
77 | val config = parse( s )
78 | config[Boolean]("foo") should be (true)
79 | config[Int]("block.bar") should be (2)
80 | config[String]("block.baz") should be ("x")
81 | config[String]("block.sub.buzz") should be ("hello")
82 | config[Boolean]("block.sub.blah") should be (true)
83 | }
84 |
85 | it must "skip all comment with nested blocks" in {
86 | val s =
87 | """
88 | # Example
89 | foo = true
90 | block {
91 | bar = 2 # ignore
92 | sub {
93 | #comment
94 | buzz = hello
95 | }
96 | baz = x
97 | }
98 | """
99 | val config = parse( s )
100 | config[Boolean]("foo") should be (true)
101 | config[Int]("block.bar") should be (2)
102 | config[String]("block.baz") should be ("x")
103 | config[String]("block.sub.buzz") should be ("hello")
104 | }
105 |
106 | it should "ignore whitespaces" in {
107 | val s =
108 | """
109 | # Example
110 | foo = true
111 | block {
112 | bar= 2
113 | sub {
114 | buzz = hello
115 | }
116 | baz =x
117 | }
118 | """
119 | val config = parse( s )
120 | config[Boolean]("foo") should be (true)
121 | config[Int]("block.bar") should be (2)
122 | config[String]("block.baz") should be ("x")
123 | config[String]("block.sub.buzz") should be ("hello")
124 | }
125 |
126 | it must "choke if no key is provided for blocks" in {
127 | val s =
128 | """
129 | # Example
130 | foo = true
131 | block {
132 | bar = 2
133 | {
134 | buzz = hello
135 | }
136 | sub.blah = true
137 | }
138 | block.baz = x
139 | """
140 | intercept[ParserException] {
141 | val config = parse( s )
142 | }
143 | }
144 |
145 | it must "choke if a block is not closed" in {
146 | val s =
147 | """
148 | # Example
149 | foo = true
150 | block {
151 | bar = 2
152 | sub {
153 | buzz = hello
154 | sub.blah = true
155 | }
156 | block.baz = x
157 | """
158 | intercept[ParserException] {
159 | val config = parse( s )
160 | }
161 | }
162 |
163 | it should "merge blocks with same key" in {
164 | val s =
165 | """
166 | # Example
167 | foo = true
168 | block {
169 | bar = 2
170 | }
171 | block {
172 | bar = x
173 | }
174 | """
175 | val config = parse( s )
176 | config[String]("block.bar") should be ("x")
177 | }
178 |
179 | it must "ignore empty blocks" in {
180 | val s =
181 | """
182 | # Example
183 | foo = true
184 | block {
185 | bar = 2
186 | sub {
187 |
188 | }
189 | baz = x
190 | sub2 {
191 | sub3 {
192 | hoo = false
193 | sub4 {
194 | }
195 | }
196 | }
197 | }
198 | """
199 | val config = parse( s )
200 | config[Boolean]("foo") should be (true)
201 | config[Int]("block.bar") should be (2)
202 | config.get[String]("block.sub") should be (None)
203 | config.get[String]("block") should be (None)
204 | config[String]("block.baz") should be ("x")
205 | config[Boolean]("block.sub2.sub3.hoo") should be (false)
206 | config.get[Boolean]("block.sub2.sub3.sub4") should be (None)
207 | }
208 |
209 | it can "parse include directive" in {
210 | val parentContent = """
211 | block {
212 | foo = true
213 | bar = 2
214 | }
215 | """
216 | val childContent = """
217 | include "%s"
218 |
219 | block {
220 | foo = false
221 | baz = "hello"
222 | }
223 | """
224 | autoFile( parentContent ) { parent =>
225 | autoFile( childContent.format(parent.getAbsolutePath) ) { child =>
226 | val config = Configuration.load(child.getAbsolutePath)
227 | config[Boolean]("block.foo") should be (false)
228 | config[Int]("block.bar") should be (2)
229 | config[String]("block.baz") should be ("hello")
230 | }
231 | }
232 | }
233 |
234 | it must "choke if the include points to a non existing file" in {
235 | val childContent = """
236 | include "/tmp/parent.conf"
237 |
238 | block {
239 | foo = false
240 | baz = "hello"
241 | }
242 | """
243 | autoFile( childContent ) { child =>
244 | intercept[java.io.FileNotFoundException] {
245 | val config = Configuration.load( child.getAbsolutePath )
246 | }
247 | }
248 | }
249 | }
250 |
--------------------------------------------------------------------------------
/core/test/io/FlatFormatSpec.scala:
--------------------------------------------------------------------------------
1 | package org.streum.configrity.test.io
2 |
3 | import org.scalatest.FlatSpec
4 | import org.scalatest.Matchers._
5 | import org.streum.configrity._
6 | import org.streum.configrity.io.FlatFormat._
7 | import org.streum.configrity.io.FlatFormat
8 |
9 |
10 | class FlatFormatSpec extends FlatSpec {
11 |
12 | "The flat format" can "write and read an empty Configuration" in {
13 | val config = Configuration()
14 | fromText( toText( config ) ) should be (config)
15 | }
16 |
17 | it can "write and read a Configuration" in {
18 | val config = Configuration(
19 | Map("foo"->"FOO", "bar"->"1234",
20 | "baz"->"on",
21 | "lst" -> """[ on, on, off, off ]""",
22 | "empty" -> ""
23 | )
24 | )
25 | fromText( toText( config ) ) should be (config)
26 | }
27 |
28 | }
29 |
30 | class FlatFormatParserSpec extends StandardParserSpec with IOHelper{
31 |
32 | lazy val parserName = "FlatFormatParser"
33 | def parse( s: String ) = FlatFormat.parser.parse(s)
34 |
35 | it can "parse include directive" in {
36 | val parentContent = """
37 | foo = true
38 | bar = 2
39 | """
40 | val childContent = """
41 | include "%s"
42 | foo = false
43 | baz = "hello"
44 | """
45 | autoFile( parentContent ) { parent =>
46 | autoFile( childContent.format(parent.getAbsolutePath) ) { child =>
47 | val config = Configuration.load(child.getAbsolutePath)
48 | config[Boolean]("foo") should be (false)
49 | config[Int]("bar") should be (2)
50 | config[String]("baz") should be ("hello")
51 | }
52 | }
53 | }
54 |
55 | it must "choke if the include points to a non existing file" in {
56 | val childContent = """
57 | include "/tmp/parent.conf"
58 | foo = false
59 | baz = "hello"
60 | """
61 | autoFile( childContent ) { child =>
62 | intercept[java.io.FileNotFoundException] {
63 | val config = Configuration.load(child.getAbsolutePath)
64 | }
65 | }
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/core/test/io/IOHelper.scala:
--------------------------------------------------------------------------------
1 | package org.streum.configrity.test.io
2 |
3 | import java.io.File
4 | import java.io.PrintWriter
5 |
6 | trait IOHelper {
7 |
8 | def autoFile[A]( content: String = "" )
9 | ( body: File => A ) = {
10 | val f = File.createTempFile("configrity", ".conf")
11 | try {
12 | if( content != "" ) {
13 | val out = new PrintWriter( f )
14 | out println content
15 | out.close
16 | }
17 | body( f )
18 | } finally {
19 | f.delete
20 | }
21 | }
22 |
23 |
24 | }
25 |
26 | object IOHelper extends IOHelper
27 |
--------------------------------------------------------------------------------
/core/test/io/Standard.scala:
--------------------------------------------------------------------------------
1 | package org.streum.configrity.test.io
2 |
3 | import org.scalatest.FlatSpec
4 | import org.scalatest.Matchers._
5 | import org.streum.configrity._
6 | import org.streum.configrity.io.StandardFormat.ParserException
7 |
8 | class Standard extends FlatSpec {
9 |
10 | "a flat format comment line" must "be ignored" in {
11 | val config = Configuration.loadResource("/test-config-comments.conf")
12 | val comment = config("comment", "")
13 | assert(comment == "")
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/core/test/io/StandardParserSpec.scala:
--------------------------------------------------------------------------------
1 | package org.streum.configrity.test.io
2 |
3 | import org.scalatest.FlatSpec
4 | import org.scalatest.Matchers._
5 | import org.streum.configrity._
6 | import org.streum.configrity.io.StandardFormat.ParserException
7 |
8 | trait StandardParserSpec extends FlatSpec {
9 |
10 | def parse( s: String ): Configuration
11 | val parserName: String
12 |
13 |
14 | parserName can "parse empty string" in {
15 | val config = parse( "" )
16 | config.data should be ('empty)
17 | }
18 |
19 | it can "parse a wellformed entry line" in {
20 | val config = parse( "foo = 2" )
21 | config[Int]("foo") should be (2)
22 | }
23 |
24 | it should "ignore leading and trailing space" in {
25 | val config = parse( " foo = 2" )
26 | config[Int]("foo") should be (2)
27 | }
28 |
29 | it should "ignore extra spaces around the equal sign" in {
30 | val config = parse( " foo = 2" )
31 | config[Int]("foo") should be (2)
32 | }
33 |
34 | it should "tolerate an equal sign without space around" in {
35 | val config = parse( " foo=2 " )
36 | config[Int]("foo") should be (2)
37 | }
38 |
39 | it can "parse several lines" in {
40 | val s =
41 | """
42 | foo = true
43 | bar = 2
44 | baz = "hello world"
45 | """
46 | val config = parse( s )
47 | config[Boolean]("foo") should be (true)
48 | config[Int]("bar") should be (2)
49 | config[String]("baz") should be ("hello world")
50 | }
51 |
52 | it can "parse several badly spaced lines" in {
53 | val s =
54 | """
55 | foo =true
56 | bar= 2
57 | baz = "hello world"
58 | """
59 | val config = parse( s )
60 | config[Boolean]("foo") should be (true)
61 | config[Int]("bar") should be (2)
62 | config[String]("baz") should be ("hello world")
63 | }
64 |
65 | it must "choke when encoutering an unquoted value with spaces" in {
66 | val s =
67 | """
68 | foo = true
69 | bar = 2
70 | baz = hello world
71 | """
72 | intercept[ParserException] {
73 | val config = parse( s )
74 | }
75 | }
76 |
77 | it must "choke when encountering lines with two equals" in {
78 | val s =
79 | """
80 | foo = true
81 | bar = 2
82 | baz = x = 2
83 | """
84 | intercept[ParserException] {
85 | val config = parse( s )
86 | }
87 | }
88 |
89 | it must "choke when encoutering a line without value" in {
90 | val s =
91 | """
92 | foo = true
93 | bar =
94 | baz = x
95 | """
96 | intercept[ParserException] {
97 | val config = parse( s )
98 | }
99 | }
100 |
101 | it must "choke when encoutering a line without key" in {
102 | val s =
103 | """
104 | foo = true
105 | = 2
106 | baz = x
107 | """
108 | intercept[ParserException] {
109 | val config = parse( s )
110 | }
111 | }
112 |
113 | it must "choke when encoutering a line without equals sign" in {
114 | val s =
115 | """
116 | foo = true
117 | bar 2
118 | baz = x
119 | """
120 | intercept[ParserException] {
121 | val config = parse( s )
122 | }
123 | }
124 |
125 | it must "skip comments starting with a '#'" in {
126 | val s =
127 | """
128 | # Example
129 | foo = true
130 | #bar = 2
131 | baz = x
132 | """
133 | val config = parse( s )
134 | config[Boolean]("foo") should be (true)
135 | config.get[Int]("bar") should be (None)
136 | config[String]("baz") should be ("x")
137 | }
138 |
139 | it must "skip comments inline" in {
140 | val s =
141 | """
142 | # Example
143 | foo = true
144 | bar = 2 # This should "be" skipped
145 | baz = x
146 | """
147 | val config = parse( s )
148 | config[Boolean]("foo") should be (true)
149 | config[Int]("bar") should be (2)
150 | config[String]("baz") should be ("x")
151 | }
152 |
153 | it must "skip commented lines when value is missing" in {
154 | val s =
155 | """
156 | baz = "hello world"
157 | # key =
158 | # foo = true
159 | # bar = 2
160 | """
161 | val config = parse( s )
162 | }
163 |
164 | it must "skip commented lines when key and value are missing" in {
165 | val s =
166 | """
167 | #=
168 | """
169 | val config = parse( s )
170 | }
171 |
172 | it can "accept lists" in {
173 | val s =
174 | """
175 | # Example
176 | foo = [ true, false ]
177 | bar = [1,2, 3,4 ]
178 | baz = [ "hello", "wo,rld" ]
179 | """
180 | val config = parse( s )
181 | config[String]("foo") should be ("[ true, false ]")
182 | config[String]("bar") should be ("[ 1, 2, 3, 4 ]")
183 | config[String]("baz") should be ("[ hello, \"wo,rld\" ]")
184 | }
185 |
186 | it can "accept empty strings" in {
187 | val s =
188 | """
189 | bar = "bar"
190 | foo = ""
191 | baz = "baz"
192 | """
193 | val config = parse( s )
194 | config[String]("foo") should be ("")
195 | config[String]("bar") should be ("bar")
196 | config[String]("baz") should be ("baz")
197 | }
198 |
199 | it can "accept empty lists" in {
200 | val s =
201 | """
202 | # Example
203 | foo = [ "hello" ]
204 | bar = []
205 | baz = [ "world" ]
206 | """
207 | val config = parse( s )
208 | config[String]("foo") should be ("[ hello ]")
209 | config[String]("bar") should be ("[ ]")
210 | config[String]("baz") should be ("[ world ]")
211 | }
212 |
213 | it can "accept lists defined over several lines" in {
214 | val s =
215 | """
216 | # Example
217 | foo = [ true, false ]
218 | bar = [ 1,
219 | 2,
220 | 3,
221 | 4
222 | ]
223 | baz = [ "hello",
224 | "wo,rld"
225 | ]
226 | """
227 | val config = parse( s )
228 | config[String]("foo") should be ("[ true, false ]")
229 | config[String]("bar") should be ("[ 1, 2, 3, 4 ]")
230 | config[String]("baz") should be ("[ hello, \"wo,rld\" ]")
231 | }
232 |
233 |
234 |
235 | it must "choke if a list is not between square brackets" in {
236 | val s =
237 | """
238 | # Example
239 | foo = [ true, false ]
240 | bar = [1,2, 3,4
241 | baz = [ "hello", "wo,rld" ]
242 | """
243 | intercept[ParserException] {
244 | val config = parse( s )
245 | }
246 | }
247 |
248 | }
249 |
--------------------------------------------------------------------------------
/modules/yaml/src/YAMLFormat.scala:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2011, Paradigmatic
3 |
4 | This file is part of Configrity.
5 |
6 | Configrity is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU Lesser General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | Configrity is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU Lesser General Public License for more details.
15 |
16 | You should have received a copy of the GNU Lesser General Public License
17 | along with Configrity. If not, see .
18 | */
19 |
20 | package org.streum.configrity.yaml
21 |
22 | import org.yaml.snakeyaml.Yaml
23 | import org.streum.configrity._
24 | import org.streum.configrity.io.{Format, HierarchyUtils}
25 | import java.util.{Map => JMap}
26 | import java.util.{List => JList}
27 | import collection.JavaConversions._
28 |
29 | class YAMLFormatException(msg:String) extends RuntimeException(msg)
30 |
31 | object YAMLFormat extends Format with HierarchyUtils {
32 |
33 | def fromText( s: String ) ={
34 | val map = yaml2map( s )
35 | new Configuration( map )
36 | }
37 |
38 | def toText( configuration: Configuration ) = writeHierarchy( configuration.data )
39 |
40 | def writeEntry(k:String,v:String,ind:Int,out:StringBuffer) {
41 | out.append( " " * ind ).append( k ).append( ": " )
42 | .append( v ).append( "\n" )
43 | }
44 |
45 | def writeBlockStart( k:String, ind:Int, out:StringBuffer ) =
46 | out.append(" "*ind).append(k).append( ": " ).append("\n")
47 |
48 | def writeBlockEnd( k:String, ind:Int, out:StringBuffer ) { }
49 |
50 | private def yaml2map(s: String): Map[String,String] = {
51 | (new Yaml).loadAll(s).head match {
52 | case jmap: JMap[_,_] => readMap( "", jmap.toMap )
53 | case other => {
54 | val klass = other.getClass
55 | except( "Top level should be a Map. Received: " + klass )
56 | }
57 | }
58 | }
59 |
60 | private def readMap( prefix: String, map: Map[_,_] ):Map[String,String] =
61 | map.foldLeft( Map[String,String]() ){
62 | case (map,(k,v)) => map ++ readValue( prefix + k.toString, v )
63 | //case (map,(k,v)) => map ++ readValue( prefix + k.toString, v )
64 | }
65 |
66 |
67 | private def readValue(key: String, value: Any ):Map[String,String] =
68 | value match {
69 | case jl: JList[_] => readList( key, jl )
70 | case jm: JMap[_,_] => readMap( key+".", jm.toMap )
71 | case null => Map.empty
72 | case _ => Map( key -> value.toString )
73 | }
74 |
75 | private def readList( key: String, lst: JList[_] ):Map[String,String] = {
76 | val value = lst.toList.map {
77 | case jlist: JList[_] => except("Lists cannot contain nested Lists.")
78 | case jMap: JMap[_,_] => except("Lists cannot contain nested Maps.")
79 | case s => "\"" + s.toString + "\""
80 | }.mkString( "[", ",", "]" )
81 | Map( key -> value )
82 | }
83 |
84 | private def except( msg: String ) =
85 | throw new YAMLFormatException( msg )
86 |
87 | }
88 |
89 |
--------------------------------------------------------------------------------
/modules/yaml/test/YAMLFormatSpec.scala:
--------------------------------------------------------------------------------
1 | package org.streum.configrity.test.yaml
2 |
3 | import org.scalatest.FlatSpec
4 | import org.scalatest.Matchers._
5 | import org.streum.configrity._
6 |
7 | import org.streum.configrity.yaml._
8 |
9 |
10 | class YAMLFormatSpec extends FlatSpec {
11 |
12 | val FMT = YAMLFormat
13 |
14 | "The yaml format" can "parse a simple YAML file" in {
15 | val yml =
16 | """
17 | foo: 1
18 | bar: true
19 | baz: hello
20 | """
21 | val config = FMT.fromText( yml )
22 | config[Int]("foo") should be (1)
23 | config[Boolean]("bar") should be (true)
24 | config[String]("baz") should be ("hello")
25 |
26 | }
27 |
28 | it should "throw an exception if YAML top level is not a map" in {
29 | val yml =
30 | """
31 | - hello
32 | - world
33 | - 1
34 | """
35 | intercept[YAMLFormatException]{
36 | val config = FMT.fromText( yml )
37 | }
38 | }
39 |
40 | it can "parse list values" in {
41 | val yml =
42 | """
43 | foo: 1
44 | bar:
45 | - true
46 | - false
47 | - true
48 | baz: hello
49 | """
50 | val config = FMT.fromText( yml )
51 | config[Int]("foo") should be (1)
52 | config[List[Boolean]]("bar") should be ( List(true,false,true) )
53 | config[String]("baz") should be ("hello")
54 | }
55 |
56 | it can "parse inline list values" in {
57 | val yml =
58 | """
59 | foo: 1
60 | bar: [true, false, true]
61 | baz: hello
62 | """
63 | val config = FMT.fromText( yml )
64 | config[Int]("foo") should be (1)
65 | config[List[Boolean]]("bar") should be ( List(true,false,true) )
66 | config[String]("baz") should be ("hello")
67 | }
68 |
69 | it should "throw an exception if list elements are maps" in {
70 | val yml =
71 | """
72 | foo: 1
73 | bar:
74 | - inner:
75 | hello: world
76 | tl: dr
77 | baz: hello
78 | """
79 | intercept[YAMLFormatException]{
80 | val config = FMT.fromText( yml )
81 | }
82 | }
83 |
84 | it should "throw an exception if list elements are lists" in {
85 | val yml =
86 | """
87 | foo: 1
88 | bar:
89 | - inner: 2
90 | - [1, 3, 5]
91 | baz: hello
92 | """
93 | intercept[YAMLFormatException]{
94 | val config = FMT.fromText( yml )
95 | }
96 | }
97 |
98 | it must "parse nested maps as Config blocks" in {
99 | val yml =
100 | """
101 | foo: 1
102 | bar:
103 | hello: 12
104 | tl: dr
105 | too:
106 | deep: map
107 | also: works
108 | baz: true
109 | """
110 | val config = FMT.fromText( yml )
111 | config[Int]("foo") should be (1)
112 | config[String]("baz") should be ("true")
113 | val inner = config.detach("bar")
114 | inner[Int]("hello") should be (12)
115 | inner[String]("tl") should be ("dr")
116 | val inner2 = inner.detach("too")
117 | inner2[String]("deep") should be ("map")
118 | }
119 |
120 | it must "parse only the first YAML document if several present" in {
121 | val yml =
122 | """
123 | ---
124 | foo: 1
125 | bar: true
126 | baz: hello
127 | ---
128 | foo: 2
129 | bar: false
130 | baz: greetings
131 | """
132 | val config = FMT.fromText( yml )
133 | config[Int]("foo") should be (1)
134 | config[Boolean]("bar") should be (true)
135 | config[String]("baz") should be ("hello")
136 | }
137 |
138 | it can "write a config into YAML" in {
139 | val config = Configuration(
140 | "foo.gnats.gnits" -> "FOO",
141 | "bar.buzz" -> 1234,
142 | "bar.baz" -> true,
143 | "lst" -> List( true, true, false, false )
144 | )
145 | val config2 = FMT.fromText( FMT.toText( config ) )
146 | config[List[Boolean]]("lst") should be (config2[List[Boolean]]("lst"))
147 | config[String]("foo.gnats.gnits") should be (config2[String]("foo.gnats.gnits"))
148 | config[Int]("bar.buzz") should be (config2[Int]("bar.buzz"))
149 | config[Boolean]("bar.baz") should be (config2[Boolean]("bar.baz"))
150 | }
151 |
152 | it must "ignore key with empty value" in {
153 | val yml =
154 | """
155 | mixer:
156 |
157 | hardware:
158 | device: "/dev/ttyUSB0"
159 | """;
160 | val config = FMT.fromText( yml )
161 | config[String]( "hardware.device" ) should be ( "/dev/ttyUSB0" )
162 | }
163 |
164 |
165 |
166 | }
167 |
--------------------------------------------------------------------------------
/notes/0.10.0.markdown:
--------------------------------------------------------------------------------
1 | Changes since version 0.9.0:
2 |
3 | - Configrity adopted a modular code layout. Most features are provided by the `configrity-core` module, which doesn't require any external dependencies. The sbt dependency line is now slightly different: check the [README file](https://github.com/paradigmatic/Configrity/blob/master/README.md).
4 |
5 | - YAML import/export is now provided by the `configrity-yaml` module, based on [snakeyaml](http://code.google.com/p/snakeyaml/). Usage and documentation in the [project wiki](https://github.com/paradigmatic/Configrity/wiki/YAML) (issue #2).
6 |
7 | If you wish for extra features, [feel free to ask](https://github.com/paradigmatic/Configrity/issues/).
8 |
--------------------------------------------------------------------------------
/notes/0.10.1.markdown:
--------------------------------------------------------------------------------
1 | Small maintenance release, including:
2 |
3 | - When loading a configuration from the classpath, a `FileNotFoundException` is thrown
4 | (issue #8) -- Martin Konicek
5 | - Looking for a non existing key will throw a `NoSuchElementException` with a message
6 | clearly refering to the missing key (issue #11) -- Jussi Virtanen
7 | - List values are sanitized by adding quotes when needed (issue #12)
8 | - Artefacts for Scala 2.9.2
9 |
10 | If you wish for extra features, [feel free to ask](https://github.com/paradigmatic/Configrity/issues/).
11 |
--------------------------------------------------------------------------------
/notes/0.10.2.markdown:
--------------------------------------------------------------------------------
1 | Small maintenance release, including:
2 |
3 | - Thanks to `sbt-scalashim`, Configrity is now backward compatible
4 | with Scala 2.8.1 and 2.8.2.
5 | - Empty string values are properly formatted (issue #13)
6 |
7 | If you wish for extra features, [feel free to ask](https://github.com/paradigmatic/Configrity/issues/).
--------------------------------------------------------------------------------
/notes/0.7.0.markdown:
--------------------------------------------------------------------------------
1 | New features since version 0.6.1:
2 |
3 | - Implicit converters for `File`, `Color`, `URL` and `URI`.
4 | - List of values are accepted:
5 |
6 |
--------------------------------------------------------------------------------
/notes/0.8.0.markdown:
--------------------------------------------------------------------------------
1 | Configrity standard formats accepts an `include` directive. This allow
2 | the parsing of *Akka* configuration files as well as most legacy
3 | *Configgy* files.
4 |
5 | New features since version 0.7.0:
6 |
7 | - Setting a list in a configuration will automatically does not
8 | require anymore proper formatting.
9 | - An `include` directive is supported in both `FlatFormat` and
10 | `BlockFormat`.
11 | - `BlockFormat` parser ignores empty blocks without complaining.
12 |
13 | If you wish for extra features, [feel free to ask](https://github.com/paradigmatic/Configrity/issues/).
14 |
--------------------------------------------------------------------------------
/notes/0.9.0.markdown:
--------------------------------------------------------------------------------
1 | Changes since version 0.8.0:
2 |
3 | - Configurations can be loaded directly from the classpath using
4 | `Configuration.loadResource`.
5 | - Better handling of lists when the configuration is built from
6 | key/values (issue #4)
7 | - All tests pass on Windows (issue #3) -- Gerolf Seitz
8 |
9 | If you wish for extra features, [feel free to ask](https://github.com/paradigmatic/Configrity/issues/).
10 |
--------------------------------------------------------------------------------
/notes/1.0.0.markdown:
--------------------------------------------------------------------------------
1 | After 18 months since first public release, we are proud to annouce the first stable version of Configrity. The version was bumped to 1.0.0 to reflect the project maturity. [Semantic versioning](http://semver.org/) will now be strictly enforced. Changes since version 0.10.2:
2 |
3 | ### Scala compatibility
4 |
5 | - Configrity is now compatible with Scala 2.9.2 and 2.10.0 (thanks to Aki Saarinen).
6 | - **Support for Scala 2.8.2 was dropped.** If you are currently using Configrity with Scala 2.8 and if you cannot upgrade to Scala 2.9, you can fill a request using the [issue tracker](https://github.com/paradigmatic/Configrity/issues/).
7 |
8 | ### New features
9 |
10 | - Detached configurations carry their context prefix. (issue #14 and #15) -- Pablo Lalloni.
11 | - The method `Configuration#detachAll` allows to detach all first level sub-configurations to produce a map that associates each prefix with its sub-configuration (issue #19) -- Jussi Virtanen.
12 | - Booleans can be represented as "yes/no" strings (issue #20) -- Yuri Molchan
13 |
14 | If you wish for extra features, [feel free to ask](https://github.com/paradigmatic/Configrity/issues/).
--------------------------------------------------------------------------------
/notes/about.markdown:
--------------------------------------------------------------------------------
1 | [Configrity](https://github.com/paradigmatic/Configrity) is a simple, immutable and flexible Scala API for handling configurations.
2 |
--------------------------------------------------------------------------------
/org/release.org:
--------------------------------------------------------------------------------
1 | * Release check-list
2 |
3 | ** Before publication [71%]
4 |
5 | - [X] Clean >> All tests green.
6 | - [X] Check version number in =project/Build.scala=
7 | - [X] Update changelog
8 | - [X] Update README file
9 | - [X] Write the new note for implicitly
10 | - [ ] Commit changes
11 | - [ ] Tag release
12 |
13 | ** Publication [0%]
14 |
15 | - [ ] SBT publish
16 | - [ ] Close the stagging repository on http://oss.sonatype.org
17 | - [ ] Release the repository
18 |
19 | ** After publication [0%]
20 |
21 | - [ ] Wait at least two hours
22 | - [ ] Check it appears in MVN central
23 | - [ ] Update README maven section
24 | - [ ] Push and push --tags
25 | - [ ] Generate the scaladoc
26 | - [ ] Push the scaladoc
27 | - [ ] Update the wiki
28 | - [ ] submit the release to implicitly
29 | - [ ] close remaining issues
30 |
31 | ** When everything is OK
32 |
33 | - [ ] Reset this checklist
34 |
--------------------------------------------------------------------------------
/project/Build.scala:
--------------------------------------------------------------------------------
1 | import sbt._
2 | import Keys._
3 |
4 | import com.typesafe.tools.mima.plugin.MimaPlugin.mimaDefaultSettings
5 | import com.typesafe.tools.mima.plugin.MimaKeys.previousArtifact
6 |
7 | object ConfigrityBuild extends Build {
8 |
9 | lazy val configrity = Project(
10 | id = "configrity",
11 | base = file("."),
12 | settings = rootSettings
13 | ).aggregate(core, yaml)
14 |
15 |
16 | lazy val core = Project(
17 | id = "configrity-core",
18 | base = file("core"),
19 | settings =
20 | standardSettings ++
21 | publishSettings ++
22 | mimaDefaultSettings ++
23 | Seq(
24 | previousArtifact := Some("org.streum" % "configrity-core_2.10" % "1.0.0")
25 | )
26 | )
27 |
28 | lazy val yaml = Project(
29 | id = "configrity-yaml",
30 | base = file("modules/yaml"),
31 | dependencies = Seq(core),
32 | settings = standardSettings ++ publishSettings ++ Seq(
33 | libraryDependencies += "org.yaml" % "snakeyaml" % "1.11"
34 | )
35 | )
36 |
37 | lazy val minimalSettings = Defaults.defaultSettings ++ Seq(
38 | organization := "org.streum",
39 | version := "1.0.1",
40 | licenses := Seq("GNU LesserGPLv3" -> url("http://www.gnu.org/licenses/lgpl.html")),
41 | homepage := Some(url("https://github.com/paradigmatic/Configrity")),
42 | scalaVersion := "2.11.2",
43 | crossScalaVersions := Seq( "2.10.3", "2.11.2" )
44 | )
45 |
46 | lazy val rootSettings = minimalSettings ++ Seq(
47 | publish := { },
48 | publishLocal := { }
49 | )
50 |
51 |
52 | lazy val scalatest = "org.scalatest" %% "scalatest" % "2.2.0" % "test"
53 | lazy val parsers = "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.0"
54 |
55 | lazy val standardSettings = minimalSettings ++ Seq(
56 | libraryDependencies ++= Seq(
57 | scalatest
58 | ),
59 | scalacOptions ++= Seq( "-deprecation", "-unchecked", "-feature", "-language:implicitConversions" ),
60 | scalaSource in Compile <<= baseDirectory(_ / "src"),
61 | scalaSource in Test <<= baseDirectory(_ / "test"),
62 | resourceDirectory in Test <<= baseDirectory { _ / "test-resources" },
63 | unmanagedClasspath in Compile +=
64 | Attributed.blank(new java.io.File("doesnotexist")),
65 | libraryDependencies := {
66 | CrossVersion.partialVersion(scalaVersion.value) match {
67 | case Some((2, scalaMajor)) if scalaMajor >= 11 =>
68 | libraryDependencies.value ++ Seq(
69 | "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.2"
70 | )
71 | case _ => libraryDependencies.value
72 | }
73 | }
74 | )
75 |
76 |
77 | lazy val publishSettings = Seq(
78 | publishMavenStyle := true,
79 | publishTo <<= version { (v: String) =>
80 | val nexus = "https://oss.sonatype.org/"
81 | if (v.trim.endsWith("SNAPSHOT"))
82 | Some("snapshots" at nexus + "content/repositories/snapshots")
83 | else
84 | Some("releases" at nexus + "service/local/staging/deploy/maven2")
85 | },
86 | publishArtifact in Test := false,
87 | pomIncludeRepository := { x => false },
88 | pomExtra := requiredPOMextra
89 | )
90 |
91 |
92 | lazy val requiredPOMextra = {
93 |
94 | https://github.com/paradigmatic/Configrity
95 | scm:git:git@github.com:paradigmatic/Configrity.git
96 |
97 |
98 |
99 | jlfalcone
100 | Jean-Luc Falcone
101 | http://paradigmatic.streum.org/
102 |
103 |
104 | }
105 |
106 | }
107 |
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=0.13.5
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.1.6")
2 |
--------------------------------------------------------------------------------