diff --git a/src/Pure/General/mailman.scala b/src/Pure/General/mailman.scala --- a/src/Pure/General/mailman.scala +++ b/src/Pure/General/mailman.scala @@ -1,646 +1,658 @@ /* Title: Pure/General/mailman.scala Author: Makarius Support for Mailman list servers. */ package isabelle import java.net.URL import scala.annotation.tailrec import scala.util.matching.Regex import scala.util.matching.Regex.Match object Mailman { /* mailing list messages */ def is_address(s: String): Boolean = s.contains('@') && s.contains('.') && !s.contains(' ') private val standard_name: Map[String, String] = Map( + "121171528@qq.com" -> "Guo Fan\n121171528@qq.com", "Aman Pohjola, Johannes (Data61, Kensington NSW)" -> "Johannes Aman Pohjola", "Andrei de AraÃjo Formiga" -> "Andrei de Araujo Formiga", "Benedikt.AHRENS@unice.fr" -> "Benedikt Ahrens\nBenedikt.AHRENS@unice.fr", "Berg, Nils Erik" -> "Nils Erik Berg", "Berger U." -> "Ulrich Berger", "Bisping, Benjamin" -> "Benjamin Bisping", "Blanchette, J.C." -> "Jasmin Christian Blanchette", "Buday Gergely István" -> "Gergely Buday", "CALaF1UJ9Uy0vGCu4WkBmbfuPDxG7nFm8hfeCMP+O3g7_5CQ0Bw@mail.gmail.com" -> "", "CRACIUN F." -> "Florin Craciun", "Carsten Schuermann" -> "Carsten Schürmann", "Chris" -> "", "Christoph Lueth" -> "Christoph Lüth", "Claude Marche" -> "Claude Marché", "Daniel StÃwe" -> "Daniel Stüwe", "Daniel.Matichuk@data61.csiro.au" -> "Daniel Matichuk\nDaniel.Matichuk@data61.csiro.au", "Daniel.Matichuk@nicta.com.au" -> "Daniel Matichuk\nDaniel.Matichuk@nicta.com.au", "David MENTRE" -> "David MENTRÉ", "Dey, Katie" -> "Katie Dey", "Dr. Brendan Patrick Mahony" -> "Brendan Mahony", "Farn" -> "Farn Wang", "Farquhar, Colin I" -> "Colin Farquhar", "Fernandez, Matthew" -> "Matthew Fernandez", "Filip Maric" -> "Filip Marić", "Filip MariÄ" -> "Filip Marić", "Fleury Mathias" -> "Mathias Fleury", "Francisco Jose CHAVES ALONSO" -> "Francisco Jose Chaves Alonso", "Frederic Tuong (Dr)" -> "Frederic Tuong", "Fulya" -> "Fulya Horozal", "George K." -> "George Karabotsos", "Gidon Ernst" -> "Gidon ERNST", "Gransden, Thomas" -> "Thomas Gransden", "Hans-JÃrg Schurr" -> "Hans-Jörg Schurr", "Henri DEBRAT" -> "Henri Debrat", "Hitoshi Ohsaki (RTA publicity chair)" -> "Hitoshi Ohsaki", "Häuselmann Rafael" -> "Rafael Häuselmann", "Isabelle" -> "", "J. Juhas (TUM)" -> "Jonatan Juhas", "Jackson, Vincent (Data61, Kensington NSW)" -> "Vincent Jackson", "Janney, Mark-P26816" -> "Mark Janney", "Jean François Molderez" -> "Jean-François Molderez", "Jean-Francois Molderez" -> "Jean-François Molderez", "John R Harrison" -> "John Harrison", "Jose DivasÃn" -> "Jose Divasón", "Julian" -> "", "Julien" -> "", "Klein, Gerwin (Data61, Kensington NSW)" -> "Gerwin Klein", "Kobayashi, Hidetsune" -> "Hidetsune Kobayashi", "Kylie Williams (IND)" -> "Kylie Williams", "Laarman, A.W." -> "A.W. Laarman", "Laurent Thery" -> "Laurent Théry", "Li, Chanjuan" -> "Li Chanjuan", "Lochbihler Andreas" -> "Andreas Lochbihler", "Luckhardt, Daniel" -> "Daniel Luckhardt", "Lutz Schroeder" -> "Lutz Schröder", "Lutz SchrÃder" -> "Lutz Schröder", "MACKENZIE Carlin" -> "Carlin MACKENZIE", "Makarius" -> "Makarius Wenzel", "Marco" -> "", "Mark" -> "", "Markus Mueller-Olm" -> "Markus Müller-Olm", "Markus" -> "", "Marmsoler, Diego" -> "Diego Marmsoler", "Martin Klebermass" -> "Martin Klebermaß", + "Martyn Johnson via RT" -> "", + "Mathias.Fleury@ens-rennes.fr" -> "Mathias Fleury\nmathias.fleury12@gmail.com", "Matthew" -> "", "Matthews, John R" -> "John Matthews", "McCarthy, Jim (C3ID)" -> "Jim McCarthy", "McCue, Brian" -> "Brian McCue", "Michael FÃrber" -> "Michael Färber", "Michel" -> "", "Miranda, Brando" -> "Brando Miranda", "Moscato, Mariano M. \\(LARC-D320\\)\\[NATIONAL INSTITUTE OF AEROSPACE\\]" -> "Mariano M. Moscato", "Mr Julian Fell" -> "Julian Fell", "Mueller Peter" -> "Peter Müller", "Munoz, Cesar Augusto (LARC-D320)" -> "Cesar A. Munoz", "Nadel, Alexander" -> "Alexander Nadel", "Nagashima, Yutaka" -> "Yutaka Nagashima", "Norrish, Michael (Data61, Acton)" -> "Michael Norrish", "O'Leary, John W" -> "John W O'Leary", "Omar Montano Rivas" -> "Omar Montaño Rivas", "Omar MontaÃo Rivas" -> "Omar Montaño Rivas", "OndÅej KunÄar" -> "Ondřej Kunčar", + "PALMER Jake" -> "Jake Palmer", "PAQUI LUCIO" -> "Paqui Lucio", "Pal, Abhik" -> "Abhik Pal", "Pasupuleti, Vijay" -> "Vijay Pasupuleti", "Peter Vincent Homeier" -> "Peter V. Homeier", "Peter" -> "", "Philipp Ruemmer" -> "Philipp Rümmer", "Philipp RÃmmer" -> "Philipp Rümmer", + "Piete Brooks via RT" -> "", "RTA publicity chair" -> "", "Raamsdonk, F. van" -> "Femke van Raamsdonk", "Raul Gutierrez" -> "Raúl Gutiérrez", "Renà Thiemann" -> "René Thiemann", "Ridgway, John V. E." -> "John V. E. Ridgway", "Roggenbach M." -> "Markus Roggenbach", "Rosu, Grigore" -> "Grigore Rosu", "Rozman, Mihaela" -> "Mihaela Rozman", "Schmaltz, J." -> "Julien Schmaltz", "Serguei A. Mokhov on behalf of PST-11" -> "Serguei A. Mokhov", "Serguei Mokhov" -> "Serguei A. Mokhov", "Shumeiko, Igor" -> "Igor Shumeiko", "Siek, Jeremy" -> "Jeremy Siek", "Silvio.Ranise@loria.fr" -> "Silvio Ranise\nSilvio.Ranise@loria.fr", "Siu, Tony" -> "Tony Siu", "Stüber, Sebastian" -> "Sebastian Stüber", "Thiemann, Rene" -> "René Thiemann", "Thiemann, René" -> "René Thiemann", "Thomas Arthur Leck Sewell" -> "Thomas Sewell", "Thomas Goethel" -> "Thomas Göthel", "Thomas.Sewell@data61.csiro.au" -> "Thomas Sewell\nThomas.Sewell@data61.csiro.au", + "Tjark Weber via RT" -> "Tjark Weber", "Toby.Murray@data61.csiro.au" -> "Toby Murray\nToby.Murray@data61.csiro.au", "Urban, Christian" -> "Christian Urban", "Ursula Eschbach" -> "", "Van Staden Stephan" -> "Stephan van Staden", "Viktor Kuncak" -> "Viktor Kunčak", "Viorel Preoteasaa" -> "Viorel Preoteasa", "Wickerson, John P" -> "John Wickerson", "Wong, Yat" -> "Yat Wong", "YAMADA, Akihisa" -> "Akihisa Yamada", "YliÃs Falcone" -> "Yliès Falcone", "amir mohajeri" -> "Amir Mohajeri", "aniello murano" -> "Aniello Murano", "barzan stefania" -> "Stefania Barzan", "benhamou" -> "Belaid Benhamou", "charmi panchal" -> "Charmi Panchal", "chen kun" -> "Chen Kun", "chunhan wu" -> "Chunhan Wu", "daniel de la concepción sáez" -> "Daniel de la Concepción Sáez", "daniel.luckhardt@mathematik.uni-goettingen.de" -> "Logiker@gmx.net", "david streader" -> "David Streader", "eschbach@in.tum.de" -> "", "f.rabe@jacobs-university.de" -> "florian.rabe@fau.de", "florian@haftmann-online.de" -> "haftmann@in.tum.de", "fredegar@haftmann-online.de" -> "haftmann@in.tum.de", "gallais @ ensl.org" -> "Guillaume Allais", "geng chen" -> "Geng Chen", "henning.seidler" -> "Henning Seidler", "hkb" -> "Hidetsune Kobayashi", + "jobs-pm@inf.ethz.ch" -> "", "julien@RadboudUniversity" -> "", "jun sun" -> "Jun Sun", "jwang whu.edu.cn (jwang)" -> "jwang", "kostas pouliasis" -> "Kostas Pouliasis", "kristof.teichel@ptb.de" -> "Kristof Teichel\nkristof.teichel@ptb.de", "lucas cavalcante" -> "Lucas Cavalcante", "mahmoud abdelazim" -> "Mahmoud Abdelazim", "manish surolia" -> "Manish Surolia", "mantel" -> "Heiko Mantel", "marco caminati" -> "Marco Caminati", + "mathias.fleury@ens-rennes.fr" -> "Mathias Fleury\nmathias.fleury12@gmail.com", "merz@loria.fr" -> "stephan.merz@loria.fr", "michel levy" -> "Michel Levy", "michel.levy2009@laposte.net" -> "Michel Levy\nmichel.levy2009@laposte.net", "nemouchi" -> "Yakoub Nemouchi", + "noam neer" -> "Noam Neer", + "olfa mraihi" -> "Olfa Mraihi", + "pathsnottakenworkshop@gmail.com" -> "Leo Freitas\nleo.freitas@newcastle.ac.uk", "patrick barlatier" -> "Patrick Barlatier", "patrick dabou" -> "Patrick Dabou", "paul zimmermann" -> "Paul Zimmermann", "popescu2@illinois.edu" -> "Andrei Popescu", "recruiting" -> "", + "recruiting@mais.informatik.tu-darmstadt.de" -> "", "roux cody" -> "Cody Roux", "scott constable" -> "Scott Constable", "superuser@mattweidner.com" -> "Matthew Weidner\nsuperuser@mattweidner.com", "urban@math.lmu.de" -> "Christian Urban\nurban@math.lmu.de", "veronique.cortier@loria.fr" -> "Veronique.Cortier@loria.fr", "vikram singh" -> "Vikram Singh", "wenzelm@in.tum.de" -> "makarius@sketis.net", "werner@lix.polytechnique.fr" -> "Benjamin Werner\nwerner@lix.polytechnique.fr", "wmansky@cs.princeton.edu" -> "William Mansky\nwmansky@cs.princeton.edu", "y.nemouchi@ensbiotech.edu.dz" -> "Yakoub Nemouchi\ny.nemouchi@ensbiotech.edu.dz", "ÐÑÐÐÐÑÐÐÐ ÐÐÐÐÐÐÐÑÐÐÐÑ ÐÐÐÑÐÐÐ" -> "", "∀X.Xπ - Tutorials about Proofs" -> "Bruno Woltzenlogel Paleo", ).withDefault(identity) def standard_author_info(author_info: List[String]): List[String] = author_info.flatMap(s => split_lines(standard_name.getOrElse(s, s))).distinct sealed case class Message( name: String, date: Date, title: String, author_info: List[String], body: String, tags: List[String]) { if (author_info.isEmpty || author_info.exists(_.isEmpty)) { error("Bad author information in " + quote(name)) } override def toString: String = name } object Messages { type Graph = isabelle.Graph[String, Message] def apply(msgs: List[Message]): Messages = { def make_node(g: Graph, node: String, msg: Message): Graph = if (g.defined(node) && Date.Ordering.compare(g.get_node(node).date, msg.date) >= 0) g else g.default_node(node, msg) def connect_nodes(g: Graph, nodes: List[String]): Graph = nodes match { case Nil => g case a :: bs => bs.foldLeft(g)({ case (g1, b) => g1.add_edge(a, b).add_edge(b, a) }) } new Messages(msgs.sortBy(_.date)(Date.Ordering), msgs.foldLeft[Graph](Graph.string)( { case (graph, msg) => val nodes = msg.author_info connect_nodes(nodes.foldLeft(graph)(make_node(_, _, msg)), nodes) })) } def find(dir: Path): Messages = { val msgs = for { archive <- List(Isabelle_Users, Isabelle_Dev) msg <- archive.find_messages(dir + Path.basic(archive.list_name)) } yield msg Messages(msgs) } sealed case class Cluster(author_info: List[String]) { val (addresses, names) = author_info.partition(is_address) val name: String = names.headOption getOrElse { addresses match { case a :: _ => a.substring(0, a.indexOf('@')).replace('.', ' ') case Nil => error("Empty cluster") } } val name_lowercase: String = Word.lowercase(name) def get_address: Option[String] = addresses.headOption def unique: Boolean = names.length == 1 && addresses.length == 1 def multi: Boolean = names.length > 1 || addresses.length > 1 def print: String = { val entries = names ::: (if (addresses.isEmpty) Nil else List("")) ::: addresses entries.mkString("\n * ", "\n ", "") } } } class Messages private(val sorted: List[Message], val graph: Messages.Graph) { override def toString: String = "Messages(" + sorted.size + ")" object Node_Ordering extends scala.math.Ordering[String] { override def compare(a: String, b: String): Int = Date.Rev_Ordering.compare(graph.get_node(a).date, graph.get_node(b).date) } def get_cluster(msg: Message): Messages.Cluster = Messages.Cluster(graph.all_succs(msg.author_info).sorted.sorted(Node_Ordering)) def get_name(msg: Message): String = get_cluster(msg).name def get_address(msg: Message): String = get_cluster(msg).get_address getOrElse error("No author address for " + quote(msg.name)) def check(check_all: Boolean, check_multi: Boolean = false): Unit = { val clusters = sorted.map(get_cluster).distinct.sortBy(_.name_lowercase) if (check_all) { Output.writeln(cat_lines("clusters:" :: clusters.map(_.print))) } else { val multi = if (check_multi) clusters.filter(_.multi) else Nil if (multi.nonEmpty) { Output.writeln(cat_lines("ambiguous clusters:" :: multi.map(_.print))) } } val unknown = clusters.filter(cluster => cluster.get_address.isEmpty) if (unknown.nonEmpty) { Output.writeln(cat_lines("\nunknown mail:" :: unknown.map(_.print))) } } } /* mailing list archives */ abstract class Archive( url: URL, name: String = "", tag: String = "") { def message_regex: Regex def message_content(name: String, lines: List[String]): Message def message_match(lines: List[String], re: Regex): Option[Match] = lines.flatMap(re.findFirstMatchIn(_)).headOption def make_name(str: String): String = { val s = str.trim.replaceAll("""\s+""", " ").replaceAll(" at ", "@") .replace("mailbroy.informatik.tu-muenchen.de", "in.tum.de") .replace("informatik.tu-muenchen.de", "in.tum.de") if (s.startsWith("=") && s.endsWith("=")) "" else s } def make_body(lines: List[String]): String = cat_lines(Library.take_suffix[String](_.isEmpty, lines)._1) private val main_url: URL = Url(Library.take_suffix[Char](_ == '/', Url.trim_index(url).toString.toList)._1.mkString + "/") private val main_html = Url.read(main_url) val list_name: String = { val title = """