src/Pure/General/path.scala
author wenzelm
Fri Jul 20 22:29:25 2012 +0200 (2012-07-20)
changeset 48409 0d2114eb412a
parent 48373 527e2bad7cca
child 48420 a8ed41b6280b
permissions -rw-r--r--
more explicit java.io.{File => JFile};
     1 /*  Title:      Pure/General/path.scala
     2     Author:     Makarius
     3 
     4 Algebra of file-system paths: basic POSIX notation, extended by named
     5 roots (e.g. //foo) and variables (e.g. $BAR).
     6 */
     7 
     8 package isabelle
     9 
    10 
    11 import java.io.{File => JFile}
    12 
    13 import scala.util.matching.Regex
    14 
    15 
    16 object Path
    17 {
    18   /* path elements */
    19 
    20   sealed abstract class Elem
    21   private case class Root(val name: String) extends Elem
    22   private case class Basic(val name: String) extends Elem
    23   private case class Variable(val name: String) extends Elem
    24   private case object Parent extends Elem
    25 
    26   private def err_elem(msg: String, s: String): Nothing =
    27     error (msg + " path element specification: " + quote(s))
    28 
    29   private def check_elem(s: String): String =
    30     if (s == "" || s == "~" || s == "~~") err_elem("Illegal", s)
    31     else
    32       s.iterator.filter(c => c == '/' || c == '\\' || c == '$' || c == ':').toList match {
    33         case Nil => s
    34         case bads =>
    35           err_elem ("Illegal character(s) " + commas_quote(bads.map(_.toString)) + " in", s)
    36       }
    37 
    38   private def root_elem(s: String): Elem = Root(check_elem(s))
    39   private def basic_elem(s: String): Elem = Basic(check_elem(s))
    40   private def variable_elem(s: String): Elem = Variable(check_elem(s))
    41 
    42   private def apply_elem(y: Elem, xs: List[Elem]): List[Elem] =
    43     (y, xs) match {
    44       case (Root(_), _) => List(y)
    45       case (Parent, Root(_) :: _) => xs
    46       case (Parent, Basic(_) :: rest) => rest
    47       case _ => y :: xs
    48     }
    49 
    50   private def norm_elems(elems: List[Elem]): List[Elem] =
    51     (elems :\ (Nil: List[Elem]))(apply_elem)
    52 
    53   private def implode_elem(elem: Elem): String =
    54     elem match {
    55       case Root("") => ""
    56       case Root(s) => "//" + s
    57       case Basic(s) => s
    58       case Variable(s) => "$" + s
    59       case Parent => ".."
    60     }
    61 
    62 
    63   /* path constructors */
    64 
    65   val current: Path = new Path(Nil)
    66   val root: Path = new Path(List(Root("")))
    67   def named_root(s: String): Path = new Path(List(root_elem(s)))
    68   def basic(s: String): Path = new Path(List(basic_elem(s)))
    69   def variable(s: String): Path = new Path(List(variable_elem(s)))
    70   val parent: Path = new Path(List(Parent))
    71 
    72 
    73   /* explode */
    74 
    75   private def explode_elem(s: String): Elem =
    76     if (s == "..") Parent
    77     else if (s == "~") Variable("USER_HOME")
    78     else if (s == "~~") Variable("ISABELLE_HOME")
    79     else if (s.startsWith("$")) variable_elem(s.substring(1))
    80     else basic_elem(s)
    81 
    82   private def explode_elems(ss: List[String]): List[Elem] =
    83     ss.filterNot(s => s.isEmpty || s == ".").map(explode_elem).reverse
    84 
    85   def explode(str: String): Path =
    86   {
    87     val ss = space_explode('/', str)
    88     val r = ss.takeWhile(_.isEmpty).length
    89     val es = ss.dropWhile(_.isEmpty)
    90     val (roots, raw_elems) =
    91       if (r == 0) (Nil, es)
    92       else if (r == 1) (List(Root("")), es)
    93       else if (es.isEmpty) (List(Root("")), Nil)
    94       else (List(root_elem(es.head)), es.tail)
    95     new Path(norm_elems(explode_elems(raw_elems) ++ roots))
    96   }
    97 
    98   def split(str: String): List[Path] =
    99     space_explode(':', str).filterNot(_.isEmpty).map(explode)
   100 }
   101 
   102 
   103 final class Path private(private val elems: List[Path.Elem]) // reversed elements
   104 {
   105   def is_current: Boolean = elems.isEmpty
   106   def is_absolute: Boolean = !elems.isEmpty && elems.last.isInstanceOf[Path.Root]
   107   def is_basic: Boolean = elems match { case List(Path.Basic(_)) => true case _ => false }
   108 
   109   def +(other: Path): Path = new Path((other.elems :\ elems)(Path.apply_elem))
   110 
   111 
   112   /* implode */
   113 
   114   def implode: String =
   115     elems match {
   116       case Nil => "."
   117       case List(Path.Root("")) => "/"
   118       case _ => elems.map(Path.implode_elem).reverse.mkString("/")
   119     }
   120 
   121   override def toString: String = quote(implode)
   122 
   123 
   124   /* base element */
   125 
   126   private def split_path: (Path, String) =
   127     elems match {
   128       case Path.Basic(s) :: xs => (new Path(xs), s)
   129       case _ => error("Cannot split path into dir/base: " + toString)
   130     }
   131 
   132   def dir: Path = split_path._1
   133   def base: Path = new Path(List(Path.Basic(split_path._2)))
   134 
   135   def ext(e: String): Path =
   136     if (e == "") this
   137     else {
   138       val (prfx, s) = split_path
   139       prfx + Path.basic(s + "." + e)
   140     }
   141 
   142   private val Ext = new Regex("(.*)\\.([^.]*)")
   143 
   144   def split_ext: (Path, String) =
   145   {
   146     val (prefix, base) = split_path
   147     base match {
   148       case Ext(b, e) => (prefix + Path.basic(b), e)
   149       case _ => (Path.basic(base), "")
   150     }
   151   }
   152 
   153 
   154   /* expand */
   155 
   156   def expand: Path =
   157   {
   158     def eval(elem: Path.Elem): List[Path.Elem] =
   159       elem match {
   160         case Path.Variable(s) =>
   161           Path.explode(Isabelle_System.getenv_strict(s)).elems
   162         case x => List(x)
   163       }
   164 
   165     new Path(Path.norm_elems(elems.map(eval).flatten))
   166   }
   167 
   168 
   169   /* platform file */
   170 
   171   def file: JFile = Isabelle_System.platform_file(this)
   172 }