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