[ANN] SqueakSource commit emails

Matthew Fulmer tapplek at gmail.com
Tue Aug 21 23:34:59 UTC 2007


I added a mechanism SqueakSource to send email notifications
whenever a new version is uploaded.

http://www.squeaksource.com/ss/SqueakSource-mtf.1025.mcz

Features:
- Configurable recipient and reply-to address, both per project
  and global default
- Can be enabled or disabled per-project, or for global default
- Three types of commit emails, only configurable globally:
  - Summary only: Sends URL and version summary information.
    Requires no server-side mcz decoding, so is instantaneous.
    Importing SqueakSource-mtf.1025.mcz to localhost took 2
    seconds under this setting.
  - Summary + full snapshot: additionally puts a text
    representation of the version in the body of the email. With
    my experiment, this setting took the longest, since a full
    snapshot is pretty big.  Importing SqueakSource-mtf.1025.mcz
    to localhost took 9 seconds under this setting.
  - Summary + patch: finds the latest ancestor in the project and
    puts a patch in the body of the email. A sample patch email is
    forwarded at the end of this email.  I was surprised to find
    this was faster than the above method; it does 2-3 times more
    parsing/processing as above, but writes much less data.
    Importing SqueakSource-mtf.1025.mcz to localhost took 5
    seconds under this setting.
- Two new textual representations for generic Monticello packages:
  MCTextWriter, and MCDiffyTextWriter. They are write-only formats
  intended for human, rather than computer, consumption. Currently
  it is grafted on top of the chunk format writer, but I can do
  better. Next version will feature a much better textual format.
  These two writer classes have no dependencies on SqueakSource,
  and could be added directly to Monticello.

Installation Instructions:

These instructions are for merging these features into an
existing SqueakSource installation. For new installations, see
http://wiki.squeak.org/squeak/5766 (slightly out of date; I will
update it soon)

I tested these instructions on a 3.8.1 image running
SqueakSource-al.1024:
1. Merge in SqueakSource-mtf.1025. It is nearly all new classes,
   and has no core changes, so it probably won't have conflicts.
2. log in as superuser
3. Go to global edit settings
4. Set the 5 options:
   - SMTP Server
   - Generated email:           sender address
   - Default email recipients:  (blank by default)
   - Default Reply-To Address:  defaults to squeak-dev list
   - Show in commit notifications: (choose detail level)
   - Default subscriptions:     (check the checkbox if you want to
     enable commit emails)
  
This sets the defaults for all projects. If any per-project
settings are non-nil, they will ignore the defaults. So, you must
do this right after installation and before any existing project
settings have been changed.

Here is a generated commit email (Summary + Patch) for
SqueakSource-mtf.1025:

----- Forwarded message from no-reply at localhost -----

To: tapplek at gmail.com
From: no-reply at localhost
Date: Tue, 21 Aug 2007 16:05:04 -0700 (MST)
Subject: SqueakSource: SqueakSource-mtf.1025.mcz

A new version of SqueakSource was added to project SqueakSource:
http://localhost:8888/ss/SqueakSource-mtf.1025.mcz

==================== Summary ====================

Name: SqueakSource-mtf.1025
Author: mtf
Time: 21 August 2007, 4:03:16 pm
UUID: e7fe67cd-2502-7f4e-81ce-7681f86bf1d8
Ancestors: SqueakSource-al.1024

- add model for sending commit emails to SSProject
- add model for default values of above to SSRepository
- add two classes for writing text representations of generic Monticello packages: MCTextWriter and MCDiffyTextWriter
- add three classes for writing text representations SqueakSource versions and packages: SSBasicTextWriter, SSTextWriter, and SSDiffyTextWriter
- add email sending capabilities via SSEmailSubscription

=============== Diff against SqueakSource-al.1024 ===============

Item was added:
+ ----- Method: SSProject>>subscriptionsDo: (in category 'accessing-subscriptions') -----
+ subscriptionsDo: aBlock
+ 	^ self subscriptions do: aBlock!

Item was added:
+ ----- Method: SSSubscription>>versionAdded:to: (in category 'as yet unclassified') -----
+ versionAdded: aVersion to: aProject
+ 	^ self!

Item was added:
+ SSBasicTextWriter subclass: #SSTextWriter
+ 	instanceVariableNames: ''
+ 	classVariableNames: ''
+ 	poolDictionaries: ''
+ 	category: 'SqueakSource-Notification'!
+ 
+ !SSTextWriter commentStamp: '<historical>' prior: 0!
+ In addition to writing summaries, my instances also dump a full snapshot of the uploaded version.
+ 
+ For speed notes of this class, see the class comment of SSDiffyTextWriter!

Item was added:
+ ----- Method: MCTextWriter>>writeVersion: (in category 'writing') -----
+ writeVersion: aVersion
+ 	self writeVersionInfo: aVersion info.
+ 	self writeSnapshot: aVersion snapshot.
+ !

Item was added:
+ ----- Method: SSEMailSubscription>>writeHeaders (in category 'as yet unclassified') -----
+ writeHeaders
+ 	sender ifNotEmpty: [	stream nextPutAll: 'From: '; nextPutAll: sender].
+ 	project emailRecipients ifNotEmpty: [stream cr; nextPutAll: 'To: '].
+ 	project emailRecipients
+ 		do: [:email | stream nextPutAll: email address]
+ 		separatedBy: [stream nextPutAll: ', '].
+ 	project replyTo ifNotEmpty: [stream
+ 		cr; nextPutAll: 'Reply-To: '; nextPutAll: project replyTo].
+ 	stream cr;
+ 		nextPutAll: 'Subject: ';
+ 		nextPutAll: project title; nextPutAll: ': ';
+ 		nextPutAll: version fileName; cr; cr!

Item was added:
+ ----- Method: SSDiffyTextWriter>>writePatchHeader: (in category 'as yet unclassified') -----
+ writePatchHeader: info
+ 	self textWriter writePatchHeader: info!

Item was added:
+ ----- Method: SSRepository>>defaultEmailRecipients (in category 'accessing-settings') -----
+ defaultEmailRecipients
+ 	^self properties
+ 		at: #defaultEmailRecipients
+ 		ifAbsent: [OrderedCollection new]!

Item was changed:
  ----- Method: SSProject>>addVersion:author: (in category 'accessing-versions') -----
  addVersion: aString author: aUser
  	| array version |
  	array := aString asByteArray.
  	version := SSVersion 
  		array: array 
  		author: aUser.
  	SSRepository storage saveMonticello: array of: version to: self.
+ 	versions at: version fileName put: version.
+ 	self versionAdded: version.
+ 	^ version
+ 	!
- 	^versions at: version fileName put: version.
- 		!

Item was added:
+ ----- Method: SSRepository>>defaultEmailRecipients: (in category 'accessing-settings') -----
+ defaultEmailRecipients: aCollection
+ 	^self properties
+ 		at: #defaultEmailRecipients
+ 		put: aCollection!

Item was added:
+ SSModel subclass: #SSEmailAddress
+ 	instanceVariableNames: 'address'
+ 	classVariableNames: ''
+ 	poolDictionaries: ''
+ 	category: 'SqueakSource-Model'!

Item was added:
+ ----- Method: SSRepository>>commitWriterClass (in category 'accessing-settings') -----
+ commitWriterClass
+ 	^self properties
+ 		at: #commitWriterClass
+ 		ifAbsent: [SSBasicTextWriter]!

Item was added:
+ ----- Method: SSEMailSubscription>>writerClass (in category 'as yet unclassified') -----
+ writerClass
+ 	^ SSRepository current commitWriterClass!

Item was added:
+ ----- Method: SSRepository>>emailSender: (in category 'accessing-settings') -----
+ emailSender: aString
+ 	^self properties
+ 		at: #emailSender
+ 		put: aString!

Item was added:
+ ----- Method: SSProject>>replyTo (in category 'accessing') -----
+ replyTo
+ 	"Answer the value of replyTo"
+ 
+ 	^ replyTo ifNil: [SSRepository current defaultReplyTo]!

Item was added:
+ ----- Method: SSTextWriter>>writeSnapshot: (in category 'as yet unclassified') -----
+ writeSnapshot: snapshot
+ 	self textWriter writeSnapshot: snapshot!

Item was added:
+ ----- Method: SSEmailAddress>>address: (in category 'accessing') -----
+ address: anObject
+ 	"Set the value of address"
+ 
+ 	address _ anObject!

Item was added:
+ ----- Method: SSDiffyTextWriter class>>description (in category 'as yet unclassified') -----
+ description
+ 	^ 'Summary + changes'!

Item was added:
+ ----- Method: SSEMailSubscription class>>description (in category 'as yet unclassified') -----
+ description
+ 	^ 'Recieve commit notifications by email'!

Item was added:
+ MCStWriter subclass: #MCTextWriter
+ 	instanceVariableNames: ''
+ 	classVariableNames: ''
+ 	poolDictionaries: ''
+ 	category: 'SqueakSource-Notification'!
+ 
+ !MCTextWriter commentStamp: '<historical>' prior: 0!
+ A basic writer for SqueakSource versions and projects. Does no decoding of .mcz files!

Item was added:
+ ----- Method: SSTextWriter class>>description (in category 'as yet unclassified') -----
+ description
+ 	^ 'Summary + full snapshot'!

Item was changed:
  ----- Method: SSProject>>metaobject (in category 'metamodel') -----
  metaobject
  	| metaobject |
  	metaobject := MWMetaobject for: self.
  	metaobject textAttribute: #id do: [ :attribute | 
  		attribute
  			label: 'Name';
  			maxLength: 20;
  			addRequiredRule;
  			addValidationRule: [ :value |
  				value allSatisfy: [ :char | char isLetter or: [ char isDigit ] ] ]
  				errorString: 'name should only contain letters and digits';
  			addValidationRule: [ :value |
  				SSRepository current isUniqueProjectId: value ]
  				errorString: 'name is not unique' ].
  	metaobject textAttribute: #title do: [ :attribute | 
  		attribute
  			label: 'Title';
  			maxLength: 50;
  			addRequiredRule ].
  	metaobject textAttribute: #description do: [ :attribute | 
  		attribute
  			label: 'Description';
  			multiLine: true ].
  		
  	metaobject multipleRelationshipAttribute: #tags do: [ :attribute | 
  		attribute
  			label: 'Tags';
  			relationshipTo: [ self repository tags ]
  			formatWith: [ :each | each name ];
  			"nilItemString: 'none yet';"
  			yourself ].
  		
  	metaobject singleRelationshipAttribute: #license do: [ :attribute | 
  		attribute
  			label: 'License';
  			relationshipTo: self licenses
  				formatWith: [ :each | each isNil
  					ifTrue: [ 'None' ]
  					ifFalse: [ each first ifNil: [ 'None' ] ] ] ].
  		
  	metaobject booleanAttribute: #canBless do: [:attribute |
  		attribute
  			label: 'Enable Blessings'.
  	].
  
  	metaobject singleRelationshipAttribute: #globalRight do: [ :attribute | 
  		attribute
  			label: 'Global';
  			relationshipTo: SSAccessPolicy globalRights
  				formatWith: [ :symbol | symbol asCapitalizedPhrase ] ].
  	metaobject multipleRelationshipAttribute: #admins do: [ :attribute |
  		attribute
  			label: 'Administrators';
  			addValidationRule: [ :admins |
  				| currentUser |
  				currentUser := SSSession currentSession user.
  				currentUser isSuperUser
  					or: [
  						admins anySatisfy: [ :each |
  							each isGroup
  								ifTrue: [ each hasMember: currentUser ]
  								ifFalse: [ each = currentUser ] ] ] ]
  			errorString: 'You can''t remove yourself from the list of administrators';
  			hide ].
  	metaobject multipleRelationshipAttribute: #developers do: [ :attribute |
  		attribute label: 'Developers'; hide ].
  	metaobject multipleRelationshipAttribute: #guests do: [ :attribute |
  		attribute label: 'Guests'; hide ].
+ 
+ 	metaobject multipleAttribute: #emailRecipients do: [ :attribute | 
+ 		attribute
+ 			label: 'Send emails to';
+ 			baseClass: SSEmailAddress].
+ 	
+ 	metaobject textAttribute: #replyTo do: [ :attribute | 
+ 		attribute
+ 			label: 'Reply-To Address';
+ 			maxLength: 50].
+ 	
+ 	metaobject multipleRelationshipAttribute: #subscriptions do: [ :attribute |
+ 		attribute
+ 			label: 'Subscriptions';
+ 			relationshipTo: [SSSubscription allSubclasses]].
+ 
  	^metaobject!

Item was added:
+ ----- Method: MCTextWriter>>writeVersionInfo: (in category 'writing') -----
+ writeVersionInfo: aVersionInfo
+ 	stream
+ 		nextPutAll: '==================== Summary ===================='; cr; cr;
+ 		nextPutAll: aVersionInfo summary; cr; cr!

Item was added:
+ ----- Method: SSRepository class>>defaultEmailSender (in category 'private') -----
+ defaultEmailSender
+ 	^'no-reply@', self hostName!

Item was changed:
  SSModel subclass: #SSProject
+ 	instanceVariableNames: 'id title description creator accessPolicy versions dateCreated feeds wiki blessings configs tags cache license emailAddresses replyTo subscriptions emailRecipients'
- 	instanceVariableNames: 'id title description creator accessPolicy versions dateCreated feeds wiki blessings configs tags cache license'
  	classVariableNames: ''
  	poolDictionaries: ''
  	category: 'SqueakSource-Model'!

Item was added:
+ ----- Method: SSEMailSubscription>>versionAdded:to: (in category 'as yet unclassified') -----
+ versionAdded: aVersion to: aProject
+ 	sender := SSRepository current emailSender.
+ 	stream := String new writeStream.
+ 	project := aProject.
+ 	version := aVersion.
+ 	self writeHeaders.
+ 	(self writerClass on: stream) writeVersion: aVersion for: aProject.
+ 	self sendMail.!

Item was added:
+ ----- Method: SSEmailAddress>>metaobject (in category 'accessing') -----
+ metaobject
+ 	| metaobject |
+ 	metaobject _ MWMetaobject for: self.
+ 	
+ 	metaobject textAttribute: #address do: [ :attribute | 
+ 		attribute
+ 			maxLength: 50;
+ 			addRequiredRule].
+ 	
+ 	^metaobject!

Item was added:
+ ----- Method: MCTextWriter>>writeMethodPreamble: (in category 'writing') -----
+ writeMethodPreamble: definition
+ 	stream
+ 		nextPutAll: '----- Method: ';
+ 		nextPutAll: definition fullClassName;
+ 		nextPutAll: '>>';
+ 		nextPutAll: definition selector;
+ 		nextPutAll: ' (in category ';
+ 		nextPutAll: definition category asString printString;
+ 		nextPutAll: ') -----'; cr!

Item was added:
+ ----- Method: SSEmailAddress>>address (in category 'accessing') -----
+ address
+ 	"Answer the value of address"
+ 
+ 	^ address!

Item was added:
+ SSTextWriter subclass: #SSDiffyTextWriter
+ 	instanceVariableNames: ''
+ 	classVariableNames: ''
+ 	poolDictionaries: ''
+ 	category: 'SqueakSource-Notification'!
+ 
+ !SSDiffyTextWriter commentStamp: '<historical>' prior: 0!
+ Rather than a full snapshot dump, my instances write a patch from the latest ancestor stored in the project. If no ancestors are in the project, they fall back onto doing a full snapshot.
+ 
+ Even though this class does much more decoding of .mcz files than SSTextWriter, it is usually a lot faster because a diff is much less information to write than a full snapshot. As a rough metric, a diff of SqueakSource-mtf.1025 (the package where this class was introduced) took 7 seconds to write (on my computer), while a full snapshot took 33 seconds to write. On the other hand, a version summary was instantaneous, as it requires no decoding of .mcz files!

Item was added:
+ ----- Method: SSRepository>>defaultSubscriptions: (in category 'accessing-settings') -----
+ defaultSubscriptions: aCollection
+ 	^self properties
+ 		at: #defaultSubscriptions
+ 		put: aCollection!

Item was added:
+ ----- Method: MCDiffyTextWriter>>writePatch: (in category 'writing') -----
+ writePatch: aPatch
+ 	aPatch operations do:
+ 		[:ea |
+ 		ea isRemoval ifTrue: [self writeRemoval: ea].
+ 		ea isAddition ifTrue: [self writeAddition: ea].
+ 		ea isModification ifTrue: [self writeModification: ea].
+ 		stream cr.].!

Item was added:
+ MCWriter subclass: #SSBasicTextWriter
+ 	instanceVariableNames: 'textWriter'
+ 	classVariableNames: ''
+ 	poolDictionaries: ''
+ 	category: 'SqueakSource-Notification'!
+ 
+ !SSBasicTextWriter commentStamp: '<historical>' prior: 0!
+ My instances write version summaries for SqueakSource versions and projects. Does no decoding of .mcz files. Subclasses extend the functionality.
+ 
+ For speed notes about this class, see the class comment of SSDiffyTextWriter!

Item was added:
+ ----- Method: SSDiffyTextWriter>>textWriterClass (in category 'as yet unclassified') -----
+ textWriterClass
+ 	^ MCDiffyTextWriter!

Item was added:
+ ----- Method: SSEMailSubscription>>writeSummary (in category 'as yet unclassified') -----
+ writeSummary
+ 	| author |
+ 	author := version authorString ifEmpty: [version guessedAuthor].
+ 	author
+ 		ifNil: [ stream
+ 			nextPutAll: 'A new version of ';
+ 			nextPutAll: version package;
+ 			nextPutAll: ' was added to project ';
+ 			nextPutAll: project title; nextPut: $:; cr]
+ 		ifNotNil: [ stream
+ 			nextPutAll: author;
+ 			nextPutAll: ' uploaded a new version of ';
+ 			nextPutAll: version package;
+ 			nextPutAll: ' to project ';
+ 			nextPutAll: project title; nextPut: $:; cr].
+ 	stream nextPutAll: (version url: project); cr; cr.!

Item was added:
+ ----- Method: MCTextWriter>>chunkContents: (in category 'writing') -----
+ chunkContents: aBlock
+ 	stream nextChunkPut: (String streamContents: aBlock); cr!

Item was added:
+ ----- Method: SSDiffyTextWriter>>writePatch: (in category 'as yet unclassified') -----
+ writePatch: patch
+ 	self textWriter writePatch: patch!

Item was added:
+ ----- Method: TextDiffBuilder>>stringForAttributes: (in category '*SqueakSource-Notifications') -----
+ stringForAttributes: type
+ 	"Private.
+ 	Answer the String that prefixes text of the given type."
+ 
+ 	^type caseOf: {
+ 		[#insert] -> [ '+ ' ].
+ 		[#remove] -> [ '- '].
+ 	} otherwise: [ '  ' ].
+ !

Item was added:
+ ----- Method: SSDiffyTextWriter>>writeVersion:for: (in category 'as yet unclassified') -----
+ writeVersion: aSSVersion for: aProject
+ 	| reader ancestor patch |
+ 	reader := aSSVersion reader: aProject.
+ 	ancestor := (aSSVersion versionInfo latestAncestorIn: aProject)
+ 		ifNil: [reader info latestAncestorIn: aProject].
+ 	self writeSummary: aSSVersion for: aProject.
+ 	self writeVersionInfo: aSSVersion versionInfo.
+ 	ancestor
+ 		ifNil: [self writeSnapshot: reader snapshot.]
+ 		ifNotNil: [
+ 			patch := reader snapshot patchRelativeToBase:
+ 				(ancestor reader: aProject) snapshot.
+ 			self writePatchHeader: ancestor versionInfo.
+ 			self writePatch: patch.]!

Item was added:
+ ----- Method: SSEMailSubscription>>sendMail (in category 'as yet unclassified') -----
+ sendMail
+ 	project emailRecipients ifEmpty: [^ self].
+ 	SeasidePlatformSupport
+ 		deliverMailFrom: sender
+ 		to: (project emailRecipients collect: [:email | email address])
+ 		text: stream contents.
+ 	"Workspace new contents: stream contents; openLabel: sender."!

Item was added:
+ ----- Method: SSTextWriter>>writeVersion:for: (in category 'as yet unclassified') -----
+ writeVersion: aSSVersion for: aProject
+ 	| reader |
+ 	reader := aSSVersion reader: aProject.
+ 	self writeSummary: aSSVersion for: aProject.
+ 	self writeVersionInfo: aSSVersion versionInfo.
+ 	self writeSnapshot: reader snapshot.!

Item was added:
+ ----- Method: MCAncestry>>latestAncestorIn: (in category '*SqueakSource') -----
+ latestAncestorIn: aProject
+ 	| ancestor |
+ 	self ancestors ifEmpty: [^ nil].
+ 	self allAncestors do: [:anAncestry | 
+ 		ancestor _ aProject versionAt: anAncestry name,'.mcz'.
+ 		ancestor ifNotNil: [^ ancestor]].
+ 	^ nil!

Item was added:
+ ----- Method: SSEMailSubscription>>writeDiffAgainst: (in category 'as yet unclassified') -----
+ writeDiffAgainst: ancestor
+ 	stream nextPutAll: 'Diff against ';
+ 		nextPutAll: ancestor fileName; cr.
+ 	self writeVersion
+ 		!

Item was added:
+ MCTestCase subclass: #MCTextWriterTest
+ 	instanceVariableNames: 'version'
+ 	classVariableNames: ''
+ 	poolDictionaries: ''
+ 	category: 'SqueakSource-Notification'!

Item was added:
+ ----- Method: SSProject>>subscriptions: (in category 'accessing-subscriptions') -----
+ subscriptions: aCollection
+ 	"Private. Set the subscriptions"
+ 	subscriptions := aCollection!

Item was added:
+ ----- Method: SSProject>>emailRecipients (in category 'accessing') -----
+ emailRecipients
+ 	"Answer the value of emailAddresses"
+ 
+ 	^ emailRecipients ifNil: [SSRepository current defaultEmailRecipients]!

Item was added:
+ ----- Method: SSRepository>>defaultSubscriptions (in category 'accessing-settings') -----
+ defaultSubscriptions
+ 	^self properties
+ 		at: #defaultSubscriptions
+ 		ifAbsent: [OrderedCollection new]!

Item was added:
+ ----- Method: SSProject>>replyTo: (in category 'accessing') -----
+ replyTo: anObject
+ 	"Set the value of replyTo"
+ 
+ 	replyTo _ anObject!

Item was added:
+ ----- Method: SSBasicTextWriter>>textWriter (in category 'as yet unclassified') -----
+ textWriter
+ 	^ textWriter ifNil: [textWriter := self textWriterClass on: stream]!

Item was added:
+ ----- Method: MCDiffyTextWriter>>writeModification: (in category 'writing') -----
+ writeModification: aModification
+ 	stream nextPutAll: 'Item was changed:'; cr.
+ 	self writePatchFrom: aModification obsoletion to: aModification definition!

Item was added:
+ SSModel subclass: #SSSubscription
+ 	instanceVariableNames: 'stream project version'
+ 	classVariableNames: ''
+ 	poolDictionaries: ''
+ 	category: 'SqueakSource-Notification'!
+ 
+ !SSSubscription commentStamp: '<historical>' prior: 0!
+ My insances represent a notification that is sent externally (an email message, for instance) in response to an event (like a commit)!

Item was changed:
  ----- Method: SSRepository class>>defaultRootUrl (in category 'private') -----
  defaultRootUrl
+ 	^'http://', self hostName, ':', self defaultPort asString, '/'!
- 	| hostName |
- 	hostName := NetNameResolver nameForAddress: (NetNameResolver localHostAddress) timeout: 5.
- 	hostName ifNil: [hostName := NetNameResolver localAddressString].
- 	^'http://', hostName, ':', self defaultPort asString, '/'!

Item was added:
+ ----- Method: SSRepository>>commitWriterClass: (in category 'accessing-settings') -----
+ commitWriterClass: aClass
+ 	^self properties
+ 		at: #commitWriterClass
+ 		put: aClass!

Item was added:
+ ----- Method: MCDiffyTextWriter>>writePatchFrom:to: (in category 'writing') -----
+ writePatchFrom: src to: dst
+ 	"src and dst are allowed to bi nil to represent a non-existent source or destination state"
+ 	stream nextPutAll: (TextDiffBuilder
+ 		from: (src ifNotNil: [self visitInFork: src] ifNil: [''])
+ 		to: (dst ifNotNil: [self visitInFork: dst] ifNil: [''])
+ 	) buildTextPatch!

Item was changed:
  SystemOrganization addCategory: #'SqueakSource-Model'!
  SystemOrganization addCategory: #'SqueakSource-View'!
  SystemOrganization addCategory: #'SqueakSource-Server'!
  SystemOrganization addCategory: #'SqueakSource-Tests'!
+ SystemOrganization addCategory: #'SqueakSource-Notification'!

Item was added:
+ ----- Method: SSBasicTextWriter>>writeVersionInfo: (in category 'as yet unclassified') -----
+ writeVersionInfo: info
+ 	self textWriter writeVersionInfo: info!

Item was added:
+ ----- Method: MCDiffyTextWriter>>writeRemoval: (in category 'writing') -----
+ writeRemoval: aRemoval
+ 	stream nextPutAll: 'Item was removed:'; cr.
+ 	self writePatchFrom: aRemoval definition to: nil!

Item was added:
+ ----- Method: MCTextWriter>>visitMethodDefinition: (in category 'visiting') -----
+ visitMethodDefinition: definition
+ 	self writeMethodPreamble: definition.
+ 	self writeMethodSource: definition.
+ 	stream cr!

Item was changed:
  ----- Method: SSProject>>initialize (in category 'initialization') -----
  initialize
  	super initialize.
  	id := title := description := String new.
  	accessPolicy := SSAccessPolicy new.
  	versions := Dictionary new.
  	dateCreated := Date today.
  	tags := SortedCollection new.
+ 	cache := IdentityDictionary new.!
- 	cache := IdentityDictionary new!

Item was added:
+ ----- Method: SSRepository class>>hostName (in category 'private') -----
+ hostName
+ 	^ (NetNameResolver nameForAddress: (NetNameResolver localHostAddress) timeout: 5)
+ 		ifNil: [NetNameResolver localAddressString]!

Item was added:
+ MCTextWriter subclass: #MCDiffyTextWriter
+ 	instanceVariableNames: ''
+ 	classVariableNames: ''
+ 	poolDictionaries: ''
+ 	category: 'SqueakSource-Notification'!

Item was added:
+ ----- Method: SSBasicTextWriter class>>description (in category 'as yet unclassified') -----
+ description
+ 	^ 'Summary only'!

Item was added:
+ ----- Method: SSProject>>versionAdded: (in category 'accessing-subscriptions') -----
+ versionAdded: aVersion
+ 	^ self subscriptionsDo: [:each | each new versionAdded: aVersion to: self]!

Item was added:
+ ----- Method: MCDiffyTextWriter>>writePatchHeader: (in category 'writing') -----
+ writePatchHeader: info
+ 	stream
+ 		nextPutAll: '=============== Diff against ';
+ 		nextPutAll: info name;
+ 		nextPutAll: ' ==============='; cr; cr!

Item was added:
+ ----- Method: SSEMailSubscription>>writeVersion (in category 'as yet unclassified') -----
+ writeVersion
+ 	| ancestor aVersion|
+ 	ancestor := version lastAncestorIn: project.
+ 	aVersion := (version reader: project) version.
+ 	ancestor
+ 		ifNotNil: [(MCDiffyTextWriter on: stream) writeVersion: 
+ 			(aVersion asDiffAgainst: (ancestor reader: project) version)]
+ 		ifNil: [(MCTextWriter on: stream) writeVersion: aVersion]
+ 		!

Item was added:
+ ----- Method: SSSubscription class>>renderOn: (in category 'as yet unclassified') -----
+ renderOn: html
+ 	html text: self description!

Item was added:
+ SSSubscription subclass: #SSEMailSubscription
+ 	instanceVariableNames: 'sender'
+ 	classVariableNames: ''
+ 	poolDictionaries: ''
+ 	category: 'SqueakSource-Notification'!

Item was added:
+ ----- Method: TextDiffBuilder>>printTextPatchSequence:on: (in category '*SqueakSource-Notifications') -----
+ printTextPatchSequence: seq on: aStream 
+ 	seq do: [:assoc | aStream
+ 		nextPutAll: (self stringForAttributes: assoc key);
+ 		nextPutAll: assoc value; cr]!

Item was changed:
  ----- Method: SSRepository>>metaobject (in category 'accessing') -----
  metaobject
  	| metaobject |
  	metaobject _ MWMetaobject for: self.
  
  	metaobject textAttribute: #rootUrl do: [ :attribute | 
  		attribute label: 'Root URL';
  			addRequiredRule].
  	metaobject integerAttribute: #port do: [ :attribute | 
  		attribute label: 'Port';
  			addValidationRule: [ :value | value notNil ]
  				errorString: 'invalid number';
  			addValidationRule: [ :value | value between: 1 and: 65535 ]
  				errorString: 'invalid port';
  			addRequiredRule].
  	metaobject textAttribute: #smtpServer do: [ :attribute | 
  		attribute label: 'SMTP server';
  			addRequiredRule].
  	metaobject textAttribute: #superUserEmail do: [ :attribute | 
  		attribute label: 'Admin email';
  			addRequiredRule].
+ 	metaobject textAttribute: #emailSender do: [ :attribute | 
+ 		attribute label: 'Generated email';
+ 			addRequiredRule].
+ 	metaobject multipleAttribute: #defaultEmailRecipients do: [ :attribute | 
+ 		attribute label: 'Default email recipients'; baseClass: SSEmailAddress].
+ 	metaobject textAttribute: #defaultReplyTo do: [ :attribute | 
+ 		attribute label: 'Default Reply-To Address'; maxLength: 50].
+ 	metaobject singleRelationshipAttribute: #commitWriterClass do: [ :attribute |
+ 		attribute label: 'Show in commit notifications';
+ 		relationshipTo: [SSBasicTextWriter withAllSubclasses];
+ 		formatWith: [ :class | class description]].
+ 	metaobject multipleRelationshipAttribute: #defaultSubscriptions do: [ :attribute |
+ 		attribute label: 'Default subscriptions'; relationshipTo: [SSSubscription allSubclasses]].
  	metaobject booleanAttribute: #allowRegisterProject do: [ :attribute | 
  		attribute label: 'Everyone can register projects'].
  	metaobject booleanAttribute: #allProjectsVisible do: [ :attribute | 
  		attribute label: 'Everyone can see all projects' ].
  	metaobject booleanAttribute: #allowCreateTag do: [ :attribute | 
  		attribute label: 'Everyone can create tags'].
  	metaobject integerAttribute: #batchSize do: [ :attribute | 
  		attribute label: 'Batch size for table reports';
  			addValidationRule: [ :value | value notNil ]
  				errorString: 'invalid number';
  			addValidationRule: [ :value | value > 1 ]
  				errorString: 'too small';
  			addRequiredRule].
  	metaobject colorAttribute: #styleColor do: [ :attribute | 
  		attribute label: 'Style color';
  			addValidationRule: [ :value | value ~= Color white ]
  			errorString: 'invalid color'].
  	metaobject textAttribute: #introText do: [ :attribute | 
  		attribute label: 'Override home text'; 
  			multiLine: true].
  	metaobject textAttribute: #timezone do: [ :attribute | 
  		attribute label: 'Timezone';
  			addRequiredRule ].
  	metaobject textAttribute: #googleAnalyticsAccount do: [ :attribute | 
  		attribute label: 'Google Analytics Account' ].
  	^metaobject!

Item was added:
+ ----- Method: SSBasicTextWriter>>textWriterClass (in category 'as yet unclassified') -----
+ textWriterClass
+ 	^ MCTextWriter!

Item was changed:
  ----- Method: SSProjectEditor>>renderMainOn: (in category 'rendering') -----
  renderMainOn: html	
+ 	self isSubForm ifTrue: [^ super renderMainOn: html].
  	self renderIntroductionTextOn: html.
  	html layoutTable: [
  		self renderValidationErrorsOn: html.
  		self renderFieldsOn: html.
  		self renderMemberListForAttribute: (self metaobject attributeOf: #admins) on: html.
  		self renderMemberListForAttribute: (self metaobject attributeOf: #developers) on: html.
  		self renderMemberListForAttribute: (self metaobject attributeOf: #guests) on: html.
  		html tableRowWith: [ html space ] span: 2.
  		html tableRowWith: [ self renderButtonsOn: html ] span: 2 ]!

Item was added:
+ ----- Method: SSRepository>>defaultReplyTo: (in category 'accessing-settings') -----
+ defaultReplyTo: aString
+ 	^self properties
+ 		at: #defaultReplyTo
+ 		put: aString!

Item was added:
+ ----- Method: SSBasicTextWriter>>writeVersion:for: (in category 'as yet unclassified') -----
+ writeVersion: aSSVersion for: aProject
+ 	self writeSummary: aSSVersion for: aProject.
+ 	self writeVersionInfo: aSSVersion versionInfo.!

Item was added:
+ ----- Method: MCTextWriterTest>>testWriteSnapshot (in category 'as yet unclassified') -----
+ testWriteSnapshot
+ 	Workspace new contents: (String streamContents: [:aStream | (MCTextWriter on: aStream) writeSnapshot: self mockSnapshot]); openLabel: 'pizza'.
+ !

Item was added:
+ ----- Method: SSRepository>>emailSender (in category 'accessing-settings') -----
+ emailSender
+ 	^self properties
+ 		at: #emailSender
+ 		ifAbsent: [self class defaultEmailSender]!

Item was added:
+ ----- Method: MCTextWriter>>visitInFork: (in category 'visiting') -----
+ visitInFork: aDefinition 
+ 	^ String streamContents: [ :forkedStream |
+ 		aDefinition accept: (self class on: forkedStream)]!

Item was added:
+ ----- Method: MCTextWriter>>writeSnapshot: (in category 'writing') -----
+ writeSnapshot: aSnapshot
+ 	stream nextPutAll: '==================== Snapshot ===================='; cr; cr.
+ 	super writeSnapshot: aSnapshot
+ !

Item was added:
+ ----- Method: SSBasicTextWriter>>writeSummary:for: (in category 'as yet unclassified') -----
+ writeSummary: aSSVersion for: aProject
+ 	| author |
+ 	author := aSSVersion authorString ifEmpty: [aSSVersion guessedAuthor].
+ 	author
+ 		ifNil: [ stream
+ 			nextPutAll: 'A new version of ';
+ 			nextPutAll: aSSVersion package;
+ 			nextPutAll: ' was added to project ';
+ 			nextPutAll: aProject title; nextPut: $:; cr]
+ 		ifNotNil: [ stream
+ 			nextPutAll: author;
+ 			nextPutAll: ' uploaded a new version of ';
+ 			nextPutAll: aSSVersion package;
+ 			nextPutAll: ' to project ';
+ 			nextPutAll: aProject title; nextPut: $:; cr].
+ 	stream nextPutAll: (aSSVersion url: aProject); cr; cr.!

Item was added:
+ ----- Method: SSRepository>>defaultReplyTo (in category 'accessing-settings') -----
+ defaultReplyTo
+ 	^self properties
+ 		at: #defaultReplyTo
+ 		ifAbsent: ['squeak-dev at lists.squeakfountation.org']!

Item was added:
+ ----- Method: MCDiffyTextWriter>>writeVersion: (in category 'writing') -----
+ writeVersion: aVersion
+ 	self writeVersionInfo: aVersion info.
+ 	self writePatchHeader: aVersion baseInfo.
+ 	self writePatch: aVersion patch.!

Item was added:
+ ----- Method: TextDiffBuilder>>buildTextPatch (in category '*SqueakSource-Notifications') -----
+ buildTextPatch
+ 	^String streamContents:[:stream|
+ 		self printTextPatchSequence: self buildPatchSequence on: stream.
+ 	]!

Item was added:
+ ----- Method: SSProject>>emailRecipients: (in category 'accessing') -----
+ emailRecipients: anObject
+ 	"Set the value of emailAddresses"
+ 
+ 	emailRecipients _ anObject!

Item was added:
+ ----- Method: MCDiffyTextWriter>>writeAddition: (in category 'writing') -----
+ writeAddition: anAddition 
+ 	stream nextPutAll: 'Item was added:'; cr.
+ 	self writePatchFrom: nil to: anAddition definition!

Item was added:
+ ----- Method: SSProject>>subscriptions (in category 'accessing-subscriptions') -----
+ subscriptions
+ 	"Answers the instances of SSSubscription who are interested in changes to this project"
+ 	^ subscriptions ifNil: [self repository defaultSubscriptions]!

Item was added:
+ ----- Method: MCTextWriter>>writeDefinitions: (in category 'writing') -----
+ writeDefinitions: aCollection
+ 	(MCDependencySorter sortItems: aCollection)
+ 		do: [:ea | ea accept: self. stream cr]
+ 		displayingProgress: 'Writing definitions...'.!


----- End forwarded message -----

-- 
Matthew Fulmer -- http://mtfulmer.wordpress.com/
Help improve Squeak Documentation: http://wiki.squeak.org/squeak/808



More information about the Squeak-dev mailing list