Revision: 12267
http://sourceforge.net/p/skim-app/code/12267
Author: hofman
Date: 2021-04-07 17:22:12 +0000 (Wed, 07 Apr 2021)
Log Message:
-----------
parse and build release notes in appcast, relnotes, and readme files for release
Modified Paths:
--------------
trunk/build_skim.py
Modified: trunk/build_skim.py
===================================================================
--- trunk/build_skim.py 2021-04-07 14:49:29 UTC (rev 12266)
+++ trunk/build_skim.py 2021-04-07 17:22:12 UTC (rev 12267)
@@ -60,16 +60,15 @@
#
import os, sys, io, getopt
+import codecs
from subprocess import Popen, PIPE
from stat import ST_SIZE
-import tarfile
from time import gmtime, strftime, localtime, sleep
import plistlib
import tempfile
+import urllib
from getpass import getuser
-from Foundation import NSXMLDocument, NSUserDefaults, NSURL,
NSXMLNodePrettyPrint, NSXMLNodePreserveCDATA
-
# determine the path based on the path of this program
SOURCE_DIR = os.path.dirname(os.path.abspath(sys.argv[0]))
assert len(SOURCE_DIR)
@@ -79,6 +78,7 @@
KEY_NAME = "Skim Sparkle Key"
APPCAST_URL = "https://skim-app.sourceforge.io/skim.xml"
+RELNOTES_URL = "https://skim-app.sourceforge.io/relnotes.html"
# create a private temporary directory
BUILD_ROOT = os.path.join("/tmp", "Skim-%s" % (getuser()))
@@ -94,6 +94,7 @@
BUILT_APP = os.path.join(BUILD_DIR, "Skim.app")
DERIVED_DATA_DIR = os.path.join(BUILD_ROOT, "DerivedData")
PLIST_PATH = os.path.join(BUILT_APP, "Contents", "Info.plist")
+RELNOTES_PATH = os.path.join(BUILT_APP, "Contents", "Resources",
"ReleaseNotes.rtf")
def read_versions():
@@ -304,6 +305,57 @@
return final_zip_name
+def release_notes():
+
+ f = codecs.open(RELNOTES_PATH, "r", encoding="utf-8")
+ relNotes = f.read()
+ f.close()
+
+ changeString = "\cf2 Changes since "
+ endLineString = "\\\n"
+ itemString = "{\\listtext\t\uc0\u8226 \t}"
+
+ changeStart = relNotes.find(changeString)
+ if changeStart is not -1:
+ changeEnd = relNotes.find(endLineString, changeStart)
+ if changeEnd is not -1:
+ oldVersion = relNotes[changeStart + len(changeString):changeEnd]
+ else:
+ oldVersion = ""
+ prevChangeStart = relNotes.find(changeString, changeStart +
len(changeString))
+ if prevChangeStart is not -1:
+ relNotes = relNotes[changeStart:prevChangeStart]
+ else:
+ relNotes = relNotes[changeStart]
+
+ newFeatures = []
+ bugsFixed = []
+
+ start = relNotes.find("Bugs Fixed")
+ endBugs = len(relNotes)
+ endNew = len(relNotes)
+ if start is not -1:
+ endNew = start
+ while True:
+ start = relNotes.find(itemString, start, endBugs)
+ if start is -1:
+ break
+ start = start + len(itemString)
+ end = relNotes.find(endLineString, start, endBugs)
+ bugsFixed.append(relNotes[start:end])
+
+ start = relNotes.find("New Features")
+ if start is not -1:
+ while True:
+ start = relNotes.find(itemString, start, endNew)
+ if start is -1:
+ break
+ start = start + len(itemString)
+ end = relNotes.find(endLineString, start, endNew)
+ newFeatures.append(relNotes[start:end])
+
+ return newFeatures, bugsFixed, oldVersion
+
def keyFromSecureNote():
# see
http://www.entropy.ch/blog/Developer/2008/09/22/Sparkle-Appcast-Automation-in-Xcode.html
@@ -345,7 +397,7 @@
return appcastSignature, fileSize
-def write_appcast(newVersion, newVersionString, minimumSystemVersion,
archive_path, outputPath):
+def write_appcast_and_release_notes(newVersion, newVersionString,
minimumSystemVersion, archive_path, outputPath):
print("create Sparkle appcast for %s" % (archive_path))
@@ -357,7 +409,21 @@
else:
type = "application/zip"
- # creating this from a string is easier than manipulating NSXMLNodes...
+ newFeatures, bugsFixed, oldVersionString = release_notes()
+
+ relNotes = "\n<h1>Version " + newVersionString + "</h1>\n"
+ if len(newFeatures) > 0:
+ relNotes = relNotes + "\n<h2>New Features</h2>\n<ul>\n"
+ for item in newFeatures:
+ relNotes = relNotes + "<li>" + item + "</li>\n"
+ relNotes = relNotes + "</ul>\n"
+ if len(bugsFixed) > 0:
+ relNotes = relNotes + "\n<h2>Bugs Fixed</h2>\n<ul>\n"
+ for item in bugsFixed:
+ relNotes = relNotes + "<li>" + item + "</li>\n"
+ relNotes = relNotes + "</ul>\n"
+
+ # the new item string for the appcast
newItemString = """<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel>
@@ -364,19 +430,7 @@
<item>
<title>Version """ + newVersionString + """</title>
<description>
- <![CDATA[
-<h1>Version """ + newVersionString + """</h1>
-
-<h2>New Features</h2>
-<ul>
-<li></li>
-</ul>
-
-<h2>Bugs Fixed</h2>
-<ul>
-<li></li>
-</ul>
- ]]>
+ <![CDATA[""" + relNotes + """ ]]>
</description>
<pubDate>""" + appcastDate + """</pubDate>
<sparkle:minimumSystemVersion>""" + minimumSystemVersion +
"""</sparkle:minimumSystemVersion>
@@ -387,52 +441,73 @@
"""
# read from the source directory
- appcastURL = NSURL.URLWithString_(APPCAST_URL)
+ appcastString = urllib.urlopen(APPCAST_URL).read().decode("utf-8")
- # xml doc from the current appcast
- (oldDoc, error) =
NSXMLDocument.alloc().initWithContentsOfURL_options_error_(appcastURL,
NSXMLNodePreserveCDATA, None)
- assert oldDoc is not None, str(error)
+ # find insertion point for the new item
+ insert = -1
+ start = -1
+ end = -1
+ if appcastString.find("<title>Version " + newVersionString + "</title>")
is -1:
+ insert = appcastString.find("<channel>")
+ start = newItemString.find("<channel>")
+ end = newItemString.find("</item>")
+ if insert is not -1 and start is not -1 and end is not -1:
+ appcastString = appcastString[:insert+9] +
newItemString[start+9:end+7] + appcastString[insert+9:]
+ appcastName = "skim.xml"
+ else:
+ appcastString = newItemString
+ appcastName = "skim-" + newVersionString + ".xml"
- # xml doc from the new appcast string
- (newDoc, error) =
NSXMLDocument.alloc().initWithXMLString_options_error_(newItemString,
NSXMLNodePreserveCDATA, None)
- assert newDoc is not None, str(error)
+ appcastPath = os.path.join(outputPath , "test_" + appcastName)
+ appcastFile = codecs.open(appcastPath, "w", "utf-8")
+ appcastFile.write(newItemString)
+ appcastFile.close()
- # get an arry of the current item titles
- (oldTitles, error) = oldDoc.nodesForXPath_error_("//item/title", None)
- assert oldTitles.count > 0, "oldTitles had no elements"
+ # construct the ReadMe file
+ readMe = "Release notes for Skim version " + newVersionString + "\n"
+ if len(newFeatures) > 0:
+ readMe = readMe + "\nNew Features\n"
+ for item in newFeatures:
+ readMe = readMe + " * " + item + "\n"
+ if len(bugsFixed) > 0:
+ readMe = readMe + "\nBugs Fixed\n"
+ for item in bugsFixed:
+ readMe = readMe + " * " + item + "\n"
- # now get the title we just created
- (newTitles, error) = newDoc.nodesForXPath_error_("//item/title", None)
- assert newTitles.count() is 1, "newTitles must have a single element"
+ # write the ReadMe file
+ readMePath = os.path.join(outputPath , "ReadMe-" + newVersionString +
".txt")
+ readMeFile = codecs.open(readMePath, "w", "utf-8")
+ readMeFile.write(readMe)
+ readMeFile.close()
- # easy test to avoid duplicating items
- if oldTitles.containsObject_(newTitles.lastObject()) is False:
+ # construct relnotes.html
+ newline = "\n "
+ relNotes = "<h1>Changes since " + oldVersionString + "</h1>" + newline +
newline
+ if len(newFeatures) > 0:
+ relNotes = relNotes + "<h2>New Features</h2>" + newline + newline +
"<ul>" + newline
+ for item in newFeatures:
+ relNotes = relNotes + " <li>" + item + "</li>" + newline
+ relNotes = relNotes + "</ul>" + newline + newline
+ if len(bugsFixed) > 0:
+ relNotes = relNotes + "<h2>Bugs Fixed</h2>" + newline + newline +
"<ul>" + newline
+ for item in bugsFixed:
+ relNotes = relNotes + " <li>" + item + "</li>" + newline
+ relNotes = relNotes + "</ul>" + newline + newline
+
+ releaseNotes = urllib.urlopen(RELNOTES_URL).read().decode("utf-8")
+
+ start = releaseNotes.find("<title>")
+ end = releaseNotes.find("</title>")
+ releaseNotes = releaseNotes[:start+7] + "Skim " + newVersionString +
releaseNotes[end:]
+ start = releaseNotes.find("<h1>Changes")
+ releaseNotes = releaseNotes[:start] + relNotes + releaseNotes[start:]
+
+ # construct relnotes.html
+ relnotesPath = os.path.join(outputPath , "relnotes.html")
+ relnotesFile = codecs.open(relnotesPath, "w", "utf-8")
+ relnotesFile.write(releaseNotes)
+ relnotesFile.close()
- # get the parent node we'll be inserting to
- (parentChannel, error) = oldDoc.nodesForXPath_error_("//channel", None)
- assert parentChannel.count() is 1, "channel count must be one"
- parentChannel = parentChannel.lastObject()
-
- # now get the new node
- (newNodes, error) = newDoc.nodesForXPath_error_("//item", None)
- assert newNodes is not None, str(error)
-
- # insert a copy of the new node
- parentChannel.insertChild_atIndex_(newNodes.lastObject().copy(), 0)
-
- # write to user Desktop
- appcastPath = os.path.join(outputPath , "skim.xml")
-
- # write to NSData, since pretty printing didn't work with
NSXMLDocument writing
-
oldDoc.XMLDataWithOptions_(NSXMLNodePrettyPrint).writeToFile_atomically_(appcastPath,
True)
-
- else:
-
- appcastPath = os.path.join(outputPath , "Skim-" + newVersionString +
".xml")
- appcastFile = open(appcastPath, "w")
- appcastFile.write(newItemString)
- appcastFile.close()
-
def get_options():
identity = ""
@@ -505,7 +580,7 @@
except Exception as e:
assert os.path.isdir(out), "%s does not exist" % (out)
- write_appcast(new_version, new_version_string, minimum_system_version,
archive_path, out)
+ write_appcast_and_release_notes(new_version, new_version_string,
minimum_system_version, archive_path, out)
target_path = os.path.join(out, os.path.basename(archive_path))
if (os.path.exists(target_path)):
This was sent by the SourceForge.net collaborative development platform, the
world's largest Open Source development site.
_______________________________________________
Skim-app-commit mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/skim-app-commit