'From Cuis 5.0 [latest update: #3892] on 2 October 2019 at 7:39:10 am'!
!classDefinition: #FeatureRequirement category: #'System-Package Support'!
Object subclass: #FeatureRequirement
	instanceVariableNames: 'name minVersion minRevision maxVersion pathName'
	classVariableNames: ''
	poolDictionaries: ''
	category: 'System-Package Support'!
!FeatureRequirement commentStamp: '<historical>' prior: 0!
A package has a (possibly empty) set of FeatureRequirement's which specifies acceptable version range and minimum revision.  See class FeatureSpec.

  name -- a symbol naming the feature
  minVersion -- the minimum version required
  maxVersion -- the maximum version required
  minRevision -- the minumum revision of the minVersion (assumed zero if minVersion does not equal maxVersion
  path -- nil or the (cached)  pathName of a file containing the package with this feature

Usually use the convenience methods:
	Feature require: 'Sound'
	Feature require: 'Tests' version: 1.0

Try any of:
  (FeatureRequirement name: #'Sound') require.		"Ask for whatever version of Sound is available"
  (FeatureRequirement name: #'Sound' version: 1) require. 		"Ask for a specific version. Any revision will do"
  (FeatureRequirement name: #'Sound' version: 1 revision: 0) require.  	"Ask for a specific version.revision"
  (FeatureRequirement name: #'Sound' minVersion: 1 minRevision: 23 maxVersion: 3) require 		"Specific version.revision, or any greater version, up to max"

This should load packages #Compression and #Sound


  (FeatureRequirement name: #'Sound') print
  (FeatureRequirement name: #'Sound' version: 1) print
  (FeatureRequirement name: #'Sound' version: 1 revision: 12) print
  (FeatureRequirement name: #'Sound' minVersion: 1 minRevision: 23 maxVersion: nil) print
  (FeatureRequirement name: #'Sound' minVersion: 1 minRevision: nil maxVersion: 3) print
  (FeatureRequirement name: #'Sound' minVersion: 1 minRevision: 23 maxVersion: 3) print!


!FeatureRequirement methodsFor: 'accessing' stamp: 'KenD 7/7/2013 14:11'!
maxVersion 

	^maxVersion ! !

!FeatureRequirement methodsFor: 'accessing' stamp: 'KenD 7/7/2013 14:11'!
minRevision

	^minRevision ! !

!FeatureRequirement methodsFor: 'accessing' stamp: 'KenD 7/7/2013 14:11'!
minVersion 

	^minVersion ! !

!FeatureRequirement methodsFor: 'accessing' stamp: 'jmv 7/28/2013 22:50'!
name
	
	^ name! !

!FeatureRequirement methodsFor: 'accessing' stamp: 'jmv 10/16/2015 16:32'!
packageFileName
	"Answer a package name based on feature name"

	^ name asString, '.pck.st'! !

!FeatureRequirement methodsFor: 'accessing' stamp: 'jmv 7/28/2013 22:50'!
pathName: aPathNameString
	
	pathName := aPathNameString! !

!FeatureRequirement methodsFor: 'accessing' stamp: 'pb 5/25/2016 01:32'!
requirements
	"Answer my requirements"
	
	| answer |
	pathName asFileEntry readStreamDo: [ :stream |
		answer _ (CodePackageFile buildFileStream: stream) requires ].
	^ answer! !


!FeatureRequirement methodsFor: 'requires' stamp: 'jmv 10/21/2015 12:11'!
findPackageFileAsReqOf: mainFeatureOrNil
	"Look in known places for packages providing required feature.
	Answer wether search was successful."
	| packageFileName entry |
	pathName ifNotNil: [
		pathName asFileEntry exists ifTrue: [ ^ true ]].
	packageFileName _ self packageFileName.
	(mainFeatureOrNil ifNil: [ self ]) placesToLookForPackagesDo: [ :directory |
		entry _ directory // packageFileName.
		entry exists ifTrue: [
			pathName _ entry pathName.
			^ true ]].
	^ false! !

!FeatureRequirement methodsFor: 'requires' stamp: 'pb 5/25/2016 01:32'!
install
	"Preconditions have been satisfied.  Install the required package."
	pathName asFileEntry readStreamDo: [ :stream |
		CodePackageFile basicInstallPackageStream: stream ].

	"No need to have a platform and machine specific path around anymore. It was just for installation. Clear it."
	pathName _ nil! !

!FeatureRequirement methodsFor: 'requires' stamp: 'KenD 9/30/2019 15:12:46'!
require
	"See if all the transitive closure of requirements can be met and answer the load order if so"

	| packagesToLoad |
	
	"Preflight before load 
	  [1] Build transitive closure as load order where Feature comes 
	       before its required features."
	[packagesToLoad _ self requireUnlessIn: OrderedCollection new main: nil]
		on: FeatureRequirementUnsatisfied  
		do: [ :error | error defaultAction. 
			^self "exit"
		].
	
	"Transcript show: 'packagesToLoad: '; newLine.
	packagesToLoad do: [ :x |Transcript show: x; newLine ].
	Transcript newLine."
	
	"[2] Check to see that each requirement is actually satisfied"
	[packagesToLoad do: [ :fReq | fReq checkRequirement ]]
		on: FeatureRequirementUnsatisfied  
		do: [ :error | error defaultAction. 
			^self "exit"
		].

	"[3] Load required packages before packages that require them"
	packagesToLoad reverseDo: [ :requirement |
		requirement isAlreadySatisfied ifFalse: [
			requirement install ]].
	self isAlreadySatisfied ifFalse: [
		self install ]! !

!FeatureRequirement methodsFor: 'requires' stamp: 'KenD 9/30/2019 15:23:40'!
requireUnlessIn: toLoad main: mainFeatureOrNil
	"See if all the requirements can be met and answer the load order"

	self isAlreadySatisfied ifFalse: [
		(toLoad anySatisfy: [ :featReq | featReq sameNameAs: self])
		ifFalse: [
		(self findPackageFileAsReqOf: mainFeatureOrNil)
			ifTrue: [ | otherRequirements |
				otherRequirements := self requirements.
				otherRequirements ifNotNil: [
					otherRequirements do: [ :requires | 
						requires requireUnlessIn: toLoad main: (mainFeatureOrNil ifNil: [self])]].
				self == mainFeatureOrNil ifFalse: [
					self addToLoad: toLoad withMyRequirements: otherRequirements ].
				]
			ifFalse: [
				FeatureRequirementUnsatisfied 
					signal: 'Could not find package supplying: ',
							String newLineString, '        ',
							self printString 
			]]].

	^ toLoad! !

!FeatureRequirement methodsFor: 'requires' stamp: 'KenD 12/15/2015 14:30'!
satisfyRequirementsAndInstall
	"Like #require, but install me even if already satisified (i.e. installed)"

	| packagesToLoad |
	
	"Preflight before load"
	[packagesToLoad _ self requireUnlessIn: OrderedCollection new main: self]
		on: FeatureRequirementUnsatisfied  
		do: [ :error | error defaultAction. 
			^self "exit"
		].
	"All requirements are satisfied; do the deed"
	packagesToLoad reverseDo: [ :requirement |
		requirement isAlreadySatisfied ifFalse: [
			requirement install ]].
	self install! !


!FeatureRequirement methodsFor: 'testing' stamp: 'KenD 9/30/2019 08:12:46'!
checkRequirement
	"Answer if I am satisfied by package found at pathName"
	
	pathName asFileEntry readStreamDo: [ :stream | | fSpec |
		fSpec := ((CodePackageFile buildFileStream: stream) featureSpec).
		((fSpec isNil) or: [(fSpec provides satisfies: self) not])
		ifTrue: [
			FeatureRequirementUnsatisfied 
					signal: pathName,
							String newLineString,
							' could not satisfy ', self printString.
			^false
		]
		ifFalse: [ ^true ]
	]
							 
! !

!FeatureRequirement methodsFor: 'testing' stamp: 'jmv 7/28/2013 22:50'!
isAlreadySatisfied
	"Answer true if requirement is satisfied by some installed package, or by base system."

	(Feature baseSystemFeature satisfies: self)
		ifTrue: [ ^true ].

	^CodePackage installedPackages anySatisfy: [ :package |
		package provides satisfies: self ]! !

!FeatureRequirement methodsFor: 'testing' stamp: 'KenD 9/30/2019 09:51:40'!
sameNameAs: anotherFeatureRequirement

	"Answer true if anotherFeatureRequirement has same name as me"
	^(self name = anotherFeatureRequirement name)! !


!FeatureRequirement methodsFor: 'printing' stamp: 'jmv 6/1/2015 21:02'!
printDetailsOn: aStream
	aStream
		nextPutAll: name;
		nextPut: $ .
	minVersion
		ifNil: [
			aStream nextPutAll: '*.*)'.
			^self ]
		ifNotNil: [ minVersion printOn: aStream ].
	aStream nextPut: $..
	minRevision
		ifNil: [ aStream nextPut: $* ]
		ifNotNil: [ minRevision printOn: aStream ].
	(minRevision notNil or: [ maxVersion isNil or: [maxVersion > minVersion ]]) ifTrue: [
		aStream nextPutAll: ' to '.
		maxVersion
			ifNil: [ aStream nextPut: $* ]
			ifNotNil: [ maxVersion printOn: aStream ].
		aStream nextPutAll: '.*']! !

!FeatureRequirement methodsFor: 'printing' stamp: 'jmv 6/1/2015 21:00'!
printOn: aStream
	aStream
		nextPutAll: self class name;
		nextPut: $(.
	self printDetailsOn: aStream.
	aStream
		nextPut: $)! !


!FeatureRequirement methodsFor: 'private' stamp: 'KenD 9/30/2019 15:13:19'!
addToLoad: toLoad withMyRequirements: requirements

	"Add self to OrderedCollection 'toLoad' before any requirements I have"
	| myRequirements |
	myRequirements := self requirements.
	toLoad do: [ :featureReq | 
		(myRequirements anySatisfy: [ :req | featureReq sameNameAs: req])
		ifTrue: [
			"Transcript show: ('adding ', self name asString, ' before: ', featureReq name asString); newLine."
			toLoad add: self before: featureReq.
			^ toLoad
		]
	].
	"Transcript show: ('adding ', self name asString); newLine."
	toLoad addLast: self.
	^ toLoad
		! !

!FeatureRequirement methodsFor: 'private' stamp: 'jmv 3/20/2019 16:36:14'!
placesToLookForPackagesDo: aBlock

	| base myDir |

	"Look inside my own folder"
	pathName ifNotNil: [
		myDir _ pathName asFileEntry parent.
		aBlock value: myDir ].

	"Look in Cuis image folder and reasonable subfolders"
	base _ DirectoryEntry smalltalkImageDirectory.
	base allDirectoriesDo: aBlock.
	
	"Look in parent directory and reasonable subfolders. 
	Useful when image is stored in a subdirectory of the main app directory.
	This could be the case when the package comes from a 'main' git repo, and image is copied from gitHub.
	First try directories including the word Cuis in the name. Then try others."
	base parent directoriesDo: [ :dir |
		('*Cuis*' match: dir name)
			ifTrue: [aBlock value: dir. dir allDirectoriesDo: aBlock]].
	base parent directoriesDo: [ :dir |
		('*Cuis*' match: dir name)
			ifFalse: [aBlock value: dir. dir allDirectoriesDo: aBlock]].

	"Also look in host OS current directory"
	(base ~= DirectoryEntry currentDirectory and: [base parent ~= DirectoryEntry currentDirectory])
		ifTrue: [
			base _ DirectoryEntry currentDirectory.
			base allDirectoriesDo: aBlock ]! !


!FeatureRequirement methodsFor: 'comparing' stamp: 'jmv 8/9/2016 10:19:52'!
= another
	self == another ifTrue: [ ^ true ].
	self class == another class ifFalse: [ ^false ].
	^name = another name and: [
		minVersion = another minVersion and: [
		minRevision = another minRevision and: [
		maxVersion = another maxVersion ]]]! !

!FeatureRequirement methodsFor: 'comparing' stamp: 'jmv 6/11/2019 11:45:31'!
hash
	"Hash is reimplemented because = is implemented."

	^name hash! !


!FeatureRequirement methodsFor: 'initialization' stamp: 'jmv 7/28/2013 23:54'!
name: featureName minVersion: minVer minRevision: rev maxVersion: maxVer

	name := featureName asSymbol.
	minVersion := minVer.
	minRevision := rev.
	maxVersion := maxVer! !

"-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!

!classDefinition: 'FeatureRequirement class' category: #'System-Package Support'!
FeatureRequirement class
	instanceVariableNames: ''!

!FeatureRequirement class methodsFor: 'instance creation' stamp: 'jmv 7/28/2013 23:54'!
name: aSymbol
	"Ask for any version, and any revision"
	^self name: aSymbol minVersion: nil minRevision: nil maxVersion: nil! !

!FeatureRequirement class methodsFor: 'instance creation' stamp: 'jmv 7/28/2013 23:54'!
name: aSymbol minVersion: minVersionOrNil minRevision: minRevisionOrNil maxVersion: maxVersionOrNil

	| newInst |
	newInst _ self new.
	newInst name: aSymbol minVersion: minVersionOrNil minRevision: minRevisionOrNil maxVersion: maxVersionOrNil.
	
	^ newInst! !

!FeatureRequirement class methodsFor: 'instance creation' stamp: 'jmv 7/28/2013 23:54'!
name: aSymbol version: integerVersion
	"Ask for a specific version, but any revision"
	^self name: aSymbol minVersion: integerVersion minRevision: nil maxVersion: integerVersion! !

!FeatureRequirement class methodsFor: 'instance creation' stamp: 'jmv 7/28/2013 23:55'!
name: aSymbol version: integerVersion revision: integerRevision
	"Ask for a specific version, and revision"
	^self name: aSymbol minVersion: integerVersion minRevision: integerRevision maxVersion: integerVersion! !
