54
|
1 |
"{ Package: 'stx:goodies/xtreams/substreams' }"
|
|
2 |
|
|
3 |
LibraryDefinition subclass:#stx_goodies_xtreams_substreams
|
|
4 |
instanceVariableNames:''
|
|
5 |
classVariableNames:''
|
|
6 |
poolDictionaries:''
|
|
7 |
category:'* Projects & Packages *'
|
|
8 |
!
|
|
9 |
|
86
|
10 |
stx_goodies_xtreams_substreams comment:'Substreams are streams embedded in other streams. Often there are multiple substreams embedded sequentially in a parent stream (for example multipart MIME messages). This package allows efficient handling of common types of substreams.
|
|
11 |
|
|
12 |
=== Fixed size substreams (#limiting:) ===
|
|
13 |
|
|
14 |
Limiting substream starts at the position when the substream is created and ends when the specified number of elements is read or written.
|
|
15 |
{{{
|
|
16 |
(''abcdefghijklmno'' reading limiting: 10) rest
|
|
17 |
}}}
|
|
18 |
{{{
|
|
19 |
| stream substream |
|
|
20 |
stream := String new writing.
|
|
21 |
substream := stream limiting: 5.
|
|
22 |
[ substream write: ''Hello World!!'' ] on: Incomplete do: [].
|
|
23 |
stream conclusion
|
|
24 |
}}}
|
|
25 |
|
|
26 |
|
|
27 |
=== Bounded substreams (#ending:/#ending:inclusive:) ===
|
|
28 |
|
|
29 |
Bounded substream ends when the argument matches the content passing through the stream. The argument can take one of the following forms:
|
|
30 |
* a block - evaluated with each element; the stream ends when the block returns true
|
|
31 |
{{{
|
|
32 |
(''abcdefghijklmnopqrstuvxyz'' reading ending: [ :e | ''gmt'' includes: e ]) rest.
|
|
33 |
}}}
|
|
34 |
{{{
|
|
35 |
| stream substream |
|
|
36 |
stream := String new writing.
|
|
37 |
substream := stream ending: [ :e | ''gmt'' includes: e ].
|
|
38 |
[ substream write: ''abcdefghijklmnopqrstuvxyz'' ] on: Incomplete do: [].
|
|
39 |
stream conclusion
|
|
40 |
}}}
|
|
41 |
* a collection - matched against the matching amount of last elements going through the stream; the stream ends when the collection matches
|
|
42 |
{{{
|
|
43 |
(''abcdefghijklmnopqrstuvxyz'' reading ending: ''mno'' inclusive: true) rest.
|
|
44 |
}}}
|
|
45 |
{{{
|
|
46 |
| stream substream |
|
|
47 |
stream := String new writing.
|
|
48 |
substream := stream ending: ''mno''.
|
|
49 |
[ substream write: ''abcdefghijklmnopqrstuvxyz'' ] on: Incomplete do: [].
|
|
50 |
stream conclusion
|
|
51 |
}}}
|
|
52 |
* any other object - the stream ends when an equal element passes through the stream"
|
|
53 |
{{{
|
|
54 |
(''ab#cd#ef!!ABC##'' reading ending: $!!) rest.
|
|
55 |
}}}
|
|
56 |
{{{
|
|
57 |
| stream substream |
|
|
58 |
stream := String new writing.
|
|
59 |
substream := stream ending: $!!.
|
|
60 |
[ substream write: ''Hello World!! Bye World!!'' ] on: Incomplete do: [].
|
|
61 |
stream conclusion
|
|
62 |
}}}
|
|
63 |
There is also a longer form #ending:inclusive: which takes an additional Boolean argument determining if the matching elements should be part of the substream content or not. The short form assumes the matching elements should be omitted.
|
|
64 |
{{{
|
|
65 |
(''abcdefghijklmnopqrstuvxyz'' reading ending: [ :e | ''gmt'' includes: e ] inclusive: true) rest.
|
|
66 |
}}}
|
|
67 |
{{{
|
|
68 |
| stream substream |
|
|
69 |
stream := String new writing.
|
|
70 |
substream := stream ending: [ :e | ''gmt'' includes: e ] inclusive: true.
|
|
71 |
[ substream write: ''abcdefghijklmnopqrstuvxyz'' ] on: Incomplete do: [].
|
|
72 |
stream conclusion
|
|
73 |
}}}
|
|
74 |
Finally, closing a substream doesn''t close the underlying stream by default. This is usually desirable because another substream is likely to follow so the main stream cannot be closed in that case. So in normal use the main stream is usually closed separately from the substreams. However, closing behavior of any substream can be modified through a custom closeBlock: if different behavior is desired.
|
|
75 |
{{{
|
|
76 |
| limited |
|
|
77 |
limited := (String new writing limiting: 10) closeBlock: [ :stream | stream destination close ]; yourself.
|
|
78 |
[ limited write: Object comment ] on: Incomplete do: [ :ex | ].
|
|
79 |
limited close; terminal
|
|
80 |
}}}
|
|
81 |
|
|
82 |
|
|
83 |
=== Streams of substreams (slicing and stitching) ===
|
|
84 |
|
|
85 |
Slicing and Stitching is a meta-stream concept. Slicing breaks a stream with content that is delimited in some fashion (limiting:, ending:, etc) in to multiple substreams. The result of slicing is a read stream of substreams. Stitching is the inverse of slicing. It takes a read stream of streams and stiches those back together to look like a single continuous stream. Stitching a slicing stream will normally give the same result as the original underlying stream.
|
|
86 |
|
|
87 |
Here is an example that creates a slicing stream that cuts the input up into substreams of size 5.
|
|
88 |
{{{
|
|
89 |
((1 to: 49) reading limiting: 5) slicing collect: [ :slice | slice rest ]
|
|
90 |
}}}
|
|
91 |
Note that only the latest substream is active, previous substream is automatically depleted before next substream is created.
|
|
92 |
Also note that since slicing always creates substreams, it is always a read stream, even if you''re slicing a write stream. Output of the slicing stream are the substreams. Again, only the latest substream is active. The previous substream is automatically closed before next substream is created.
|
|
93 |
{{{
|
|
94 |
| samples slices |
|
|
95 |
samples := String new writing.
|
|
96 |
slices := (samples limiting: 3) slicing.
|
|
97 |
Date.MonthNames do: [ :month | [ slices get write: month ] on: Xtreams.Incomplete do: [] ].
|
|
98 |
samples conclusion
|
|
99 |
}}}
|
|
100 |
|
|
101 |
In the following example the stream contains multiple messages delimited by $!! and each message has multiple parts ending with $#. We want to process each part as a stream of its own (this is a simplified version of how multipart messages are represented in MIME).
|
|
102 |
{{{
|
|
103 |
| messages |
|
|
104 |
messages := (''ab#cd#ef!!ABC##'' reading ending: $!!) slicing.
|
|
105 |
messages collect: [ :message |
|
|
106 |
(message ending: $#) slicing collect: [ :part | part rest ] ]
|
|
107 |
}}}
|
|
108 |
Note that since only the last slice is active any sort of read-ahead will likely interfere with the expected behavior, e.g. if we used collecting: instead of collect: in the example above, it would not work.
|
|
109 |
|
|
110 |
To generate the "hash-bang" style message encoding used in previous example we don''t need any sort of end-detecting substream. Instead we need to emit the closing character when the substream is closed. There is a special "closing" substream and corresponding slicer for that as well.
|
|
111 |
{{{
|
|
112 |
| connection messages |
|
|
113 |
connection := String new writing.
|
|
114 |
messages := (connection closing: [ connection put: $!! ]) slicing.
|
|
115 |
3 timesRepeat: [ | parts message |
|
|
116 |
message := messages get.
|
|
117 |
parts := (message closing: [ connection put: $# ]) slicing.
|
|
118 |
#(one two three four) do: [ :body | parts get write: body ] ].
|
|
119 |
connection conclusion
|
|
120 |
}}}
|
|
121 |
Stitching takes a read stream of streams and makes them behave as a single continuous stream. Following example takes the ''chunks'' and stitches them together.
|
|
122 |
{{{
|
|
123 |
| chunks |
|
|
124 |
chunks := (#(''abc'' ''de'' '''' ''fghij'') collect: [ :c | c reading ]) reading.
|
|
125 |
chunks stitching rest
|
|
126 |
}}}
|
|
127 |
The stream of streams can be inifinite in which case the stitching stream is inifinite as well. For example the following stitching stream will not end.
|
|
128 |
{{{
|
|
129 |
| main |
|
|
130 |
main := (1 to: 10) reading.
|
|
131 |
[ main limiting: 3 ] reading stitching rest
|
|
132 |
}}}
|
|
133 |
The problem is that the underlying stream of streams keeps producing empty limiting streams at the end of the main stream. To make the example end with an empty limiting stream can be done as follows.
|
|
134 |
{{{
|
|
135 |
| main wasEmpty |
|
|
136 |
main := (1 to: 10) reading.
|
|
137 |
wasEmpty := false.
|
|
138 |
[ wasEmpty ifTrue: [ Incomplete zero raise ].
|
|
139 |
wasEmpty := true.
|
|
140 |
(main doing: [ :e | wasEmpty := false ]) limiting: 3
|
|
141 |
] reading stitching rest
|
|
142 |
}}}
|
|
143 |
Here the #doing: transform captures the fact that there was in fact an element flowing through the limited: stream and sets the wasEmpty flag accordingly. This way we can detect the first empty limiting: stream and raise Incomplete. Alternatively the block stream can capture the substream in a variable and before creating next one it can check if its position reached the limit.
|
|
144 |
{{{
|
|
145 |
| main current |
|
|
146 |
main := (1 to: 10) reading.
|
|
147 |
current := nil.
|
|
148 |
[ (current notNil and: [ current position < 3 ]) ifTrue: [ Incomplete zero raise ].
|
|
149 |
current := main limiting: 3
|
|
150 |
] reading stitching rest
|
|
151 |
}}}
|
|
152 |
Following example traverses current directory recursively by continuously adding to a queue of directories to search from and using the stitching to combine them together in to one long stream of filenames.
|
|
153 |
{{{
|
|
154 |
| directories files |
|
|
155 |
directories := Xtreams.ElasticBuffer new: 10 class: Array.
|
|
156 |
directories put: ''.'' asFilename.
|
|
157 |
files :=
|
|
158 |
[ directories get reading
|
|
159 |
doing: [:filename | filename isDirectory ifTrue: [directories put: filename]]
|
|
160 |
] reading stitching.
|
|
161 |
files rest
|
|
162 |
}}}
|
|
163 |
|
|
164 |
A practical example of stitching write streams is chunking of written content into size-prefixed chunks of some maximum size. This is something that can be often seen in network protocols (e.g. when individual chunks need to be encrypted or signed).
|
|
165 |
{{{
|
|
166 |
| output buffer |
|
|
167 |
output := ByteArray new writing.
|
|
168 |
buffer := RingBuffer on: (ByteArray new: 5).
|
|
169 |
[ (buffer writing limiting: buffer cacheSize)
|
|
170 |
closeBlock: [ output put: buffer readSize; write: buffer ];
|
|
171 |
yourself
|
|
172 |
] reading stitching
|
|
173 |
write: (1 to: 22); close.
|
|
174 |
output close terminal
|
|
175 |
}}}
|
|
176 |
'
|
|
177 |
!
|
|
178 |
|
54
|
179 |
!stx_goodies_xtreams_substreams class methodsFor:'documentation'!
|
|
180 |
|
|
181 |
extensionsVersion_SVN
|
86
|
182 |
^ '$Id: extensions.st 76 2012-01-30 22:41:10Z mkobetic $'
|
54
|
183 |
! !
|
|
184 |
|
|
185 |
!stx_goodies_xtreams_substreams class methodsFor:'description'!
|
|
186 |
|
|
187 |
excludedFromPreRequisites
|
|
188 |
"list all packages which should be ignored in the automatic
|
|
189 |
preRequisites scan. See #preRequisites for more."
|
|
190 |
|
|
191 |
^ #(
|
|
192 |
)
|
|
193 |
!
|
|
194 |
|
|
195 |
preRequisites
|
|
196 |
"list all required packages.
|
|
197 |
This list can be maintained manually or (better) generated and
|
|
198 |
updated by scanning the superclass hierarchies and looking for
|
|
199 |
global variable accesses. (the browser has a menu function for that)
|
|
200 |
Howevery, often too much is found, and you may want to explicitely
|
|
201 |
exclude individual packages in the #excludedFromPrerequisites method."
|
|
202 |
|
|
203 |
^ #(
|
75
|
204 |
#'stx:goodies/xtreams/core' "Xtreams::WriteStream - superclass of Xtreams::WriteSubstream "
|
76
|
205 |
#'stx:goodies/xtreams/support'
|
75
|
206 |
#'stx:libbasic' "Object - superclass of Xtreams::TestReadSubstream "
|
54
|
207 |
)
|
|
208 |
! !
|
|
209 |
|
|
210 |
!stx_goodies_xtreams_substreams class methodsFor:'description - contents'!
|
|
211 |
|
|
212 |
classNamesAndAttributes
|
|
213 |
"lists the classes which are to be included in the project.
|
|
214 |
Each entry in the list may be: a single class-name (symbol),
|
|
215 |
or an array-literal consisting of class name and attributes.
|
|
216 |
Attributes are: #autoload or #<os> where os is one of win32, unix,..."
|
|
217 |
|
|
218 |
^ #(
|
|
219 |
"<className> or (<className> attributes...) in load order"
|
|
220 |
#'Xtreams::ReadSubstream'
|
|
221 |
#'Xtreams::StitchReadStream'
|
|
222 |
#'Xtreams::StitchWriteStream'
|
|
223 |
#'Xtreams::WriteSubstream'
|
75
|
224 |
#'stx_goodies_xtreams_substreams'
|
54
|
225 |
#'Xtreams::MatchReadSubstream'
|
|
226 |
#'Xtreams::MatchWriteSubstream'
|
|
227 |
#'Xtreams::PositionReadSubstream'
|
|
228 |
#'Xtreams::PositionWriteSubstream'
|
|
229 |
#'Xtreams::TestReadSubstream'
|
|
230 |
#'Xtreams::TestWriteSubstream'
|
|
231 |
#'Xtreams::LimitReadSubstream'
|
|
232 |
#'Xtreams::LimitWriteSubstream'
|
|
233 |
)
|
|
234 |
!
|
|
235 |
|
|
236 |
extensionMethodNames
|
|
237 |
"lists the extension methods which are to be included in the project.
|
|
238 |
Entries are 2-element array literals, consisting of class-name and selector."
|
|
239 |
|
|
240 |
^ #(
|
|
241 |
Block streamingReadMatching:inclusive:
|
|
242 |
Block streamingWriteMatching:inclusive:
|
|
243 |
Object streamingReadMatching:inclusive:
|
|
244 |
Object streamingWriteMatching:inclusive:
|
|
245 |
SequenceableCollection streamingMatchPrefixFunction
|
|
246 |
SequenceableCollection streamingReadMatching:inclusive:
|
|
247 |
SequenceableCollection streamingWriteMatching:inclusive:
|
|
248 |
#'Xtreams::ReadStream' #','
|
|
249 |
#'Xtreams::ReadStream' closing:
|
|
250 |
#'Xtreams::ReadStream' ending:
|
|
251 |
#'Xtreams::ReadStream' ending:inclusive:
|
|
252 |
#'Xtreams::ReadStream' limiting:
|
|
253 |
#'Xtreams::ReadStream' slicing
|
|
254 |
#'Xtreams::ReadStream' stitching
|
|
255 |
#'Xtreams::WriteStream' closing:
|
|
256 |
#'Xtreams::WriteStream' ending:
|
|
257 |
#'Xtreams::WriteStream' ending:inclusive:
|
|
258 |
#'Xtreams::WriteStream' limiting:
|
|
259 |
#'Xtreams::WriteStream' slicing
|
|
260 |
#'Xtreams::WriteStream' stitching
|
75
|
261 |
Block cull:
|
|
262 |
Block cull:cull:
|
|
263 |
SequenceableCollection copyGrownToAtLeast:
|
|
264 |
SequenceableCollection recycle
|
54
|
265 |
)
|
|
266 |
! !
|
|
267 |
|
|
268 |
!stx_goodies_xtreams_substreams class methodsFor:'description - project information'!
|
|
269 |
|
|
270 |
applicationIconFileName
|
|
271 |
"Return the name (without suffix) of an icon-file (the app's icon); will be included in the rc-resource file"
|
|
272 |
|
|
273 |
^ nil
|
|
274 |
"/ ^ self applicationName
|
|
275 |
!
|
|
276 |
|
|
277 |
companyName
|
|
278 |
"Return a companyname which will appear in <lib>.rc"
|
|
279 |
|
|
280 |
^ 'eXept Software AG'
|
|
281 |
!
|
|
282 |
|
|
283 |
description
|
|
284 |
"Return a description string which will appear in vc.def / bc.def"
|
|
285 |
|
|
286 |
^ 'Smalltalk/X Class library'
|
|
287 |
!
|
|
288 |
|
|
289 |
legalCopyright
|
|
290 |
"Return a copyright string which will appear in <lib>.rc"
|
|
291 |
|
|
292 |
^ 'Copyright Claus Gittinger 1988-2011\nCopyright eXept Software AG 1998-2011'
|
|
293 |
!
|
|
294 |
|
|
295 |
productName
|
|
296 |
"Return a product name which will appear in <lib>.rc"
|
|
297 |
|
|
298 |
^ 'Smalltalk/X'
|
|
299 |
! !
|
|
300 |
|
|
301 |
!stx_goodies_xtreams_substreams class methodsFor:'description - svn'!
|
|
302 |
|
|
303 |
svnRepositoryUrlString
|
|
304 |
"Return a SVN repository URL of myself.
|
|
305 |
(Generated since 2011-04-08)
|
|
306 |
"
|
|
307 |
|
|
308 |
^ '$URL: https://swing.fit.cvut.cz/svn/stx/goodies/xtreams/trunk/substreams/stx_goodies_xtreams_substreams.st $'
|
|
309 |
!
|
|
310 |
|
|
311 |
svnRevisionNr
|
|
312 |
"Return a SVN revision number of myself.
|
|
313 |
This number is updated after a commit"
|
|
314 |
|
76
|
315 |
^ "$SVN-Revision:"'76'"$"
|
54
|
316 |
! !
|
|
317 |
|
|
318 |
!stx_goodies_xtreams_substreams class methodsFor:'documentation'!
|
|
319 |
|
|
320 |
version_SVN
|
|
321 |
^ '$Id: stx_goodies_xtreams_substreams.st 14 2011-11-21 06:01:55Z mkobetic $'
|
|
322 |
! !
|