src/Pure/Admin/afp.scala
author wenzelm
Mon Mar 25 19:50:52 2019 +0100 (3 months ago)
changeset 69982 f150253cb201
parent 69981 3dced198b9ec
child 69995 2d5c313e8582
permissions -rw-r--r--
RDF meta data for AFP entries;
tuned;
wenzelm@66820
     1
/*  Title:      Pure/Admin/afp.scala
wenzelm@66820
     2
    Author:     Makarius
wenzelm@66820
     3
wenzelm@66820
     4
Administrative support for the Archive of Formal Proofs.
wenzelm@66820
     5
*/
wenzelm@66820
     6
wenzelm@66820
     7
package isabelle
wenzelm@66820
     8
wenzelm@66820
     9
wenzelm@69980
    10
import java.time.LocalDate
wenzelm@69974
    11
import scala.collection.immutable.SortedMap
wenzelm@69974
    12
wenzelm@69974
    13
wenzelm@66820
    14
object AFP
wenzelm@66820
    15
{
wenzelm@69787
    16
  val repos_source = "https://isabelle.sketis.net/repos/afp-devel"
wenzelm@66854
    17
wenzelm@69693
    18
  val groups: Map[String, String] =
wenzelm@69693
    19
    Map("large" -> "full 64-bit memory model or word arithmetic required",
wenzelm@69693
    20
      "slow" -> "CPU time much higher than 60min (on mid-range hardware)",
wenzelm@69693
    21
      "very_slow" -> "elapsed time of many hours (on high-end hardware)")
wenzelm@69693
    22
wenzelm@69693
    23
  def groups_bulky: List[String] = List("large", "slow")
wenzelm@69693
    24
wenzelm@66824
    25
  def init(options: Options, base_dir: Path = Path.explode("$AFP_BASE")): AFP =
wenzelm@66824
    26
    new AFP(options, base_dir)
wenzelm@66820
    27
wenzelm@69973
    28
wenzelm@69973
    29
  /* entries */
wenzelm@69973
    30
wenzelm@69977
    31
  def parse_date(s: String): Date =
wenzelm@69977
    32
  {
wenzelm@69977
    33
    val t = Date.Formatter.pattern("uuuu-MM-dd").parse(s)
wenzelm@69980
    34
    Date(LocalDate.from(t).atStartOfDay(Date.timezone_berlin))
wenzelm@69977
    35
  }
wenzelm@69977
    36
wenzelm@69975
    37
  sealed case class Entry(name: String, metadata: Properties.T, sessions: List[String])
wenzelm@69976
    38
  {
wenzelm@69977
    39
    def get(prop: String): Option[String] = Properties.get(metadata, prop)
wenzelm@69977
    40
    def get_string(prop: String): String = get(prop).getOrElse("")
wenzelm@69977
    41
    def get_strings(prop: String): List[String] =
wenzelm@69977
    42
      Library.space_explode(',', get_string(prop)).map(_.trim).filter(_.nonEmpty)
wenzelm@69977
    43
wenzelm@69977
    44
    def title: String = get_string("title")
wenzelm@69977
    45
    def authors: List[String] = get_strings("author")
wenzelm@69981
    46
    def date: Date =
wenzelm@69981
    47
      parse_date(get("date").getOrElse(error("Missing date for entry " + quote(name))))
wenzelm@69977
    48
    def topics: List[String] = get_strings("topic")
wenzelm@69981
    49
    def `abstract`: String = get_string("abstract").trim
wenzelm@69977
    50
    def maintainers: List[String] = get_strings("notify")
wenzelm@69977
    51
    def contributors: List[String] = get_strings("contributors")
wenzelm@69981
    52
    def license: String = get("license").getOrElse("BSD")
wenzelm@69982
    53
wenzelm@69982
    54
    def rdf_meta_data: Properties.T =
wenzelm@69982
    55
      RDF.meta_data(
wenzelm@69982
    56
        proper_string(title).map(Markup.META_TITLE -> _).toList :::
wenzelm@69982
    57
        authors.map(Markup.META_CREATOR -> _) :::
wenzelm@69982
    58
        contributors.map(Markup.META_CONTRIBUTOR -> _) :::
wenzelm@69982
    59
        List(Markup.META_DATE -> RDF.date_format(date)) :::
wenzelm@69982
    60
        List(Markup.META_LICENSE -> license) :::
wenzelm@69982
    61
        proper_string(`abstract`).map(Markup.META_DESCRIPTION -> _).toList)
wenzelm@69976
    62
  }
wenzelm@66820
    63
}
wenzelm@66820
    64
wenzelm@66824
    65
class AFP private(options: Options, val base_dir: Path)
wenzelm@66820
    66
{
wenzelm@66821
    67
  override def toString: String = base_dir.expand.toString
wenzelm@66821
    68
wenzelm@66820
    69
  val main_dir: Path = base_dir + Path.explode("thys")
wenzelm@66820
    70
wenzelm@66821
    71
wenzelm@69975
    72
  /* metadata */
wenzelm@69975
    73
wenzelm@69975
    74
  private val entry_metadata: Map[String, Properties.T] =
wenzelm@69975
    75
  {
wenzelm@69975
    76
    val metadata_file = base_dir + Path.explode("metadata/metadata")
wenzelm@69975
    77
wenzelm@69975
    78
    var result = Map.empty[String, Properties.T]
wenzelm@69975
    79
    var section = ""
wenzelm@69975
    80
    var props = List.empty[Properties.Entry]
wenzelm@69975
    81
wenzelm@69975
    82
    val Section = """^\[(\S+)\]\s*$""".r
wenzelm@69975
    83
    val Property = """^(\S+)\s*=(.*)$""".r
wenzelm@69975
    84
    val Extra_Line = """^\s+(.*)$""".r
wenzelm@69975
    85
    val Blank_Line = """^\s*$""".r
wenzelm@69975
    86
wenzelm@69975
    87
    def flush()
wenzelm@69975
    88
    {
wenzelm@69978
    89
      if (section != "") result += (section -> props.reverse.filter(p => p._2.nonEmpty))
wenzelm@69975
    90
      section = ""
wenzelm@69975
    91
      props = Nil
wenzelm@69975
    92
    }
wenzelm@69975
    93
wenzelm@69975
    94
    for ((line, i) <- split_lines(File.read(metadata_file)).zipWithIndex)
wenzelm@69975
    95
    {
wenzelm@69975
    96
      def err(msg: String): Nothing =
wenzelm@69975
    97
        error(msg + Position.here(Position.Line_File(i + 1, metadata_file.expand.implode)))
wenzelm@69975
    98
wenzelm@69975
    99
      line match {
wenzelm@69975
   100
        case Section(name) => flush(); section = name
wenzelm@69975
   101
        case Property(a, b) =>
wenzelm@69975
   102
          if (section == "") err("Property without a section")
wenzelm@69978
   103
          props = (a -> b.trim) :: props
wenzelm@69975
   104
        case Extra_Line(line) =>
wenzelm@69975
   105
          props match {
wenzelm@69975
   106
            case Nil => err("Extra line without a property")
wenzelm@69975
   107
            case (a, b) :: rest => props = (a, b + "\n" + line.trim) :: rest
wenzelm@69975
   108
          }
wenzelm@69975
   109
        case Blank_Line() =>
wenzelm@69975
   110
        case _ => err("Bad input")
wenzelm@69975
   111
      }
wenzelm@69975
   112
    }
wenzelm@69975
   113
wenzelm@69975
   114
    flush()
wenzelm@69975
   115
    result
wenzelm@69975
   116
  }
wenzelm@69975
   117
wenzelm@69975
   118
wenzelm@69979
   119
  /* entries */
wenzelm@66821
   120
wenzelm@69974
   121
  val entries_map: SortedMap[String, AFP.Entry] =
wenzelm@69973
   122
  {
wenzelm@69973
   123
    val entries =
wenzelm@69974
   124
      for (name <- Sessions.parse_roots(main_dir + Sessions.ROOTS)) yield {
wenzelm@69973
   125
        val metadata =
wenzelm@69973
   126
          entry_metadata.getOrElse(name, error("Entry without metadata: " + quote(name)))
wenzelm@69973
   127
        val sessions =
wenzelm@69973
   128
          Sessions.parse_root_entries(main_dir + Path.explode(name) + Sessions.ROOT).map(_.name)
wenzelm@69973
   129
        AFP.Entry(name, metadata, sessions)
wenzelm@69974
   130
      }
wenzelm@69973
   131
wenzelm@69974
   132
    val entries_map =
wenzelm@69974
   133
      (SortedMap.empty[String, AFP.Entry] /: entries)({ case (m, e) => m + (e.name -> e) })
wenzelm@69974
   134
wenzelm@69973
   135
    val extra_metadata =
wenzelm@69974
   136
      (for ((name, _) <- entry_metadata.iterator if !entries_map.isDefinedAt(name)) yield name).
wenzelm@69974
   137
        toList.sorted
wenzelm@69974
   138
    if (extra_metadata.nonEmpty)
wenzelm@69974
   139
      error("Meta data without entry: " + commas_quote(extra_metadata))
wenzelm@69973
   140
wenzelm@69974
   141
    entries_map
wenzelm@69973
   142
  }
wenzelm@66820
   143
wenzelm@69974
   144
  val entries: List[AFP.Entry] = entries_map.toList.map(_._2)
wenzelm@69979
   145
wenzelm@69979
   146
wenzelm@69979
   147
  /* sessions */
wenzelm@69979
   148
wenzelm@69979
   149
  val sessions_map: SortedMap[String, AFP.Entry] =
wenzelm@69979
   150
    (SortedMap.empty[String, AFP.Entry] /: entries)(
wenzelm@69979
   151
      { case (m1, e) => (m1 /: e.sessions)({ case (m2, s) => m2 + (s -> e) }) })
wenzelm@69979
   152
wenzelm@66821
   153
  val sessions: List[String] = entries.flatMap(_.sessions)
wenzelm@66824
   154
wenzelm@67052
   155
  val sessions_structure: Sessions.Structure =
wenzelm@67026
   156
    Sessions.load_structure(options, dirs = List(main_dir)).
wenzelm@67025
   157
      selection(Sessions.Selection(sessions = sessions.toList))
wenzelm@66821
   158
wenzelm@66821
   159
wenzelm@66824
   160
  /* dependency graph */
wenzelm@66821
   161
wenzelm@66824
   162
  private def sessions_deps(entry: AFP.Entry): List[String] =
wenzelm@66824
   163
    entry.sessions.flatMap(sessions_structure.imports_graph.imm_preds(_)).distinct.sorted
wenzelm@66824
   164
wenzelm@66852
   165
  lazy val entries_graph: Graph[String, Unit] =
wenzelm@66821
   166
  {
wenzelm@66821
   167
    val session_entries =
wenzelm@66831
   168
      (Map.empty[String, String] /: entries) {
wenzelm@66831
   169
        case (m1, e) => (m1 /: e.sessions) { case (m2, s) => m2 + (s -> e.name) }
wenzelm@66821
   170
      }
wenzelm@66831
   171
    (Graph.empty[String, Unit] /: entries) { case (g, entry) =>
wenzelm@66831
   172
      val e1 = entry.name
wenzelm@66832
   173
      (g.default_node(e1, ()) /: sessions_deps(entry)) { case (g1, s) =>
wenzelm@66832
   174
        (g1 /: session_entries.get(s).filterNot(_ == e1)) { case (g2, e2) =>
wenzelm@66852
   175
          try { g2.default_node(e2, ()).add_edge_acyclic(e2, e1) }
wenzelm@66852
   176
          catch {
wenzelm@66852
   177
            case exn: Graph.Cycles[_] =>
wenzelm@66852
   178
              error(cat_lines(exn.cycles.map(cycle =>
wenzelm@66852
   179
                "Cyclic dependency of " + cycle.map(c => quote(c.toString)).mkString(" via ") +
wenzelm@66852
   180
                " due to session " + quote(s))))
wenzelm@66832
   181
          }
wenzelm@66821
   182
        }
wenzelm@66821
   183
      }
wenzelm@66824
   184
    }
wenzelm@66821
   185
  }
wenzelm@66821
   186
wenzelm@66824
   187
  def entries_graph_display: Graph_Display.Graph =
wenzelm@66824
   188
    Graph_Display.make_graph(entries_graph)
wenzelm@66823
   189
wenzelm@66824
   190
  def entries_json_text: String =
wenzelm@66824
   191
    (for (entry <- entries.iterator) yield {
wenzelm@66824
   192
      val distrib_deps = sessions_deps(entry).filterNot(sessions.contains(_))
wenzelm@66824
   193
      val afp_deps = entries_graph.imm_preds(entry.name).toList
wenzelm@66824
   194
      """
wenzelm@66821
   195
 {""" + JSON.Format(entry.name) + """:
wenzelm@66821
   196
  {"distrib_deps": """ + JSON.Format(distrib_deps) + """,
wenzelm@66821
   197
   "afp_deps": """ + JSON.Format(afp_deps) + """
wenzelm@66821
   198
  }
wenzelm@66821
   199
 }"""
wenzelm@66824
   200
    }).mkString("[", ", ", "\n]\n")
wenzelm@66861
   201
wenzelm@66861
   202
wenzelm@66861
   203
  /* partition sessions */
wenzelm@66861
   204
wenzelm@67817
   205
  val force_partition1: List[String] = List("Category3", "HOL-ODE")
wenzelm@67776
   206
wenzelm@66861
   207
  def partition(n: Int): List[String] =
wenzelm@66861
   208
    n match {
wenzelm@66861
   209
      case 0 => Nil
wenzelm@66861
   210
      case 1 | 2 =>
wenzelm@66861
   211
        val graph = sessions_structure.build_graph.restrict(sessions.toSet)
wenzelm@67817
   212
        val force_part1 =
wenzelm@67817
   213
          graph.all_preds(graph.all_succs(force_partition1.filter(graph.defined(_)))).toSet
wenzelm@67817
   214
        val (part1, part2) = graph.keys.partition(a => force_part1(a) || graph.is_isolated(a))
wenzelm@67817
   215
        if (n == 1) part1 else part2
wenzelm@66861
   216
      case _ => error("Bad AFP partition: " + n + " (should be 0, 1, 2)")
wenzelm@66861
   217
    }
wenzelm@66820
   218
}