Skip to content

Commit b9b7f3c

Browse files
committed
Merge pull request #22 from retronym/release/2.10.3-RC2-markdown
Release/2.10.3 rc2 markdown
2 parents e7a80d0 + abfc4b1 commit b9b7f3c

File tree

6 files changed

+215
-59
lines changed

6 files changed

+215
-59
lines changed

README.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,20 @@ For now, it's still mostly manual. What you need to do:
1010

1111
1. Fill in community project list from emails. See the `community-projects.txt` file for the format and how to copy-paste into it.
1212
2. Update the highlights notes in `hand-written.md`.
13-
3. run `sbt console`, and then the following:
13+
3. run `sbt -Dfile.encoding=UTF-8 console`, and then the following if you want .html output:
1414

15-
scala> MakeReleaseNotes(new java.io.File("~/git/scala"), "v2.9.2", "v2.9.3")
15+
```
16+
scala> MakeReleaseNotes(new java.io.File("~/git/scala"), "v2.9.2", "v2.9.3")
17+
scala> MakeReleaseNotes(new java.io.File("~/git/scala"), "v2.9.2", "v2.9.3")(MarkDown) // markdown for scala-lang.org
18+
```
1619

1720
where the two strings are the tags to compare.
1821

22+
To make the download page:
23+
24+
```
25+
scala> new MakeDownloadPage("2.10.3-RC2").write()
26+
```
1927

2028
## Contributing
2129

src/main/scala/GitInfo.scala

Lines changed: 29 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,103 +3,93 @@ case class Commit(sha: String, author: String, header: String, body: String) {
33
override def toString = " * " + sha + " (" + author + ") " + header + " - " + body.take(5) + " ..."
44
}
55

6-
76
/** Gobal functions for dealing with git. */
87
object GitHelper {
98
def processGitCommits(gitDir: java.io.File, previousTag: String, currentTag: String): IndexedSeq[Commit] = {
109
import sys.process._
1110
val gitFormat = "%h %s" // sha and subject
12-
val log = Process(Seq("git", "--no-pager", "log", s"${previousTag}..${currentTag}","--format=format:"+gitFormat,"--no-merges", "--topo-order"), gitDir).lines
11+
val log = Process(Seq("git", "--no-pager", "log", s"${previousTag}..${currentTag}", "--format=format:" + gitFormat, "--no-merges", "--topo-order"), gitDir).lines
1312

1413
log.par.map(_.split(" ", 2)).collect {
1514
case Array(sha, title) =>
16-
val (author :: body) = Process(Seq("git", "--no-pager", "show", sha ,"--format=format:%aN%n%b","--quiet"), gitDir).lines.toList
15+
val (author :: body) = Process(Seq("git", "--no-pager", "show", sha, "--format=format:%aN%n%b", "--quiet"), gitDir).lines.toList
1716
Commit(sha, author, title, body.mkString("\n"))
1817
}.toVector
1918
}
2019

2120
def hasFixins(msg: String): Boolean = (
2221
(msg contains "SI-") /*&& ((msg.toLowerCase contains "fix") || (msg.toLowerCase contains "close"))*/
2322
)
24-
23+
2524
val siPattern = java.util.regex.Pattern.compile("(SI-[0-9]+)")
2625

27-
def fixLinks(commit: Commit): String = {
26+
def fixLinks(commit: Commit)(implicit targetLanguage: TargetLanguage): String = {
2827
val searchString = commit.body + commit.header
2928
val m = siPattern matcher searchString
3029
val issues = new collection.mutable.ArrayBuffer[String]
31-
while(m.find()) {
30+
while (m.find()) {
3231
issues += (m group 1)
3332
}
34-
issues map (si => """<a href="https://issues.scala-lang.org/browse/%s">%s</a>""" format (si, si)) mkString ", "
33+
issues map (si => targetLanguage.createHyperLink(s"https://issues.scala-lang.org/browse/$si", si)) mkString ", "
3534
}
3635

3736
def htmlEncode(s: String) = org.apache.commons.lang3.StringEscapeUtils.escapeHtml4(s)
3837
}
3938

40-
class GitInfo(gitDir: java.io.File, val previousTag: String, val currentTag: String) {
39+
class GitInfo(gitDir: java.io.File, val previousTag: String, val currentTag: String)(implicit targetLanguage: TargetLanguage) {
4140
import GitHelper._
4241
val commits = processGitCommits(gitDir, previousTag, currentTag)
4342

4443
val authors: Seq[(String, Int)] = {
45-
val grouped: Vector[(String,Int)] = (commits groupBy (_.author)).map { case (a,c) => a -> c.length }{collection.breakOut}
44+
val grouped: Vector[(String, Int)] = (commits groupBy (_.author)).map { case (a, c) => a -> c.length } { collection.breakOut }
4645
(grouped sortBy (_._2)).reverse
4746
}
4847

4948
val fixCommits =
50-
for {
51-
commit <- commits
52-
searchString = commit.body + commit.header
53-
if hasFixins(searchString)
54-
} yield commit
49+
for {
50+
commit <- commits
51+
searchString = commit.body + commit.header
52+
if hasFixins(searchString)
53+
} yield commit
5554

56-
private def commitShaLink(sha: String) =
57-
s"""<a href="https://github.com/scala/scala/commit/${sha}">${sha}</a>"""
55+
private def commitShaLink(sha: String) =
56+
targetLanguage.createHyperLink(s"https://github.com/scala/scala/commit/${sha}", sha)
5857

59-
private def blankLine(): String = "<p>&nbsp;</p>"
60-
private def header4(msg: String): String = s"<h4>$msg</h4>"
58+
private def blankLine(): String = targetLanguage.blankLine()
59+
private def header4(msg: String): String = targetLanguage.header4(msg)
6160

6261
def renderCommitterList: String = {
6362
val sb = new StringBuffer
6463
sb append blankLine()
6564
sb append header4("A big thank you to all the contributors!")
66-
sb append """|<table border="0" cellspacing="0" cellpadding="1">
67-
| <thead><tr><th>#</th><th align="left">Author</th></tr></thead>
68-
|<tbody>""".stripMargin
69-
for((author, count) <- authors)
70-
sb append s"""<tr><td align="right">${count} &nbsp;</td><td>${htmlEncode(author)}</td></tr>"""
71-
sb append """</tbody></table>"""
65+
sb append targetLanguage.tableHeader("#", "Author")
66+
for ((author, count) <- authors)
67+
sb append targetLanguage.tableRow(count.toString, author)
68+
sb append targetLanguage.tableEnd
7269
sb.toString
7370
}
7471

7572
def renderCommitList: String = {
7673
val sb = new StringBuffer
7774
sb append blankLine()
7875
sb append header4("Complete commit list!")
79-
sb append """<table border="0" cellspacing="0" cellpadding="1">
80-
<thead><tr><th>sha</th><th align="left">Title</th></tr></thead>
81-
<tbody>"""
82-
for(commit <- commits)
83-
sb append s"""<tr><td align="right">${commitShaLink(commit.sha)}&nbsp;</td><td>${htmlEncode(commit.header)}</td></tr>"""
84-
sb append """</tbody>
85-
</table>"""
76+
sb append targetLanguage.tableHeader("sha", "Title")
77+
for (commit <- commits)
78+
sb append targetLanguage.tableRow(commitShaLink(commit.sha), commit.header)
79+
sb append targetLanguage.tableEnd
8680
sb.toString
8781
}
8882

8983
def renderFixedIssues: String = {
9084
val sb = new StringBuffer
9185
sb append blankLine()
9286
sb append header4(s"Commits and the issues they fixed since ${previousTag}")
93-
sb append ("""<table border="0" cellspacing="0" cellpading="1">
94-
<thead><tr><th>Issue(s)</th><th>Commit</th><th>Message</th></tr></thead>
95-
<tbody>""")
96-
for(commit <- fixCommits)
97-
sb append s"""<tr><td>${fixLinks(commit)}&nbsp;</td><td>${commitShaLink(commit.sha)}&nbsp;</td><td>${htmlEncode(commit.header)}</td></tr>"""
98-
sb append """</tbody>
99-
</table>"""
87+
sb append targetLanguage.tableHeader("Issue(s)", "Commit", "Message")
88+
for (commit <- fixCommits)
89+
sb append targetLanguage.tableRow(fixLinks(commit), commitShaLink(commit.sha), commit.header)
90+
sb append targetLanguage.tableEnd
10091
sb append blankLine()
10192
sb.toString
10293
}
10394

104-
10595
}

src/main/scala/IO.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import java.io.File
2+
3+
object IO {
4+
def write(f: java.io.File, contents: String) {
5+
val buf = new java.io.BufferedWriter(new java.io.FileWriter(f))
6+
try buf.write(contents)
7+
finally buf.close()
8+
}
9+
}

src/main/scala/MakeDownloadPage.scala

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import java.util.Date
2+
import java.text._
3+
4+
class MakeDownloadPage(version: String, releaseDate: Date = new Date()) {
5+
def write() = {
6+
require(!version.startsWith("v"), "version should *not* start with 'v'")
7+
val fileName = s"${format("yyyy-MM-dd")}-$version.md"
8+
IO.write(new java.io.File(fileName), page)
9+
println("generated " + fileName)
10+
}
11+
12+
def humanSize(url: String) = {
13+
import scala.sys.process._
14+
println(url)
15+
val tmpFile = java.io.File.createTempFile("download", ".tmp")
16+
val res = s"curl --fail --silent --output ${tmpFile.getAbsolutePath} $url".!
17+
val dfOutput = s"du -h ${tmpFile.getAbsolutePath}".!!
18+
val output = dfOutput.trim.split("\t").head
19+
if (output == "0B") {
20+
println(s"warning: could not fetch $url")
21+
""
22+
} else output
23+
}
24+
25+
def resourceArchive(cls: String, name: String, ext: String, desc: String) = {
26+
val fileName = s"$name-$version.$ext"
27+
val relUrl = s"/files/archive/$fileName"
28+
val fullUrl = s"http://www.scala-lang.org$relUrl"
29+
resource(cls, fileName, desc, relUrl, fullUrl)
30+
}
31+
32+
def resource(cls: String, fileName: String, desc: String, relUrl: String, fullUrl: String) = {
33+
s"""[$cls, "$fileName", "$relUrl", "$desc", "${humanSize(fullUrl)}"]"""
34+
}
35+
36+
def defaultClass = "-non-main-sys"
37+
38+
def format(fmt: String) = new SimpleDateFormat(fmt).format(releaseDate)
39+
40+
def ghSourceUrl = s"https://github.com/scala/scala/archive/v$version.tar.gz"
41+
42+
def page: String = {
43+
44+
s"""
45+
---
46+
title: Scala $version
47+
start: ${format("dd MMMM yyyy")}
48+
layout: downloadpage
49+
release_version: $version
50+
release_date: "${format("MMMM dd, yyyy")}"
51+
show_resources: "true"
52+
permalink: /download/$version.html
53+
requirements: "This Scala software distribution can be installed on any Unix-like or Windows system. It requires the Java runtime version 1.6 or later, which can be downloaded <a href='http://www.java.com/'>here</a>."
54+
resources: [
55+
${resourceArchive("-main-unixsys", "scala", "tgz", "Max OS X, Unix, Cygwin" )}
56+
${resourceArchive("-main-windows", "scala", "msi", "Windows (msi installer)" )},
57+
${resourceArchive(defaultClass, "scala", "zip", "Windows" )},
58+
${resourceArchive(defaultClass, "scala-docs", "txz", "API docs" )},
59+
${resourceArchive(defaultClass, "scala-docs", "zip", "API docs" )},
60+
${resource (defaultClass, s"scala-sources-$version.zip", "sources", ghSourceUrl, ghSourceUrl)},
61+
${resourceArchive(defaultClass, "scala-tool-support", "tgz", "Scala Tool Support (tgz)")},
62+
${resourceArchive(defaultClass, "scala-tool-support", "zip", "Scala Tool Support (zip)")}
63+
]
64+
---
65+
66+
67+
"""
68+
}
69+
}

src/main/scala/MakeReleaseNotes.scala

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,56 @@
11

22

33
import java.io.BufferedReader
4+
import scala.io.Source
45
object MakeReleaseNotes {
5-
6-
7-
def apply(scalaDir: java.io.File, previousTag: String, currentTag: String): Unit = {
8-
val out = new java.io.File("release-notes.html")
9-
val buf = new java.io.BufferedWriter(new java.io.FileWriter(out))
10-
try buf.write(makeReleaseNotes(scalaDir, previousTag, currentTag))
11-
finally buf.close()
6+
7+
def apply(scalaDir: java.io.File, previousTag: String, currentTag: String)(implicit targetLanguage: TargetLanguage = Html): Unit = {
8+
val out = targetLanguage match {
9+
case Html => new java.io.File("release-notes.html")
10+
case MarkDown => new java.io.File(s"release-notes-${currentTag}.md")
11+
}
12+
IO.write(out, makeReleaseNotes(scalaDir, previousTag, currentTag))
1213
}
13-
14-
15-
14+
1615
def parseHandWrittenNotes(file: java.io.File = new java.io.File("hand-written.md")): String = {
1716
import org.pegdown._
1817
val parser = new PegDownProcessor
19-
18+
2019
val in = new java.io.BufferedReader(new java.io.FileReader(file))
21-
def read(buf: StringBuffer): String =
20+
def read(buf: StringBuffer): String =
2221
in.readLine match {
2322
case null => buf.toString
24-
case line =>
23+
case line =>
2524
buf append s"${line}\n"
2625
read(buf)
2726
}
2827
val content =
2928
try read(new StringBuffer)
3029
finally in.close()
31-
30+
3231
parser markdownToHtml content
3332
}
34-
35-
def makeReleaseNotes(scalaDir: java.io.File, previousTag: String, currentTag: String): String = {
33+
34+
def makeReleaseNotes(scalaDir: java.io.File, previousTag: String, currentTag: String)(implicit targetLanguage: TargetLanguage): String = {
35+
def rawHandWrittenNotes(file: java.io.File = new java.io.File(s"hand-written.md")): String = {
36+
val lines: List[String] = if (file.exists) {
37+
val src = Source.fromFile(file)
38+
src.getLines.toList
39+
} else Nil
40+
// if you don't have the next line, sub-bullets would be screwed!
41+
// please take this case into account and comment out 2 next lines and uncomment the line after!
42+
val newLines = lines.map(x => if (x.startsWith(" *")) "\n" + x else x)
43+
newLines.mkString("\n")
44+
// lines.mkString("\n")
45+
}
3646
val info = new GitInfo(scalaDir, previousTag, currentTag)
3747
// val communityProjects = CommunityProjects.loadHtmlFromFile()
38-
import info.{currentTag => _, _}
48+
import info.{ currentTag => _, _ }
3949
// <h3> Known issues </h3>
4050
// ${JiraIssues.makeOpenIssuesString}
4151

42-
s"""<html>
52+
targetLanguage match {
53+
case Html => s"""<html>
4354
<head>
4455
<title>${currentTag} - Release notes</title>
4556
</head>
@@ -52,5 +63,18 @@ object MakeReleaseNotes {
5263
${renderCommitList}
5364
</body>
5465
</html>"""
66+
case MarkDown => s"""---
67+
layout: news
68+
post-type: announcement
69+
title: "Scala ${currentTag drop 1} is now available!"
70+
---
71+
${rawHandWrittenNotes()}
72+
73+
${renderCommitterList}
74+
${renderFixedIssues}
75+
${renderCommitList}
76+
"""
77+
}
78+
5579
}
5680
}

src/main/scala/TargetLanguage.scala

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
sealed trait TargetLanguage {
2+
def createHyperLink(link: String, content: String): String
3+
def blankLine(): String
4+
def header4(msg: String): String
5+
def tableHeader(firstColumn: String, secondColumn: String): String
6+
def tableRow(firstColumn: String, secondColumn: String): String
7+
def tableHeader(firstColumn: String, secondColumn: String, thirdColumn: String): String
8+
def tableRow(firstColumn: String, secondColumn: String, thirdColumn: String): String
9+
def tableEnd: String
10+
}
11+
case object MarkDown extends TargetLanguage {
12+
def createHyperLink(link: String, content: String): String =
13+
s"[$link]($content)"
14+
def blankLine(): String = "\n"
15+
def header4(msg: String): String = s"#### $msg\n"
16+
def tableHeader(firstColumn: String, secondColumn: String): String =
17+
s"""
18+
${markdownEncode(firstColumn)} | $secondColumn
19+
---: | ---
20+
"""
21+
def tableRow(firstColumn: String, secondColumn: String): String = s"${firstColumn} | <notextile>${escapeHtml(secondColumn)}</notextile>\n"
22+
def tableHeader(firstColumn: String, secondColumn: String, thirdColumn: String): String =
23+
s"""
24+
$firstColumn | $secondColumn | $thirdColumn
25+
--- | --- | ---
26+
"""
27+
def tableRow(firstColumn: String, secondColumn: String, thirdColumn: String): String = s"$firstColumn | $secondColumn | <notextile>${escapeHtml(thirdColumn)}</notextile>\n"
28+
def tableEnd: String = "\n"
29+
30+
def markdownEncode(s: String): String = s.flatMap {
31+
case c if (List('*', '`', '[', ']', '#').contains(c)) => "\\" + c
32+
case x => x.toString
33+
}
34+
35+
def escapeHtml(s: String): String = Html.htmlEncode(s).flatMap {
36+
case '|' => "&#124;" // it would destroy tables!
37+
case c => c.toString
38+
}
39+
}
40+
case object Html extends TargetLanguage {
41+
def createHyperLink(link: String, content: String): String =
42+
s"""<a href="$link">$content</a>"""
43+
def blankLine(): String = "<p>&nbsp;</p>"
44+
def header4(msg: String): String = s"<h4>$msg</h4>"
45+
def tableHeader(firstColumn: String, secondColumn: String): String = s"""|<table border="0" cellspacing="0" cellpadding="1">
46+
| <thead><tr><th>$firstColumn</th><th align="left">$secondColumn</th></tr></thead>
47+
|<tbody>""".stripMargin
48+
def tableRow(firstColumn: String, secondColumn: String): String = s"""<tr><td align="right">${firstColumn} &nbsp;</td><td>${htmlEncode(secondColumn)}</td></tr>"""
49+
def tableHeader(firstColumn: String, secondColumn: String, thirdColumn: String): String = s"""<table border="0" cellspacing="0" cellpading="1">
50+
<thead><tr><th>$firstColumn</th><th>$secondColumn</th><th>$thirdColumn</th></tr></thead>
51+
<tbody>"""
52+
def tableRow(firstColumn: String, secondColumn: String, thirdColumn: String): String = s"""<tr><td>${firstColumn}&nbsp;</td><td>${secondColumn}&nbsp;</td><td>${htmlEncode(thirdColumn)}</td></tr>"""
53+
def tableEnd: String = "</tbody></table>"
54+
55+
def htmlEncode(s: String) = org.apache.commons.lang3.StringEscapeUtils.escapeHtml4(s)
56+
}

0 commit comments

Comments
 (0)