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@localhost ----- To: [hidden email] From: no-reply@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: ['[hidden email]']! 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 |
this is cool.
Stef On 22 août 07, at 01:34, Matthew Fulmer wrote: > 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@localhost ----- > > To: [hidden email] > From: no-reply@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: ['[hidden email]']! > > 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 > > |
Free forum by Nabble | Edit this page |