src/Pure/Thy/export.scala
changeset 68116 ac82ee617a75
parent 68115 23c6ae3dd3a0
child 68151 3c321783bae3
equal deleted inserted replaced
68115:23c6ae3dd3a0 68116:ac82ee617a75
     3 
     3 
     4 Manage theory exports: compressed blobs.
     4 Manage theory exports: compressed blobs.
     5 */
     5 */
     6 
     6 
     7 package isabelle
     7 package isabelle
       
     8 
       
     9 
       
    10 import scala.annotation.tailrec
       
    11 import scala.util.matching.Regex
       
    12 
     8 
    13 
     9 object Export
    14 object Export
    10 {
    15 {
    11   /* SQL data model */
    16   /* SQL data model */
    12 
    17 
    19     val body = SQL.Column.bytes("body")
    24     val body = SQL.Column.bytes("body")
    20 
    25 
    21     val table =
    26     val table =
    22       SQL.Table("isabelle_exports", List(session_name, theory_name, name, compressed, body))
    27       SQL.Table("isabelle_exports", List(session_name, theory_name, name, compressed, body))
    23 
    28 
    24     def where_equal(session_name: String, theory_name: String): SQL.Source =
    29     def where_equal(session_name: String, theory_name: String = "", name: String = ""): SQL.Source =
    25       "WHERE " + Data.session_name.equal(session_name) +
    30       "WHERE " + Data.session_name.equal(session_name) +
    26       " AND " + Data.theory_name.equal(theory_name)
    31         (if (theory_name == "") "" else " AND " + Data.theory_name.equal(theory_name)) +
       
    32         (if (name == "") "" else " AND " + Data.name.equal(name))
       
    33   }
       
    34 
       
    35   def read_name(db: SQL.Database, session_name: String, theory_name: String, name: String): Boolean =
       
    36   {
       
    37     val select =
       
    38       Data.table.select(List(Data.name), Data.where_equal(session_name, theory_name, name))
       
    39     db.using_statement(select)(stmt => stmt.execute_query().next())
    27   }
    40   }
    28 
    41 
    29   def read_names(db: SQL.Database, session_name: String, theory_name: String): List[String] =
    42   def read_names(db: SQL.Database, session_name: String, theory_name: String): List[String] =
    30   {
    43   {
    31     val select = Data.table.select(List(Data.name), Data.where_equal(session_name, theory_name))
    44     val select = Data.table.select(List(Data.name), Data.where_equal(session_name, theory_name))
    32     db.using_statement(select)(stmt =>
    45     db.using_statement(select)(stmt =>
    33       stmt.execute_query().iterator(res => res.string(Data.name)).toList)
    46       stmt.execute_query().iterator(res => res.string(Data.name)).toList)
    34   }
    47   }
    35 
    48 
    36   def read_name(db: SQL.Database, session_name: String, theory_name: String, name: String): Boolean =
    49   def read_theory_names(db: SQL.Database, session_name: String): List[(String, String)] =
    37   {
    50   {
    38     val select =
    51     val select = Data.table.select(List(Data.theory_name, Data.name), Data.where_equal(session_name))
    39       Data.table.select(List(Data.name),
    52     db.using_statement(select)(stmt =>
    40         Data.where_equal(session_name, theory_name) + " AND " + Data.name.equal(name))
    53       stmt.execute_query().iterator(res =>
    41     db.using_statement(select)(stmt => stmt.execute_query().next())
    54         (res.string(Data.theory_name), res.string(Data.name))).toList)
    42   }
    55   }
    43 
    56 
    44   def message(msg: String, theory_name: String, name: String): String =
    57   def message(msg: String, theory_name: String, name: String): String =
    45     msg + " " + quote(name) + " for theory " + quote(theory_name)
    58     msg + " " + quote(name) + " for theory " + quote(theory_name)
       
    59 
       
    60   def compound_name(a: String, b: String): String = a + ":" + b
    46 
    61 
    47   sealed case class Entry(
    62   sealed case class Entry(
    48     session_name: String,
    63     session_name: String,
    49     theory_name: String,
    64     theory_name: String,
    50     name: String,
    65     name: String,
    51     compressed: Boolean,
    66     compressed: Boolean,
    52     body: Future[Bytes])
    67     body: Future[Bytes])
    53   {
    68   {
    54     override def toString: String = theory_name + ":" + name
    69     override def toString: String = compound_name(theory_name, name)
    55 
    70 
    56     def write(db: SQL.Database)
    71     def write(db: SQL.Database)
    57     {
    72     {
    58       val bytes = body.join
    73       val bytes = body.join
    59       db.using_statement(Data.table.insert())(stmt =>
    74       db.using_statement(Data.table.insert())(stmt =>
    64         stmt.bool(4) = compressed
    79         stmt.bool(4) = compressed
    65         stmt.bytes(5) = bytes
    80         stmt.bytes(5) = bytes
    66         stmt.execute()
    81         stmt.execute()
    67       })
    82       })
    68     }
    83     }
       
    84 
       
    85     def body_uncompressed: Bytes =
       
    86       if (compressed) body.join.uncompress() else body.join
       
    87   }
       
    88 
       
    89   def make_regex(pattern: String): Regex =
       
    90   {
       
    91     @tailrec def make(result: List[String], depth: Int, chs: List[Char]): Regex =
       
    92       chs match {
       
    93         case '*' :: '*' :: rest => make("[^:]*" :: result, depth, rest)
       
    94         case '*' :: rest => make("[^:/]*" :: result, depth, rest)
       
    95         case '?' :: rest => make("[^:/]" :: result, depth, rest)
       
    96         case '\\' :: c :: rest => make(("\\" + c) :: result, depth, rest)
       
    97         case '{' :: rest => make("(" :: result, depth + 1, rest)
       
    98         case ',' :: rest if depth > 0 => make("|" :: result, depth, rest)
       
    99         case '}' :: rest if depth > 0 => make(")" :: result, depth - 1, rest)
       
   100         case c :: rest if ".+()".contains(c) => make(("\\" + c) :: result, depth, rest)
       
   101         case c :: rest => make(c.toString :: result, depth, rest)
       
   102         case Nil => result.reverse.mkString.r
       
   103       }
       
   104     make(Nil, 0, pattern.toList)
    69   }
   105   }
    70 
   106 
    71   def make_entry(session_name: String, args: Markup.Export.Args, body: Bytes): Entry =
   107   def make_entry(session_name: String, args: Markup.Export.Args, body: Bytes): Entry =
    72   {
   108   {
    73     Entry(session_name, args.theory_name, args.name, args.compress,
   109     Entry(session_name, args.theory_name, args.name, args.compress,
    76 
   112 
    77   def read_entry(db: SQL.Database, session_name: String, theory_name: String, name: String): Entry =
   113   def read_entry(db: SQL.Database, session_name: String, theory_name: String, name: String): Entry =
    78   {
   114   {
    79     val select =
   115     val select =
    80       Data.table.select(List(Data.compressed, Data.body),
   116       Data.table.select(List(Data.compressed, Data.body),
    81         Data.where_equal(session_name, theory_name) + " AND " + Data.name.equal(name))
   117         Data.where_equal(session_name, theory_name, name))
    82     db.using_statement(select)(stmt =>
   118     db.using_statement(select)(stmt =>
    83     {
   119     {
    84       val res = stmt.execute_query()
   120       val res = stmt.execute_query()
    85       if (res.next()) {
   121       if (res.next()) {
    86         val compressed = res.bool(Data.compressed)
   122         val compressed = res.bool(Data.compressed)
   124       consumer.shutdown()
   160       consumer.shutdown()
   125       if (close) db.close()
   161       if (close) db.close()
   126       export_errors.value.reverse
   162       export_errors.value.reverse
   127     }
   163     }
   128   }
   164   }
       
   165 
       
   166 
       
   167   /* Isabelle tool wrapper */
       
   168 
       
   169   val default_export_dir = Path.explode("export")
       
   170 
       
   171   val isabelle_tool = Isabelle_Tool("export", "retrieve theory exports", args =>
       
   172   {
       
   173     /* arguments */
       
   174 
       
   175     var export_dir = default_export_dir
       
   176     var dirs: List[Path] = Nil
       
   177     var export_list = false
       
   178     var no_build = false
       
   179     var options = Options.init()
       
   180     var system_mode = false
       
   181     var export_pattern = ""
       
   182 
       
   183     val getopts = Getopts("""
       
   184 Usage: isabelle export [OPTIONS] SESSION
       
   185 
       
   186   Options are:
       
   187     -D DIR       target directory for exported files (default: """ + default_export_dir + """)
       
   188     -d DIR       include session directory
       
   189     -l           list exports
       
   190     -n           no build of session
       
   191     -o OPTION    override Isabelle system OPTION (via NAME=VAL or NAME)
       
   192     -s           system build mode for session image
       
   193     -x PATTERN   extract files matching pattern (e.g. "*:**" for all)
       
   194 
       
   195   List or export theory exports for SESSION: named blobs produced by
       
   196   isabelle build. Option -l or -x is required.
       
   197 
       
   198   The PATTERN language resembles glob patterns in the shell, with ? and *
       
   199   (both excluding ":" and "/"), ** (excluding ":"), and [abc] or [^abc],
       
   200   and variants {pattern1,pattern2,pattern3}.
       
   201 """,
       
   202       "D:" -> (arg => export_dir = Path.explode(arg)),
       
   203       "d:" -> (arg => dirs = dirs ::: List(Path.explode(arg))),
       
   204       "l" -> (_ => export_list = true),
       
   205       "n" -> (_ => no_build = true),
       
   206       "o:" -> (arg => options = options + arg),
       
   207       "s" -> (_ => system_mode = true),
       
   208       "x:" -> (arg => export_pattern = arg))
       
   209 
       
   210     val more_args = getopts(args)
       
   211     val session_name =
       
   212       more_args match {
       
   213         case List(session_name) if export_list || export_pattern != "" => session_name
       
   214         case _ => getopts.usage()
       
   215       }
       
   216 
       
   217 
       
   218     /* build */
       
   219 
       
   220     val progress = new Console_Progress()
       
   221 
       
   222     if (!no_build &&
       
   223         !Build.build(options, no_build = true, dirs = dirs, system_mode = system_mode,
       
   224           sessions = List(session_name)).ok)
       
   225     {
       
   226       progress.echo("Build started for Isabelle/" + session_name + " ...")
       
   227       progress.interrupt_handler {
       
   228         val res =
       
   229           Build.build(options, progress = progress, dirs = dirs, system_mode = system_mode,
       
   230             sessions = List(session_name))
       
   231         if (!res.ok) sys.exit(res.rc)
       
   232       }
       
   233     }
       
   234 
       
   235 
       
   236     /* database */
       
   237 
       
   238     val store = Sessions.store(system_mode)
       
   239     val database =
       
   240       store.find_database(session_name) match {
       
   241         case None => error("Missing database for session " + quote(session_name))
       
   242         case Some(database) => database
       
   243       }
       
   244 
       
   245     using(SQLite.open_database(database))(db =>
       
   246     {
       
   247       db.transaction {
       
   248         val export_names = read_theory_names(db, session_name)
       
   249 
       
   250         // list
       
   251         if (export_list) {
       
   252           (for ((theory_name, name) <- export_names) yield compound_name(theory_name, name)).
       
   253             sorted.foreach(Output.writeln(_, stdout = true))
       
   254         }
       
   255 
       
   256         // export
       
   257         if (export_pattern != "") {
       
   258           val regex = make_regex(export_pattern)
       
   259           for {
       
   260             (theory_name, name) <- export_names
       
   261             if regex.pattern.matcher(compound_name(theory_name, name)).matches
       
   262           } {
       
   263             val entry = read_entry(db, session_name, theory_name, name)
       
   264             val path = export_dir + Path.basic(theory_name) + Path.explode(name)
       
   265 
       
   266             progress.echo("exporting " + path)
       
   267             Isabelle_System.mkdirs(path.dir)
       
   268             Bytes.write(path, entry.body_uncompressed)
       
   269           }
       
   270         }
       
   271       }
       
   272     })
       
   273   })
   129 }
   274 }