Skip to content

Commit

Permalink
Merge branch 'master' into doc_magritte-parsers
Browse files Browse the repository at this point in the history
  • Loading branch information
seandenigris authored Nov 19, 2023
2 parents 04e9184 + 0a2e139 commit fedee20
Show file tree
Hide file tree
Showing 12 changed files with 723 additions and 48 deletions.
22 changes: 17 additions & 5 deletions repository/Neo-CSV-Core/NeoCSVReader.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Class {
'recordClassIsIndexable',
'fieldAccessors',
'emptyFieldValue',
'emptyFieldInput',
'strict'
],
#category : #'Neo-CSV-Core'
Expand Down Expand Up @@ -297,6 +298,14 @@ NeoCSVReader >> do: block [
block value: self next ]
]

{ #category : #'initialize-release' }
NeoCSVReader >> emptyFieldInput: block [
"Set an optional block to test for field input values that should be considered empty.
The default is nil, equivalent to [ :field | field isEmpty ]."

emptyFieldInput := block
]

{ #category : #'initialize-release' }
NeoCSVReader >> emptyFieldValue: object [
"Set the value to be used when reading empty or missing fields.
Expand Down Expand Up @@ -452,11 +461,14 @@ NeoCSVReader >> readEndOfQuotedField [
{ #category : #'private - reading' }
NeoCSVReader >> readField [
^ self peekQuote
ifTrue: [
self readQuotedField ]
ifFalse: [
self readUnquotedField ]
| field |
field := self peekQuote
ifTrue: [ self readQuotedField ]
ifFalse: [ self readUnquotedField ].
(field ~= emptyFieldValue and: [ emptyFieldInput notNil ]) ifTrue: [
(emptyFieldInput value: field)
ifTrue: [ ^ emptyFieldValue ] ].
^ field
]
{ #category : #'private - reading' }
Expand Down
10 changes: 6 additions & 4 deletions repository/Neo-CSV-Core/NeoNumberParser.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -151,13 +151,15 @@ NeoNumberParser >> on: readStream [

{ #category : #parsing }
NeoNumberParser >> parseNumber [
| negated number |
| negated number isFloat |
negated := stream peekFor: $-.
number := self parseNumberInteger.
(stream peekFor: radixPoint)
ifTrue: [ number := number + self parseNumberFraction ].
isFloat := (stream peekFor: radixPoint)
ifTrue: [ number := number + self parseNumberFraction. true ]
ifFalse: [ false ].
((stream peekFor: $e) or: [ stream peekFor: $E ])
ifTrue: [ number := number * self parseNumberExponent ].
isFloat ifTrue: [ number := number asFloat ].
negated
ifTrue: [ number := number negated ].
^ number
Expand All @@ -180,7 +182,7 @@ NeoNumberParser >> parseNumberExponent [
NeoNumberParser >> parseNumberFraction [
| number power |
number := 0.
power := 1.0.
power := 1.
self digitsDo: [ :x |
number := base * number + x.
power := power * base ].
Expand Down
82 changes: 82 additions & 0 deletions repository/Neo-CSV-Magritte/MACSVField.class.st
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
Class {
#name : #MACSVField,
#superclass : #Object,
#instVars : [
'descriptionSource',
'name',
'encoder',
'decoder'
],
#category : #'Neo-CSV-Magritte'
}

{ #category : #accessing }
MACSVField >> configureDescriptionFor: aWriter [

| description |
self descriptionSource isSymbol ifTrue: [
description := aWriter subjectDescription detect: [ :fieldDesc |
fieldDesc definingContext methodSelector = self descriptionSource ] ].

self descriptionSource isBlock ifTrue: [
description := self descriptionSource value.
aWriter subjectDescription add: description ].

description
propertyAt: aWriter fieldNamePropertyKey
put: self name.

self encoder ifNotNil: [ :anEncoder |
description
propertyAt: aWriter fieldWriterPropertyKey
put: anEncoder ].

self decoder ifNotNil: [ :aDecoder |
description
propertyAt: aWriter fieldWriterPropertyKey
put: aDecoder ].

^ description
]

{ #category : #accessing }
MACSVField >> decoder [
^ decoder
]

{ #category : #accessing }
MACSVField >> decoder: anObject [
decoder := anObject
]

{ #category : #accessing }
MACSVField >> descriptionSource [
^ descriptionSource
]

{ #category : #accessing }
MACSVField >> descriptionSource: anObject [
"anObject - either a block returning a description, or the defining selector of a description already defined by the domain objects being written"

descriptionSource := anObject
]

{ #category : #accessing }
MACSVField >> encoder [
^ encoder
]

{ #category : #accessing }
MACSVField >> encoder: anObject [
encoder := anObject
]

{ #category : #accessing }
MACSVField >> name [
^ name
]

{ #category : #accessing }
MACSVField >> name: anObject [
name := anObject
]
63 changes: 52 additions & 11 deletions repository/Neo-CSV-Magritte/MACSVImporter.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,72 @@ Class {
'reader',
'readerClass',
'recordClass',
'source'
'source',
'columnNames'
],
#category : 'Neo-CSV-Magritte-Visitors'
#category : #'Neo-CSV-Magritte-Visitors'
}

{ #category : #accessing }
MACSVImporter >> columnNames [
"See setter comment"

^ columnNames
]

{ #category : #accessing }
MACSVImporter >> columnNames: aCollection [
"If not supplied, will assume the first row contains them"

columnNames := aCollection
]

{ #category : #private }
MACSVImporter >> configureReaderFor: aStream [
self reader on: aStream.
self columnNames ifNil: [ self columnNames: self readHeader ].
self reader recordClass: self recordClass.
]

{ #category : #accessing }
MACSVImporter >> execute [
^ self source isStream
ifTrue: [ self importStream: self source ]
ifFalse: [ self source readStreamDo: [ :str | self importStream: str ] ]
]

{ #category : #accessing }
MACSVImporter >> fieldNamePropertyKey [
"The property where the element description stores the field name; override to customize"

^ #csvFieldName
]

{ #category : #accessing }
MACSVImporter >> fieldReaderPropertyKey [
"The property where the element description stores the field reader. Override to customize. See `MAElementDescription>>#csvReader:` method comment for more info"

^ #csvReader
]

{ #category : #private }
MACSVImporter >> importStream: aStream [
| fields |
self configureReaderFor: aStream.

| fields header |
self reader on: aStream.
header := self readHeader.
self reader recordClass: self recordClass.
fields := self recordClass new magritteDescription children.
header do: [ :h |
fields
detect: [ :f | f csvFieldName = h asString trimmed ]
ifFound: [ :e | self reader addFieldDescribedByMagritte: e ]
ifNone: [ self reader addIgnoredField ] ].
self columnNames
do: [ :h |
fields
detect: [ :f |
f
propertyAt: self fieldNamePropertyKey
ifPresent: [ :fieldName | fieldName = h asString trimmed ]
ifAbsent: [ false ] ]
ifFound: [ :e |
self flag: 'need a way to customize the reader here'.
self reader addFieldDescribedByMagritte: e ]
ifNone: [ self reader addIgnoredField ] ].
^ self reader upToEnd "or do more processing e.g. `select: [ :record | record lastName isNotNil ]`"
]

Expand Down
104 changes: 104 additions & 0 deletions repository/Neo-CSV-Magritte/MACSVMappedPragmaBuilder.class.st
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
"
I provide a way to extend Magritte element descriptions for CSV reading without modifying the containing domain class. I help avoid bloating domain classes with extensions for each supported CSV source. For example, for contacts, Google has its field names, Outlook has others… By using me, you will not have to extend existing element descriptions via Magritte's pragma (the one that takes the description selector as an argument).
To configure me, you should:
- set the `#fieldNamePropertyKey` that your reader expects to map descriptions to CSV fields
- as needed, specify the `#fieldReaderPropertyKey` that your reader uses to convert the CSV field string to a domain object; otherwise the default reader will be used
The two primary ways to map fields to descriptions are:
- `aBuilder map: fieldName fieldTo: descriptionSelector`
- `aBuilder map: fieldName fieldTo: descriptionSelector using: reader`
"
Class {
#name : #MACSVMappedPragmaBuilder,
#superclass : #MAPragmaBuilder,
#instVars : [
'map',
'fieldNamePropertyKey',
'fieldReaderPropertyKey'
],
#category : #'Neo-CSV-Magritte-Support'
}

{ #category : #private }
MACSVMappedPragmaBuilder >> description: description extendedBy: descriptionExtensions for: descriptionSelector of: anObject [
| result |
result := super
description: description
extendedBy: descriptionExtensions
for: descriptionSelector
of: anObject.

self ensureFieldPropertiesForDescription: result from: descriptionSelector.

^ result
]

{ #category : #private }
MACSVMappedPragmaBuilder >> ensureFieldPropertiesForDescription: description from: descriptionSelector [

self map
at: descriptionSelector
ifPresent: [ :anArray |
description
propertyAt: self fieldNamePropertyKey
put: anArray first.

anArray second ifNil: [ ^ self ].
description
propertyAt: self fieldReaderPropertyKey
put: [ :trimmed |
anArray second
cull: trimmed
cull: description ] ].
]

{ #category : #accessing }
MACSVMappedPragmaBuilder >> fieldNamePropertyKey [
^ fieldNamePropertyKey
]

{ #category : #accessing }
MACSVMappedPragmaBuilder >> fieldNamePropertyKey: anObject [
fieldNamePropertyKey := anObject
]

{ #category : #accessing }
MACSVMappedPragmaBuilder >> fieldReaderPropertyKey [
^ fieldReaderPropertyKey
]

{ #category : #accessing }
MACSVMappedPragmaBuilder >> fieldReaderPropertyKey: anObject [
fieldReaderPropertyKey := anObject
]

{ #category : #accessing }
MACSVMappedPragmaBuilder >> map [
^ map ifNil: [ map := Dictionary new ]
]

{ #category : #accessing }
MACSVMappedPragmaBuilder >> map: anObject [
map := anObject
]

{ #category : #accessing }
MACSVMappedPragmaBuilder >> map: fieldName fieldTo: descriptionSelector [

self map: fieldName fieldTo: descriptionSelector using: nil
]

{ #category : #accessing }
MACSVMappedPragmaBuilder >> map: fieldName fieldTo: descriptionSelector using: reader [
"
descriptionSelector - returning the description to be used
reader - typically a block, where arguments are:
1) the field string input and
2) the description
A typical pattern is to customize the description and then use the default reader with something like this:
`desc defaultCsvReader cull: input trimmed`"

self map at: descriptionSelector put: { fieldName. reader }
]
Loading

0 comments on commit fedee20

Please sign in to comment.