2010-02-08 05:45:19 +01:00
/* sbt -- Simple Build Tool
* Copyright 2009 , 2010 Mark Harrah
*/
2009-09-26 08:18:04 +02:00
package xsbt.boot
2010-02-05 00:56:07 +01:00
2009-10-18 04:40:02 +02:00
import Pre._
2010-08-12 00:50:44 +02:00
import ConfigurationParser._
2009-09-26 08:18:04 +02:00
import java.lang.Character.isWhitespace
2009-10-18 04:40:02 +02:00
import java.io. { BufferedReader , File , FileInputStream , InputStreamReader , Reader , StringReader }
2009-10-02 04:59:02 +02:00
import java.net. { MalformedURLException , URL }
2009-10-18 04:40:02 +02:00
import java.util.regex.Pattern
2010-06-16 02:38:18 +02:00
import scala.collection.immutable.List
2009-09-26 08:18:04 +02:00
2010-08-12 00:50:44 +02:00
object ConfigurationParser
{
def trim ( s : Array [ String ] ) = s . map ( _ . trim ) . toList
def ids ( value : String ) = trim ( value . split ( "," ) ) . filter ( isNonEmpty )
implicit val readIDs = ids _
}
2011-04-11 06:44:28 +02:00
class ConfigurationParser
2009-09-26 08:18:04 +02:00
{
2009-10-18 04:40:02 +02:00
def apply ( file : File ) : LaunchConfiguration = Using ( new InputStreamReader ( new FileInputStream ( file ) , "UTF-8" ) ) ( apply )
def apply ( s : String ) : LaunchConfiguration = Using ( new StringReader ( s ) ) ( apply )
def apply ( reader : Reader ) : LaunchConfiguration = Using ( new BufferedReader ( reader ) ) ( apply )
private def apply ( in : BufferedReader ) : LaunchConfiguration =
{
def readLine ( accum : List [ Line ] , index : Int ) : List [ Line ] =
2009-09-26 08:18:04 +02:00
{
2009-10-18 04:40:02 +02:00
val line = in . readLine ( )
if ( line eq null ) accum . reverse else readLine ( ParseLine ( line , index ) : :: accum , index + 1 )
2009-09-26 08:18:04 +02:00
}
2009-10-18 04:40:02 +02:00
processSections ( processLines ( readLine ( Nil , 0 ) ) )
}
2009-09-26 08:18:04 +02:00
// section -> configuration instance processing
def processSections ( sections : SectionMap ) : LaunchConfiguration =
{
2010-01-10 22:46:15 +01:00
val ( ( scalaVersion , scalaClassifiers ) , m1 ) = processSection ( sections , "scala" , getScala )
2010-05-14 00:38:55 +02:00
val ( ( app , appClassifiers ) , m2 ) = processSection ( m1 , "app" , getApplication )
2009-09-26 08:18:04 +02:00
val ( repositories , m3 ) = processSection ( m2 , "repositories" , getRepositories )
val ( boot , m4 ) = processSection ( m3 , "boot" , getBoot )
val ( logging , m5 ) = processSection ( m4 , "log" , getLogging )
2009-10-15 02:53:15 +02:00
val ( properties , m6 ) = processSection ( m5 , "app-properties" , getAppProperties )
2011-03-12 16:28:53 +01:00
val ( ivyHome , m7 ) = processSection ( m6 , "ivy" , getIvy )
2010-05-14 00:38:55 +02:00
check ( m7 , "section" )
2010-08-12 00:50:44 +02:00
val classifiers = Classifiers ( scalaClassifiers , appClassifiers )
2011-03-12 16:28:53 +01:00
new LaunchConfiguration ( scalaVersion , IvyOptions ( ivyHome , classifiers , repositories ) , app , boot , logging , properties )
2010-01-10 22:46:15 +01:00
}
def getScala ( m : LabelMap ) =
{
val ( scalaVersion , m1 ) = getVersion ( m , "Scala version" , "scala.version" )
2010-08-12 00:50:44 +02:00
val ( scalaClassifiers , m2 ) = getClassifiers ( m1 , "Scala classifiers" )
2010-01-10 22:46:15 +01:00
check ( m2 , "label" )
2010-06-07 16:51:22 +02:00
( scalaVersion , scalaClassifiers )
2009-09-26 08:18:04 +02:00
}
2010-08-12 00:50:44 +02:00
def getClassifiers ( m : LabelMap , label : String ) : ( Value [ List [ String ] ] , LabelMap ) =
process ( m , "classifiers" , processClassifiers ( label ) )
def processClassifiers ( label : String ) ( value : Option [ String ] ) : Value [ List [ String ] ] =
value . map ( readValue [ List [ String ] ] ( label ) ) getOrElse new Explicit ( Nil )
def getVersion ( m : LabelMap , label : String , defaultName : String ) : ( Value [ String ] , LabelMap ) = process ( m , "version" , processVersion ( label , defaultName ) )
def processVersion ( label : String , defaultName : String ) ( value : Option [ String ] ) : Value [ String ] =
value . map ( readValue [ String ] ( label ) ) . getOrElse ( new Implicit ( defaultName , None ) )
def readValue [ T ] ( label : String ) ( implicit read : String => T ) : String => Value [ T ] = value =>
2009-09-26 08:18:04 +02:00
{
2010-08-12 00:50:44 +02:00
if ( isEmpty ( value ) ) error ( label + " cannot be empty (omit declaration to use the default)" )
try { parsePropertyValue ( label , value ) ( Value . readImplied [ T ] ) }
catch { case e : BootException => new Explicit ( read ( value ) ) }
2009-09-26 08:18:04 +02:00
}
2009-10-18 04:40:02 +02:00
def processSection [ T ] ( sections : SectionMap , name : String , f : LabelMap => T ) =
process [ String ,LabelMap ,T ] ( sections , name , m => f ( m default ( x => None ) ) )
def process [ K ,V ,T ] ( sections : ListMap [ K ,V ] , name : K , f : V => T ) : ( T , ListMap [ K ,V ] ) = ( f ( sections ( name ) ) , sections - name )
def check ( map : ListMap [ String , _ ] , label : String ) : Unit = if ( map . isEmpty ) ( ) else error ( map . keys . mkString ( "Invalid " + label + "(s): " , "," , "" ) )
def check [ T ] ( label : String , pair : ( T , ListMap [ String , _ ] ) ) : T = { check ( pair . _2 , label ) ; pair . _1 }
2009-10-15 02:53:15 +02:00
def id ( map : LabelMap , name : String , default : String ) : ( String , LabelMap ) =
2010-02-06 01:06:44 +01:00
( orElse ( getOrNone ( map , name ) , default ) , map - name )
def getOrNone [ K ,V ] ( map : ListMap [ K ,Option [ V ] ] , k : K ) = orElse ( map . get ( k ) , None )
2009-10-18 04:40:02 +02:00
def ids ( map : LabelMap , name : String , default : List [ String ] ) =
2009-09-26 08:18:04 +02:00
{
2010-08-12 00:50:44 +02:00
val result = map ( name ) map ConfigurationParser . ids
2010-02-06 01:06:44 +01:00
( orElse ( result , default ) , map - name )
2009-09-26 08:18:04 +02:00
}
2009-10-15 02:53:15 +02:00
def bool ( map : LabelMap , name : String , default : Boolean ) : ( Boolean , LabelMap ) =
{
val ( b , m ) = id ( map , name , default . toString )
2009-10-18 04:40:02 +02:00
( toBoolean ( b ) , m )
2009-10-15 02:53:15 +02:00
}
2011-04-19 04:17:55 +02:00
2009-12-18 23:46:57 +01:00
def toFiles ( paths : List [ String ] ) : List [ File ] = paths . map ( toFile )
2011-04-19 04:17:55 +02:00
def toFile ( path : String ) : File = new File ( substituteVariables ( path ) . replace ( '/' , File . separatorChar ) ) // if the path is relative, it will be resolved by Launch later
2009-09-26 08:18:04 +02:00
def file ( map : LabelMap , name : String , default : File ) : ( File , LabelMap ) =
2010-02-06 01:06:44 +01:00
( orElse ( getOrNone ( map , name ) . map ( toFile ) , default ) , map - name )
2009-09-26 08:18:04 +02:00
2010-05-14 00:38:55 +02:00
def getIvy ( m : LabelMap ) : Option [ File ] =
{
2011-03-12 16:28:53 +01:00
val ( ivyHome , m1 ) = file ( m , "ivy-home" , null ) // fix this later
2010-05-14 00:38:55 +02:00
check ( m1 , "label" )
2011-03-12 16:28:53 +01:00
if ( ivyHome eq null ) None else Some ( ivyHome )
2010-05-14 00:38:55 +02:00
}
2009-09-26 08:18:04 +02:00
def getBoot ( m : LabelMap ) : BootSetup =
{
2009-10-02 04:59:02 +02:00
val ( dir , m1 ) = file ( m , "directory" , toFile ( "project/boot" ) )
val ( props , m2 ) = file ( m1 , "properties" , toFile ( "project/build.properties" ) )
val ( search , m3 ) = getSearch ( m2 , props )
2009-10-15 02:53:15 +02:00
val ( enableQuick , m4 ) = bool ( m3 , "quick-option" , false )
val ( promptFill , m5 ) = bool ( m4 , "prompt-fill" , false )
val ( promptCreate , m6 ) = id ( m5 , "prompt-create" , "" )
check ( m6 , "label" )
BootSetup ( dir , props , search , promptCreate , enableQuick , promptFill )
2009-09-26 08:18:04 +02:00
}
def getLogging ( m : LabelMap ) : Logging = check ( "label" , process ( m , "level" , getLevel ) )
2009-10-18 04:40:02 +02:00
def getLevel ( m : Option [ String ] ) = m . map ( LogLevel . apply ) . getOrElse ( new Logging ( LogLevel . Info ) )
2009-10-02 04:59:02 +02:00
def getSearch ( m : LabelMap , defaultPath : File ) : ( Search , LabelMap ) =
ids ( m , "search" , Nil ) match
{
case ( Nil , newM ) => ( Search . none , newM )
2009-10-18 04:40:02 +02:00
case ( tpe : : Nil , newM ) => ( Search ( tpe , List ( defaultPath ) ) , newM )
2009-12-18 23:46:57 +01:00
case ( tpe : : paths , newM ) => ( Search ( tpe , toFiles ( paths ) ) , newM )
2009-10-02 04:59:02 +02:00
}
2009-09-26 08:18:04 +02:00
2010-08-12 00:50:44 +02:00
def getApplication ( m : LabelMap ) : ( Application , Value [ List [ String ] ] ) =
2009-09-26 08:18:04 +02:00
{
val ( org , m1 ) = id ( m , "org" , "org.scala-tools.sbt" )
val ( name , m2 ) = id ( m1 , "name" , "sbt" )
2009-10-20 05:18:13 +02:00
val ( rev , m3 ) = getVersion ( m2 , name + " version" , name + ".version" )
2009-09-26 08:18:04 +02:00
val ( main , m4 ) = id ( m3 , "class" , "xsbt.Main" )
2009-10-18 04:40:02 +02:00
val ( components , m5 ) = ids ( m4 , "components" , List ( "default" ) )
2009-09-26 08:18:04 +02:00
val ( crossVersioned , m6 ) = id ( m5 , "cross-versioned" , "true" )
2009-12-18 23:46:57 +01:00
val ( resources , m7 ) = ids ( m6 , "resources" , Nil )
2010-08-12 00:50:44 +02:00
val ( classifiers , m8 ) = getClassifiers ( m7 , "Application classifiers" )
2010-05-14 00:38:55 +02:00
check ( m8 , "label" )
2010-02-05 00:56:07 +01:00
val classpathExtra = toArray ( toFiles ( resources ) )
2010-05-14 00:38:55 +02:00
val app = new Application ( org , name , rev , main , components , toBoolean ( crossVersioned ) , classpathExtra )
( app , classifiers )
2009-09-26 08:18:04 +02:00
}
2009-10-18 04:40:02 +02:00
def getRepositories ( m : LabelMap ) : List [ Repository ] =
2009-09-26 08:18:04 +02:00
{
import Repository. { Ivy , Maven , Predefined }
2009-10-18 04:40:02 +02:00
m . toList . map {
2009-10-02 04:59:02 +02:00
case ( key , None ) => Predefined ( key )
2009-09-26 08:18:04 +02:00
case ( key , Some ( value ) ) =>
val r = trim ( value . split ( "," , 2 ) )
2009-10-18 04:40:02 +02:00
val url = try { new URL ( r ( 0 ) ) } catch { case e : MalformedURLException => error ( "Invalid URL specified for '" + key + "': " + e . getMessage ) }
2009-09-26 08:18:04 +02:00
if ( r . length == 2 ) Ivy ( key , url , r ( 1 ) ) else Maven ( key , url )
}
}
2009-10-18 04:40:02 +02:00
def getAppProperties ( m : LabelMap ) : List [ AppProperty ] =
for ( ( name , Some ( value ) ) <- m . toList ) yield
2009-10-15 02:53:15 +02:00
{
2009-10-18 04:40:02 +02:00
val map = ListMap ( trim ( value . split ( "," ) ) . map ( parsePropertyDefinition ( name ) ) : _ * )
2009-10-15 02:53:15 +02:00
AppProperty ( name ) ( map . get ( "quick" ) , map . get ( "new" ) , map . get ( "fill" ) )
}
def parsePropertyDefinition ( name : String ) ( value : String ) = value . split ( "=" , 2 ) match {
case Array ( mode , value ) => ( mode , parsePropertyValue ( name , value ) ( defineProperty ( name ) ) )
2009-10-18 04:40:02 +02:00
case x => error ( "Invalid property definition '" + x + "' for property '" + name + "'" )
2009-10-15 02:53:15 +02:00
}
def defineProperty ( name : String ) ( action : String , requiredArg : String , optionalArg : Option [ String ] ) =
action match
{
2009-10-18 04:40:02 +02:00
case "prompt" => new PromptProperty ( requiredArg , optionalArg )
case "set" => new SetProperty ( requiredArg )
case _ => error ( "Unknown action '" + action + "' for property '" + name + "'" )
2009-10-15 02:53:15 +02:00
}
2011-04-19 04:17:55 +02:00
private [ this ] lazy val propertyPattern = Pattern . compile ( """(.+)\((.*)\)(?:\[(.*)\])?""" ) // examples: prompt(Version)[1.0] or set(1.0)
2009-10-15 02:53:15 +02:00
def parsePropertyValue [ T ] ( name : String , definition : String ) ( f : ( String , String , Option [ String ] ) => T ) : T =
{
val m = propertyPattern . matcher ( definition )
2009-10-18 04:40:02 +02:00
if ( ! m . matches ( ) ) error ( "Invalid property definition '" + definition + "' for property '" + name + "'" )
2009-10-15 02:53:15 +02:00
val optionalArg = m . group ( 3 )
f ( m . group ( 1 ) , m . group ( 2 ) , if ( optionalArg eq null ) None else Some ( optionalArg ) )
}
2009-09-26 08:18:04 +02:00
2009-10-18 04:40:02 +02:00
type LabelMap = ListMap [ String , Option [ String ] ]
2009-09-26 08:18:04 +02:00
// section-name -> label -> value
2009-10-18 04:40:02 +02:00
type SectionMap = ListMap [ String , LabelMap ]
2009-09-26 08:18:04 +02:00
def processLines ( lines : List [ Line ] ) : SectionMap =
{
type State = ( SectionMap , Option [ String ] )
val s : State =
2009-10-18 04:40:02 +02:00
( ( ( ListMap . empty . default ( x => ListMap . empty [ String ,Option [ String ] ] ) , None ) : State ) /: lines ) {
2009-09-26 08:18:04 +02:00
case ( x , Comment ) => x
2009-10-18 04:40:02 +02:00
case ( ( map , _ ) , s : Section ) => ( map , Some ( s . name ) )
case ( ( _ , None ) , l : Labeled ) => error ( "Label " + l . label + " is not in a section" )
2009-09-26 08:18:04 +02:00
case ( ( map , s @ Some ( section ) ) , l : Labeled ) =>
val sMap = map ( section )
2009-10-18 04:40:02 +02:00
if ( sMap . contains ( l . label ) ) error ( "Duplicate label '" + l . label + "' in section '" + section + "'" )
2009-09-26 08:18:04 +02:00
else ( map ( section ) = ( sMap ( l . label ) = l . value ) , s )
}
s . _1
}
2011-04-19 04:17:55 +02:00
private [ this ] lazy val VarPattern = Pattern . compile ( """\$\{([\w.]+(-(.+))?)\}""" )
def substituteVariables ( s : String ) : String = if ( s . indexOf ( '$' ) >= 0 ) substituteVariables0 ( s ) else s
// scala.util.Regex brought in 30kB, so we code it explicitly
def substituteVariables0 ( s : String ) : String =
{
val m = VarPattern . matcher ( s )
val b = new StringBuffer
while ( m . find ( ) )
{
val key = m . group ( 1 )
val defined = System . getProperty ( key )
val value =
if ( defined ne null )
defined
else
{
val default = m . group ( 3 )
if ( default eq null ) m . group ( ) else substituteVariables ( default )
}
m . appendReplacement ( b , value )
}
m . appendTail ( b )
b . toString
}
2009-10-18 04:40:02 +02:00
}
2009-09-26 08:18:04 +02:00
2011-04-11 06:44:28 +02:00
sealed trait Line
2009-10-18 04:40:02 +02:00
final class Labeled ( val label : String , val value : Option [ String ] ) extends Line
final class Section ( val name : String ) extends Line
object Comment extends Line
2009-09-26 08:18:04 +02:00
2009-10-18 04:40:02 +02:00
class ParseException ( val content : String , val line : Int , val col : Int , val msg : String )
extends BootException ( "[" + ( line + 1 ) + ", " + ( col + 1 ) + "]" + msg + "\n" + content + "\n" + List . make ( col , " " ) . mkString + "^" )
2009-09-26 08:18:04 +02:00
2009-10-18 04:40:02 +02:00
object ParseLine
{
def apply ( content : String , line : Int ) =
{
def error ( col : Int , msg : String ) = throw new ParseException ( content , line , col , msg )
def check ( condition : Boolean ) ( col : Int , msg : String ) = if ( condition ) ( ) else error ( col , msg )
2009-09-26 08:18:04 +02:00
2009-10-18 04:40:02 +02:00
val trimmed = trimLeading ( content )
val offset = content . length - trimmed . length
2009-09-26 08:18:04 +02:00
2009-10-18 04:40:02 +02:00
def section =
2009-09-26 08:18:04 +02:00
{
2009-10-18 04:40:02 +02:00
val closing = trimmed . indexOf ( ']' , 1 )
check ( closing > 0 ) ( content . length , "Expected ']', found end of line" )
val extra = trimmed . substring ( closing + 1 )
val trimmedExtra = trimLeading ( extra )
check ( isEmpty ( trimmedExtra ) ) ( content . length - trimmedExtra . length , "Expected end of line, found '" + extra + "'" )
new Section ( trimmed . substring ( 1 , closing ) . trim )
}
def labeled =
{
trimmed . split ( ":" , 2 ) match {
case Array ( label , value ) =>
val trimmedValue = value . trim
check ( isNonEmpty ( trimmedValue ) ) ( content . indexOf ( ':' ) , "Value for '" + label + "' was empty" )
new Labeled ( label , Some ( trimmedValue ) )
case x => new Labeled ( x . mkString , None )
2009-09-26 08:18:04 +02:00
}
}
2009-10-18 04:40:02 +02:00
if ( isEmpty ( trimmed ) ) Nil
else
2009-09-26 08:18:04 +02:00
{
2009-10-18 04:40:02 +02:00
val processed =
trimmed . charAt ( 0 ) match
{
case '#' => Comment
case '[' => section
case _ => labeled
}
processed : : Nil
2009-09-26 08:18:04 +02:00
}
}
2009-10-18 04:40:02 +02:00
}