Adding an initial S3 sample and code to register for event notification on an S3 bucket.
Showing
56 changed files
with
5094 additions
and
2 deletions
... | @@ -170,7 +170,20 @@ class Kappa(object): | ... | @@ -170,7 +170,20 @@ class Kappa(object): |
170 | for log_event in response['events']: | 170 | for log_event in response['events']: |
171 | print(log_event['message']) | 171 | print(log_event['message']) |
172 | 172 | ||
173 | - def add_event_source(self): | 173 | + def _get_function_arn(self): |
174 | + name = self.config['lambda']['name'] | ||
175 | + arn = None | ||
176 | + lambda_svc = self.session.create_client('lambda', self.region) | ||
177 | + try: | ||
178 | + response = lambda_svc.get_function_configuration( | ||
179 | + FunctionName=name) | ||
180 | + LOG.debug(response) | ||
181 | + arn = response['FunctionARN'] | ||
182 | + except Exception: | ||
183 | + LOG.debug('Unable to find ARN for function: %s' % name) | ||
184 | + return arn | ||
185 | + | ||
186 | + def _add_kinesis_event_source(self, event_source_arn): | ||
174 | lambda_svc = self.session.create_client('lambda', self.region) | 187 | lambda_svc = self.session.create_client('lambda', self.region) |
175 | try: | 188 | try: |
176 | invoke_role = self.get_role_arn( | 189 | invoke_role = self.get_role_arn( |
... | @@ -178,12 +191,38 @@ class Kappa(object): | ... | @@ -178,12 +191,38 @@ class Kappa(object): |
178 | response = lambda_svc.add_event_source( | 191 | response = lambda_svc.add_event_source( |
179 | FunctionName=self.config['lambda']['name'], | 192 | FunctionName=self.config['lambda']['name'], |
180 | Role=invoke_role, | 193 | Role=invoke_role, |
181 | - EventSource=self.config['lambda']['event_source'], | 194 | + EventSource=event_source_arn, |
182 | BatchSize=self.config['lambda'].get('batch_size', 100)) | 195 | BatchSize=self.config['lambda'].get('batch_size', 100)) |
183 | LOG.debug(response) | 196 | LOG.debug(response) |
184 | except Exception: | 197 | except Exception: |
185 | LOG.exception('Unable to add event source') | 198 | LOG.exception('Unable to add event source') |
186 | 199 | ||
200 | + def _add_s3_event_source(self, event_source_arn): | ||
201 | + s3_svc = self.session.create_client('s3', self.region) | ||
202 | + bucket_name = event_source_arn.split(':')[-1] | ||
203 | + invoke_role = self.get_role_arn( | ||
204 | + self.config['cloudformation']['invoke_role']) | ||
205 | + notification_spec = { | ||
206 | + 'CloudFunctionConfiguration': { | ||
207 | + 'Id': 'Kappa-%s-notification' % self.config['lambda']['name'], | ||
208 | + 'Events': [e for e in self.config['lambda']['s3_events']], | ||
209 | + 'CloudFunction': self._get_function_arn(), | ||
210 | + 'InvocationRole': invoke_role}} | ||
211 | + response = s3_svc.put_bucket_notification( | ||
212 | + Bucket=bucket_name, | ||
213 | + NotificationConfiguration=notification_spec) | ||
214 | + LOG.debug(response) | ||
215 | + | ||
216 | + def add_event_source(self): | ||
217 | + event_source_arn = self.config['lambda']['event_source'] | ||
218 | + _, _, svc, _ = event_source_arn.split(':', 3) | ||
219 | + if svc == 'kinesis': | ||
220 | + self._add_kinesis_event_source(event_source_arn) | ||
221 | + elif svc == 's3': | ||
222 | + self._add_s3_event_source(event_source_arn) | ||
223 | + else: | ||
224 | + raise ValueError('Unsupported event source: %s' % event_source_arn) | ||
225 | + | ||
187 | def deploy(self): | 226 | def deploy(self): |
188 | self.create_update_roles( | 227 | self.create_update_roles( |
189 | self.config['cloudformation']['stack_name'], | 228 | self.config['cloudformation']['stack_name'], | ... | ... |
samples/s3/config.yml
0 → 100644
1 | +--- | ||
2 | +profile: personal | ||
3 | +region: us-east-1 | ||
4 | +cloudformation: | ||
5 | + template: roles.cf | ||
6 | + stack_name: TestS3 | ||
7 | + exec_role: ExecRole | ||
8 | + invoke_role: InvokeRole | ||
9 | +lambda: | ||
10 | + name: S3Sample | ||
11 | + zipfile_name: S3Sample.zip | ||
12 | + description: Testing S3 Lambda handler | ||
13 | + path: examplefolder | ||
14 | + handler: CreateThumbnail.handler | ||
15 | + runtime: nodejs | ||
16 | + memory_size: 128 | ||
17 | + timeout: 3 | ||
18 | + mode: event | ||
19 | + test_data: input.json | ||
20 | + event_source: arn:aws:s3:::garnaat_pub | ||
21 | + s3_events: | ||
22 | + - s3:ObjectCreated:* | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
samples/s3/examplefolder/CreateThumbnail.js
0 → 100644
1 | +// dependencies | ||
2 | +var async = require('async'); | ||
3 | +var AWS = require('aws-sdk'); | ||
4 | +var gm = require('gm') | ||
5 | + .subClass({ imageMagick: true }); // Enable ImageMagick integration. | ||
6 | +var util = require('util'); | ||
7 | + | ||
8 | +// constants | ||
9 | +var MAX_WIDTH = 100; | ||
10 | +var MAX_HEIGHT = 100; | ||
11 | + | ||
12 | +// get reference to S3 client | ||
13 | +var s3 = new AWS.S3(); | ||
14 | + | ||
15 | +exports.handler = function(event, context) { | ||
16 | + // Read options from the event. | ||
17 | + console.log("Reading options from event:\n", util.inspect(event, {depth: 5})); | ||
18 | + var srcBucket = event.Records[0].s3.bucket.name; | ||
19 | + var srcKey = event.Records[0].s3.object.key; | ||
20 | + var dstBucket = srcBucket + "resized"; | ||
21 | + var dstKey = "resized-" + srcKey; | ||
22 | + | ||
23 | + // Sanity check: validate that source and destination are different buckets. | ||
24 | + if (srcBucket == dstBucket) { | ||
25 | + console.error("Destination bucket must not match source bucket."); | ||
26 | + return; | ||
27 | + } | ||
28 | + | ||
29 | + // Infer the image type. | ||
30 | + var typeMatch = srcKey.match(/\.([^.]*)$/); | ||
31 | + if (!typeMatch) { | ||
32 | + console.error('unable to infer image type for key ' + srcKey); | ||
33 | + return; | ||
34 | + } | ||
35 | + var imageType = typeMatch[1]; | ||
36 | + if (imageType != "jpg" && imageType != "png") { | ||
37 | + console.log('skipping non-image ' + srcKey); | ||
38 | + return; | ||
39 | + } | ||
40 | + | ||
41 | + // Download the image from S3, transform, and upload to a different S3 bucket. | ||
42 | + async.waterfall([ | ||
43 | + function download(next) { | ||
44 | + // Download the image from S3 into a buffer. | ||
45 | + s3.getObject({ | ||
46 | + Bucket: srcBucket, | ||
47 | + Key: srcKey | ||
48 | + }, | ||
49 | + next); | ||
50 | + }, | ||
51 | + function tranform(response, next) { | ||
52 | + gm(response.Body).size(function(err, size) { | ||
53 | + // Infer the scaling factor to avoid stretching the image unnaturally. | ||
54 | + var scalingFactor = Math.min( | ||
55 | + MAX_WIDTH / size.width, | ||
56 | + MAX_HEIGHT / size.height | ||
57 | + ); | ||
58 | + var width = scalingFactor * size.width; | ||
59 | + var height = scalingFactor * size.height; | ||
60 | + | ||
61 | + // Transform the image buffer in memory. | ||
62 | + this.resize(width, height) | ||
63 | + .toBuffer(imageType, function(err, buffer) { | ||
64 | + if (err) { | ||
65 | + next(err); | ||
66 | + } else { | ||
67 | + next(null, response.ContentType, buffer); | ||
68 | + } | ||
69 | + }); | ||
70 | + }); | ||
71 | + }, | ||
72 | + function upload(contentType, data, next) { | ||
73 | + // Stream the transformed image to a different S3 bucket. | ||
74 | + s3.putObject({ | ||
75 | + Bucket: dstBucket, | ||
76 | + Key: dstKey, | ||
77 | + Body: data, | ||
78 | + ContentType: contentType | ||
79 | + }, | ||
80 | + next); | ||
81 | + } | ||
82 | + ], function (err) { | ||
83 | + if (err) { | ||
84 | + console.error( | ||
85 | + 'Unable to resize ' + srcBucket + '/' + srcKey + | ||
86 | + ' and upload to ' + dstBucket + '/' + dstKey + | ||
87 | + ' due to an error: ' + err | ||
88 | + ); | ||
89 | + } else { | ||
90 | + console.log( | ||
91 | + 'Successfully resized ' + srcBucket + '/' + srcKey + | ||
92 | + ' and uploaded to ' + dstBucket + '/' + dstKey | ||
93 | + ); | ||
94 | + } | ||
95 | + | ||
96 | + context.done(); | ||
97 | + } | ||
98 | + ); | ||
99 | +}; |
1 | +Copyright (c) 2010-2014 Caolan McMahon | ||
2 | + | ||
3 | +Permission is hereby granted, free of charge, to any person obtaining a copy | ||
4 | +of this software and associated documentation files (the "Software"), to deal | ||
5 | +in the Software without restriction, including without limitation the rights | ||
6 | +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
7 | +copies of the Software, and to permit persons to whom the Software is | ||
8 | +furnished to do so, subject to the following conditions: | ||
9 | + | ||
10 | +The above copyright notice and this permission notice shall be included in | ||
11 | +all copies or substantial portions of the Software. | ||
12 | + | ||
13 | +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
14 | +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
15 | +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
16 | +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
17 | +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
18 | +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
19 | +THE SOFTWARE. |
1 | +# Async.js | ||
2 | + | ||
3 | +[](https://travis-ci.org/caolan/async) | ||
4 | + | ||
5 | + | ||
6 | +Async is a utility module which provides straight-forward, powerful functions | ||
7 | +for working with asynchronous JavaScript. Although originally designed for | ||
8 | +use with [Node.js](http://nodejs.org), it can also be used directly in the | ||
9 | +browser. Also supports [component](https://github.com/component/component). | ||
10 | + | ||
11 | +Async provides around 20 functions that include the usual 'functional' | ||
12 | +suspects (`map`, `reduce`, `filter`, `each`…) as well as some common patterns | ||
13 | +for asynchronous control flow (`parallel`, `series`, `waterfall`…). All these | ||
14 | +functions assume you follow the Node.js convention of providing a single | ||
15 | +callback as the last argument of your `async` function. | ||
16 | + | ||
17 | + | ||
18 | +## Quick Examples | ||
19 | + | ||
20 | +```javascript | ||
21 | +async.map(['file1','file2','file3'], fs.stat, function(err, results){ | ||
22 | + // results is now an array of stats for each file | ||
23 | +}); | ||
24 | + | ||
25 | +async.filter(['file1','file2','file3'], fs.exists, function(results){ | ||
26 | + // results now equals an array of the existing files | ||
27 | +}); | ||
28 | + | ||
29 | +async.parallel([ | ||
30 | + function(){ ... }, | ||
31 | + function(){ ... } | ||
32 | +], callback); | ||
33 | + | ||
34 | +async.series([ | ||
35 | + function(){ ... }, | ||
36 | + function(){ ... } | ||
37 | +]); | ||
38 | +``` | ||
39 | + | ||
40 | +There are many more functions available so take a look at the docs below for a | ||
41 | +full list. This module aims to be comprehensive, so if you feel anything is | ||
42 | +missing please create a GitHub issue for it. | ||
43 | + | ||
44 | +## Common Pitfalls | ||
45 | + | ||
46 | +### Binding a context to an iterator | ||
47 | + | ||
48 | +This section is really about `bind`, not about `async`. If you are wondering how to | ||
49 | +make `async` execute your iterators in a given context, or are confused as to why | ||
50 | +a method of another library isn't working as an iterator, study this example: | ||
51 | + | ||
52 | +```js | ||
53 | +// Here is a simple object with an (unnecessarily roundabout) squaring method | ||
54 | +var AsyncSquaringLibrary = { | ||
55 | + squareExponent: 2, | ||
56 | + square: function(number, callback){ | ||
57 | + var result = Math.pow(number, this.squareExponent); | ||
58 | + setTimeout(function(){ | ||
59 | + callback(null, result); | ||
60 | + }, 200); | ||
61 | + } | ||
62 | +}; | ||
63 | + | ||
64 | +async.map([1, 2, 3], AsyncSquaringLibrary.square, function(err, result){ | ||
65 | + // result is [NaN, NaN, NaN] | ||
66 | + // This fails because the `this.squareExponent` expression in the square | ||
67 | + // function is not evaluated in the context of AsyncSquaringLibrary, and is | ||
68 | + // therefore undefined. | ||
69 | +}); | ||
70 | + | ||
71 | +async.map([1, 2, 3], AsyncSquaringLibrary.square.bind(AsyncSquaringLibrary), function(err, result){ | ||
72 | + // result is [1, 4, 9] | ||
73 | + // With the help of bind we can attach a context to the iterator before | ||
74 | + // passing it to async. Now the square function will be executed in its | ||
75 | + // 'home' AsyncSquaringLibrary context and the value of `this.squareExponent` | ||
76 | + // will be as expected. | ||
77 | +}); | ||
78 | +``` | ||
79 | + | ||
80 | +## Download | ||
81 | + | ||
82 | +The source is available for download from | ||
83 | +[GitHub](http://github.com/caolan/async). | ||
84 | +Alternatively, you can install using Node Package Manager (`npm`): | ||
85 | + | ||
86 | + npm install async | ||
87 | + | ||
88 | +__Development:__ [async.js](https://github.com/caolan/async/raw/master/lib/async.js) - 29.6kb Uncompressed | ||
89 | + | ||
90 | +## In the Browser | ||
91 | + | ||
92 | +So far it's been tested in IE6, IE7, IE8, FF3.6 and Chrome 5. | ||
93 | + | ||
94 | +Usage: | ||
95 | + | ||
96 | +```html | ||
97 | +<script type="text/javascript" src="async.js"></script> | ||
98 | +<script type="text/javascript"> | ||
99 | + | ||
100 | + async.map(data, asyncProcess, function(err, results){ | ||
101 | + alert(results); | ||
102 | + }); | ||
103 | + | ||
104 | +</script> | ||
105 | +``` | ||
106 | + | ||
107 | +## Documentation | ||
108 | + | ||
109 | +### Collections | ||
110 | + | ||
111 | +* [`each`](#each) | ||
112 | +* [`eachSeries`](#eachSeries) | ||
113 | +* [`eachLimit`](#eachLimit) | ||
114 | +* [`map`](#map) | ||
115 | +* [`mapSeries`](#mapSeries) | ||
116 | +* [`mapLimit`](#mapLimit) | ||
117 | +* [`filter`](#filter) | ||
118 | +* [`filterSeries`](#filterSeries) | ||
119 | +* [`reject`](#reject) | ||
120 | +* [`rejectSeries`](#rejectSeries) | ||
121 | +* [`reduce`](#reduce) | ||
122 | +* [`reduceRight`](#reduceRight) | ||
123 | +* [`detect`](#detect) | ||
124 | +* [`detectSeries`](#detectSeries) | ||
125 | +* [`sortBy`](#sortBy) | ||
126 | +* [`some`](#some) | ||
127 | +* [`every`](#every) | ||
128 | +* [`concat`](#concat) | ||
129 | +* [`concatSeries`](#concatSeries) | ||
130 | + | ||
131 | +### Control Flow | ||
132 | + | ||
133 | +* [`series`](#seriestasks-callback) | ||
134 | +* [`parallel`](#parallel) | ||
135 | +* [`parallelLimit`](#parallellimittasks-limit-callback) | ||
136 | +* [`whilst`](#whilst) | ||
137 | +* [`doWhilst`](#doWhilst) | ||
138 | +* [`until`](#until) | ||
139 | +* [`doUntil`](#doUntil) | ||
140 | +* [`forever`](#forever) | ||
141 | +* [`waterfall`](#waterfall) | ||
142 | +* [`compose`](#compose) | ||
143 | +* [`seq`](#seq) | ||
144 | +* [`applyEach`](#applyEach) | ||
145 | +* [`applyEachSeries`](#applyEachSeries) | ||
146 | +* [`queue`](#queue) | ||
147 | +* [`priorityQueue`](#priorityQueue) | ||
148 | +* [`cargo`](#cargo) | ||
149 | +* [`auto`](#auto) | ||
150 | +* [`retry`](#retry) | ||
151 | +* [`iterator`](#iterator) | ||
152 | +* [`apply`](#apply) | ||
153 | +* [`nextTick`](#nextTick) | ||
154 | +* [`times`](#times) | ||
155 | +* [`timesSeries`](#timesSeries) | ||
156 | + | ||
157 | +### Utils | ||
158 | + | ||
159 | +* [`memoize`](#memoize) | ||
160 | +* [`unmemoize`](#unmemoize) | ||
161 | +* [`log`](#log) | ||
162 | +* [`dir`](#dir) | ||
163 | +* [`noConflict`](#noConflict) | ||
164 | + | ||
165 | + | ||
166 | +## Collections | ||
167 | + | ||
168 | +<a name="forEach" /> | ||
169 | +<a name="each" /> | ||
170 | +### each(arr, iterator, callback) | ||
171 | + | ||
172 | +Applies the function `iterator` to each item in `arr`, in parallel. | ||
173 | +The `iterator` is called with an item from the list, and a callback for when it | ||
174 | +has finished. If the `iterator` passes an error to its `callback`, the main | ||
175 | +`callback` (for the `each` function) is immediately called with the error. | ||
176 | + | ||
177 | +Note, that since this function applies `iterator` to each item in parallel, | ||
178 | +there is no guarantee that the iterator functions will complete in order. | ||
179 | + | ||
180 | +__Arguments__ | ||
181 | + | ||
182 | +* `arr` - An array to iterate over. | ||
183 | +* `iterator(item, callback)` - A function to apply to each item in `arr`. | ||
184 | + The iterator is passed a `callback(err)` which must be called once it has | ||
185 | + completed. If no error has occured, the `callback` should be run without | ||
186 | + arguments or with an explicit `null` argument. | ||
187 | +* `callback(err)` - A callback which is called when all `iterator` functions | ||
188 | + have finished, or an error occurs. | ||
189 | + | ||
190 | +__Examples__ | ||
191 | + | ||
192 | + | ||
193 | +```js | ||
194 | +// assuming openFiles is an array of file names and saveFile is a function | ||
195 | +// to save the modified contents of that file: | ||
196 | + | ||
197 | +async.each(openFiles, saveFile, function(err){ | ||
198 | + // if any of the saves produced an error, err would equal that error | ||
199 | +}); | ||
200 | +``` | ||
201 | + | ||
202 | +```js | ||
203 | +// assuming openFiles is an array of file names | ||
204 | + | ||
205 | +async.each(openFiles, function( file, callback) { | ||
206 | + | ||
207 | + // Perform operation on file here. | ||
208 | + console.log('Processing file ' + file); | ||
209 | + | ||
210 | + if( file.length > 32 ) { | ||
211 | + console.log('This file name is too long'); | ||
212 | + callback('File name too long'); | ||
213 | + } else { | ||
214 | + // Do work to process file here | ||
215 | + console.log('File processed'); | ||
216 | + callback(); | ||
217 | + } | ||
218 | +}, function(err){ | ||
219 | + // if any of the file processing produced an error, err would equal that error | ||
220 | + if( err ) { | ||
221 | + // One of the iterations produced an error. | ||
222 | + // All processing will now stop. | ||
223 | + console.log('A file failed to process'); | ||
224 | + } else { | ||
225 | + console.log('All files have been processed successfully'); | ||
226 | + } | ||
227 | +}); | ||
228 | +``` | ||
229 | + | ||
230 | +--------------------------------------- | ||
231 | + | ||
232 | +<a name="forEachSeries" /> | ||
233 | +<a name="eachSeries" /> | ||
234 | +### eachSeries(arr, iterator, callback) | ||
235 | + | ||
236 | +The same as [`each`](#each), only `iterator` is applied to each item in `arr` in | ||
237 | +series. The next `iterator` is only called once the current one has completed. | ||
238 | +This means the `iterator` functions will complete in order. | ||
239 | + | ||
240 | + | ||
241 | +--------------------------------------- | ||
242 | + | ||
243 | +<a name="forEachLimit" /> | ||
244 | +<a name="eachLimit" /> | ||
245 | +### eachLimit(arr, limit, iterator, callback) | ||
246 | + | ||
247 | +The same as [`each`](#each), only no more than `limit` `iterator`s will be simultaneously | ||
248 | +running at any time. | ||
249 | + | ||
250 | +Note that the items in `arr` are not processed in batches, so there is no guarantee that | ||
251 | +the first `limit` `iterator` functions will complete before any others are started. | ||
252 | + | ||
253 | +__Arguments__ | ||
254 | + | ||
255 | +* `arr` - An array to iterate over. | ||
256 | +* `limit` - The maximum number of `iterator`s to run at any time. | ||
257 | +* `iterator(item, callback)` - A function to apply to each item in `arr`. | ||
258 | + The iterator is passed a `callback(err)` which must be called once it has | ||
259 | + completed. If no error has occured, the callback should be run without | ||
260 | + arguments or with an explicit `null` argument. | ||
261 | +* `callback(err)` - A callback which is called when all `iterator` functions | ||
262 | + have finished, or an error occurs. | ||
263 | + | ||
264 | +__Example__ | ||
265 | + | ||
266 | +```js | ||
267 | +// Assume documents is an array of JSON objects and requestApi is a | ||
268 | +// function that interacts with a rate-limited REST api. | ||
269 | + | ||
270 | +async.eachLimit(documents, 20, requestApi, function(err){ | ||
271 | + // if any of the saves produced an error, err would equal that error | ||
272 | +}); | ||
273 | +``` | ||
274 | + | ||
275 | +--------------------------------------- | ||
276 | + | ||
277 | +<a name="map" /> | ||
278 | +### map(arr, iterator, callback) | ||
279 | + | ||
280 | +Produces a new array of values by mapping each value in `arr` through | ||
281 | +the `iterator` function. The `iterator` is called with an item from `arr` and a | ||
282 | +callback for when it has finished processing. Each of these callback takes 2 arguments: | ||
283 | +an `error`, and the transformed item from `arr`. If `iterator` passes an error to this | ||
284 | +callback, the main `callback` (for the `map` function) is immediately called with the error. | ||
285 | + | ||
286 | +Note, that since this function applies the `iterator` to each item in parallel, | ||
287 | +there is no guarantee that the `iterator` functions will complete in order. | ||
288 | +However, the results array will be in the same order as the original `arr`. | ||
289 | + | ||
290 | +__Arguments__ | ||
291 | + | ||
292 | +* `arr` - An array to iterate over. | ||
293 | +* `iterator(item, callback)` - A function to apply to each item in `arr`. | ||
294 | + The iterator is passed a `callback(err, transformed)` which must be called once | ||
295 | + it has completed with an error (which can be `null`) and a transformed item. | ||
296 | +* `callback(err, results)` - A callback which is called when all `iterator` | ||
297 | + functions have finished, or an error occurs. Results is an array of the | ||
298 | + transformed items from the `arr`. | ||
299 | + | ||
300 | +__Example__ | ||
301 | + | ||
302 | +```js | ||
303 | +async.map(['file1','file2','file3'], fs.stat, function(err, results){ | ||
304 | + // results is now an array of stats for each file | ||
305 | +}); | ||
306 | +``` | ||
307 | + | ||
308 | +--------------------------------------- | ||
309 | + | ||
310 | +<a name="mapSeries" /> | ||
311 | +### mapSeries(arr, iterator, callback) | ||
312 | + | ||
313 | +The same as [`map`](#map), only the `iterator` is applied to each item in `arr` in | ||
314 | +series. The next `iterator` is only called once the current one has completed. | ||
315 | +The results array will be in the same order as the original. | ||
316 | + | ||
317 | + | ||
318 | +--------------------------------------- | ||
319 | + | ||
320 | +<a name="mapLimit" /> | ||
321 | +### mapLimit(arr, limit, iterator, callback) | ||
322 | + | ||
323 | +The same as [`map`](#map), only no more than `limit` `iterator`s will be simultaneously | ||
324 | +running at any time. | ||
325 | + | ||
326 | +Note that the items are not processed in batches, so there is no guarantee that | ||
327 | +the first `limit` `iterator` functions will complete before any others are started. | ||
328 | + | ||
329 | +__Arguments__ | ||
330 | + | ||
331 | +* `arr` - An array to iterate over. | ||
332 | +* `limit` - The maximum number of `iterator`s to run at any time. | ||
333 | +* `iterator(item, callback)` - A function to apply to each item in `arr`. | ||
334 | + The iterator is passed a `callback(err, transformed)` which must be called once | ||
335 | + it has completed with an error (which can be `null`) and a transformed item. | ||
336 | +* `callback(err, results)` - A callback which is called when all `iterator` | ||
337 | + calls have finished, or an error occurs. The result is an array of the | ||
338 | + transformed items from the original `arr`. | ||
339 | + | ||
340 | +__Example__ | ||
341 | + | ||
342 | +```js | ||
343 | +async.mapLimit(['file1','file2','file3'], 1, fs.stat, function(err, results){ | ||
344 | + // results is now an array of stats for each file | ||
345 | +}); | ||
346 | +``` | ||
347 | + | ||
348 | +--------------------------------------- | ||
349 | + | ||
350 | +<a name="select" /> | ||
351 | +<a name="filter" /> | ||
352 | +### filter(arr, iterator, callback) | ||
353 | + | ||
354 | +__Alias:__ `select` | ||
355 | + | ||
356 | +Returns a new array of all the values in `arr` which pass an async truth test. | ||
357 | +_The callback for each `iterator` call only accepts a single argument of `true` or | ||
358 | +`false`; it does not accept an error argument first!_ This is in-line with the | ||
359 | +way node libraries work with truth tests like `fs.exists`. This operation is | ||
360 | +performed in parallel, but the results array will be in the same order as the | ||
361 | +original. | ||
362 | + | ||
363 | +__Arguments__ | ||
364 | + | ||
365 | +* `arr` - An array to iterate over. | ||
366 | +* `iterator(item, callback)` - A truth test to apply to each item in `arr`. | ||
367 | + The `iterator` is passed a `callback(truthValue)`, which must be called with a | ||
368 | + boolean argument once it has completed. | ||
369 | +* `callback(results)` - A callback which is called after all the `iterator` | ||
370 | + functions have finished. | ||
371 | + | ||
372 | +__Example__ | ||
373 | + | ||
374 | +```js | ||
375 | +async.filter(['file1','file2','file3'], fs.exists, function(results){ | ||
376 | + // results now equals an array of the existing files | ||
377 | +}); | ||
378 | +``` | ||
379 | + | ||
380 | +--------------------------------------- | ||
381 | + | ||
382 | +<a name="selectSeries" /> | ||
383 | +<a name="filterSeries" /> | ||
384 | +### filterSeries(arr, iterator, callback) | ||
385 | + | ||
386 | +__Alias:__ `selectSeries` | ||
387 | + | ||
388 | +The same as [`filter`](#filter) only the `iterator` is applied to each item in `arr` in | ||
389 | +series. The next `iterator` is only called once the current one has completed. | ||
390 | +The results array will be in the same order as the original. | ||
391 | + | ||
392 | +--------------------------------------- | ||
393 | + | ||
394 | +<a name="reject" /> | ||
395 | +### reject(arr, iterator, callback) | ||
396 | + | ||
397 | +The opposite of [`filter`](#filter). Removes values that pass an `async` truth test. | ||
398 | + | ||
399 | +--------------------------------------- | ||
400 | + | ||
401 | +<a name="rejectSeries" /> | ||
402 | +### rejectSeries(arr, iterator, callback) | ||
403 | + | ||
404 | +The same as [`reject`](#reject), only the `iterator` is applied to each item in `arr` | ||
405 | +in series. | ||
406 | + | ||
407 | + | ||
408 | +--------------------------------------- | ||
409 | + | ||
410 | +<a name="reduce" /> | ||
411 | +### reduce(arr, memo, iterator, callback) | ||
412 | + | ||
413 | +__Aliases:__ `inject`, `foldl` | ||
414 | + | ||
415 | +Reduces `arr` into a single value using an async `iterator` to return | ||
416 | +each successive step. `memo` is the initial state of the reduction. | ||
417 | +This function only operates in series. | ||
418 | + | ||
419 | +For performance reasons, it may make sense to split a call to this function into | ||
420 | +a parallel map, and then use the normal `Array.prototype.reduce` on the results. | ||
421 | +This function is for situations where each step in the reduction needs to be async; | ||
422 | +if you can get the data before reducing it, then it's probably a good idea to do so. | ||
423 | + | ||
424 | +__Arguments__ | ||
425 | + | ||
426 | +* `arr` - An array to iterate over. | ||
427 | +* `memo` - The initial state of the reduction. | ||
428 | +* `iterator(memo, item, callback)` - A function applied to each item in the | ||
429 | + array to produce the next step in the reduction. The `iterator` is passed a | ||
430 | + `callback(err, reduction)` which accepts an optional error as its first | ||
431 | + argument, and the state of the reduction as the second. If an error is | ||
432 | + passed to the callback, the reduction is stopped and the main `callback` is | ||
433 | + immediately called with the error. | ||
434 | +* `callback(err, result)` - A callback which is called after all the `iterator` | ||
435 | + functions have finished. Result is the reduced value. | ||
436 | + | ||
437 | +__Example__ | ||
438 | + | ||
439 | +```js | ||
440 | +async.reduce([1,2,3], 0, function(memo, item, callback){ | ||
441 | + // pointless async: | ||
442 | + process.nextTick(function(){ | ||
443 | + callback(null, memo + item) | ||
444 | + }); | ||
445 | +}, function(err, result){ | ||
446 | + // result is now equal to the last value of memo, which is 6 | ||
447 | +}); | ||
448 | +``` | ||
449 | + | ||
450 | +--------------------------------------- | ||
451 | + | ||
452 | +<a name="reduceRight" /> | ||
453 | +### reduceRight(arr, memo, iterator, callback) | ||
454 | + | ||
455 | +__Alias:__ `foldr` | ||
456 | + | ||
457 | +Same as [`reduce`](#reduce), only operates on `arr` in reverse order. | ||
458 | + | ||
459 | + | ||
460 | +--------------------------------------- | ||
461 | + | ||
462 | +<a name="detect" /> | ||
463 | +### detect(arr, iterator, callback) | ||
464 | + | ||
465 | +Returns the first value in `arr` that passes an async truth test. The | ||
466 | +`iterator` is applied in parallel, meaning the first iterator to return `true` will | ||
467 | +fire the detect `callback` with that result. That means the result might not be | ||
468 | +the first item in the original `arr` (in terms of order) that passes the test. | ||
469 | + | ||
470 | +If order within the original `arr` is important, then look at [`detectSeries`](#detectSeries). | ||
471 | + | ||
472 | +__Arguments__ | ||
473 | + | ||
474 | +* `arr` - An array to iterate over. | ||
475 | +* `iterator(item, callback)` - A truth test to apply to each item in `arr`. | ||
476 | + The iterator is passed a `callback(truthValue)` which must be called with a | ||
477 | + boolean argument once it has completed. | ||
478 | +* `callback(result)` - A callback which is called as soon as any iterator returns | ||
479 | + `true`, or after all the `iterator` functions have finished. Result will be | ||
480 | + the first item in the array that passes the truth test (iterator) or the | ||
481 | + value `undefined` if none passed. | ||
482 | + | ||
483 | +__Example__ | ||
484 | + | ||
485 | +```js | ||
486 | +async.detect(['file1','file2','file3'], fs.exists, function(result){ | ||
487 | + // result now equals the first file in the list that exists | ||
488 | +}); | ||
489 | +``` | ||
490 | + | ||
491 | +--------------------------------------- | ||
492 | + | ||
493 | +<a name="detectSeries" /> | ||
494 | +### detectSeries(arr, iterator, callback) | ||
495 | + | ||
496 | +The same as [`detect`](#detect), only the `iterator` is applied to each item in `arr` | ||
497 | +in series. This means the result is always the first in the original `arr` (in | ||
498 | +terms of array order) that passes the truth test. | ||
499 | + | ||
500 | + | ||
501 | +--------------------------------------- | ||
502 | + | ||
503 | +<a name="sortBy" /> | ||
504 | +### sortBy(arr, iterator, callback) | ||
505 | + | ||
506 | +Sorts a list by the results of running each `arr` value through an async `iterator`. | ||
507 | + | ||
508 | +__Arguments__ | ||
509 | + | ||
510 | +* `arr` - An array to iterate over. | ||
511 | +* `iterator(item, callback)` - A function to apply to each item in `arr`. | ||
512 | + The iterator is passed a `callback(err, sortValue)` which must be called once it | ||
513 | + has completed with an error (which can be `null`) and a value to use as the sort | ||
514 | + criteria. | ||
515 | +* `callback(err, results)` - A callback which is called after all the `iterator` | ||
516 | + functions have finished, or an error occurs. Results is the items from | ||
517 | + the original `arr` sorted by the values returned by the `iterator` calls. | ||
518 | + | ||
519 | +__Example__ | ||
520 | + | ||
521 | +```js | ||
522 | +async.sortBy(['file1','file2','file3'], function(file, callback){ | ||
523 | + fs.stat(file, function(err, stats){ | ||
524 | + callback(err, stats.mtime); | ||
525 | + }); | ||
526 | +}, function(err, results){ | ||
527 | + // results is now the original array of files sorted by | ||
528 | + // modified date | ||
529 | +}); | ||
530 | +``` | ||
531 | + | ||
532 | +__Sort Order__ | ||
533 | + | ||
534 | +By modifying the callback parameter the sorting order can be influenced: | ||
535 | + | ||
536 | +```js | ||
537 | +//ascending order | ||
538 | +async.sortBy([1,9,3,5], function(x, callback){ | ||
539 | + callback(err, x); | ||
540 | +}, function(err,result){ | ||
541 | + //result callback | ||
542 | +} ); | ||
543 | + | ||
544 | +//descending order | ||
545 | +async.sortBy([1,9,3,5], function(x, callback){ | ||
546 | + callback(err, x*-1); //<- x*-1 instead of x, turns the order around | ||
547 | +}, function(err,result){ | ||
548 | + //result callback | ||
549 | +} ); | ||
550 | +``` | ||
551 | + | ||
552 | +--------------------------------------- | ||
553 | + | ||
554 | +<a name="some" /> | ||
555 | +### some(arr, iterator, callback) | ||
556 | + | ||
557 | +__Alias:__ `any` | ||
558 | + | ||
559 | +Returns `true` if at least one element in the `arr` satisfies an async test. | ||
560 | +_The callback for each iterator call only accepts a single argument of `true` or | ||
561 | +`false`; it does not accept an error argument first!_ This is in-line with the | ||
562 | +way node libraries work with truth tests like `fs.exists`. Once any iterator | ||
563 | +call returns `true`, the main `callback` is immediately called. | ||
564 | + | ||
565 | +__Arguments__ | ||
566 | + | ||
567 | +* `arr` - An array to iterate over. | ||
568 | +* `iterator(item, callback)` - A truth test to apply to each item in the array | ||
569 | + in parallel. The iterator is passed a callback(truthValue) which must be | ||
570 | + called with a boolean argument once it has completed. | ||
571 | +* `callback(result)` - A callback which is called as soon as any iterator returns | ||
572 | + `true`, or after all the iterator functions have finished. Result will be | ||
573 | + either `true` or `false` depending on the values of the async tests. | ||
574 | + | ||
575 | +__Example__ | ||
576 | + | ||
577 | +```js | ||
578 | +async.some(['file1','file2','file3'], fs.exists, function(result){ | ||
579 | + // if result is true then at least one of the files exists | ||
580 | +}); | ||
581 | +``` | ||
582 | + | ||
583 | +--------------------------------------- | ||
584 | + | ||
585 | +<a name="every" /> | ||
586 | +### every(arr, iterator, callback) | ||
587 | + | ||
588 | +__Alias:__ `all` | ||
589 | + | ||
590 | +Returns `true` if every element in `arr` satisfies an async test. | ||
591 | +_The callback for each `iterator` call only accepts a single argument of `true` or | ||
592 | +`false`; it does not accept an error argument first!_ This is in-line with the | ||
593 | +way node libraries work with truth tests like `fs.exists`. | ||
594 | + | ||
595 | +__Arguments__ | ||
596 | + | ||
597 | +* `arr` - An array to iterate over. | ||
598 | +* `iterator(item, callback)` - A truth test to apply to each item in the array | ||
599 | + in parallel. The iterator is passed a callback(truthValue) which must be | ||
600 | + called with a boolean argument once it has completed. | ||
601 | +* `callback(result)` - A callback which is called after all the `iterator` | ||
602 | + functions have finished. Result will be either `true` or `false` depending on | ||
603 | + the values of the async tests. | ||
604 | + | ||
605 | +__Example__ | ||
606 | + | ||
607 | +```js | ||
608 | +async.every(['file1','file2','file3'], fs.exists, function(result){ | ||
609 | + // if result is true then every file exists | ||
610 | +}); | ||
611 | +``` | ||
612 | + | ||
613 | +--------------------------------------- | ||
614 | + | ||
615 | +<a name="concat" /> | ||
616 | +### concat(arr, iterator, callback) | ||
617 | + | ||
618 | +Applies `iterator` to each item in `arr`, concatenating the results. Returns the | ||
619 | +concatenated list. The `iterator`s are called in parallel, and the results are | ||
620 | +concatenated as they return. There is no guarantee that the results array will | ||
621 | +be returned in the original order of `arr` passed to the `iterator` function. | ||
622 | + | ||
623 | +__Arguments__ | ||
624 | + | ||
625 | +* `arr` - An array to iterate over. | ||
626 | +* `iterator(item, callback)` - A function to apply to each item in `arr`. | ||
627 | + The iterator is passed a `callback(err, results)` which must be called once it | ||
628 | + has completed with an error (which can be `null`) and an array of results. | ||
629 | +* `callback(err, results)` - A callback which is called after all the `iterator` | ||
630 | + functions have finished, or an error occurs. Results is an array containing | ||
631 | + the concatenated results of the `iterator` function. | ||
632 | + | ||
633 | +__Example__ | ||
634 | + | ||
635 | +```js | ||
636 | +async.concat(['dir1','dir2','dir3'], fs.readdir, function(err, files){ | ||
637 | + // files is now a list of filenames that exist in the 3 directories | ||
638 | +}); | ||
639 | +``` | ||
640 | + | ||
641 | +--------------------------------------- | ||
642 | + | ||
643 | +<a name="concatSeries" /> | ||
644 | +### concatSeries(arr, iterator, callback) | ||
645 | + | ||
646 | +Same as [`concat`](#concat), but executes in series instead of parallel. | ||
647 | + | ||
648 | + | ||
649 | +## Control Flow | ||
650 | + | ||
651 | +<a name="series" /> | ||
652 | +### series(tasks, [callback]) | ||
653 | + | ||
654 | +Run the functions in the `tasks` array in series, each one running once the previous | ||
655 | +function has completed. If any functions in the series pass an error to its | ||
656 | +callback, no more functions are run, and `callback` is immediately called with the value of the error. | ||
657 | +Otherwise, `callback` receives an array of results when `tasks` have completed. | ||
658 | + | ||
659 | +It is also possible to use an object instead of an array. Each property will be | ||
660 | +run as a function, and the results will be passed to the final `callback` as an object | ||
661 | +instead of an array. This can be a more readable way of handling results from | ||
662 | +[`series`](#series). | ||
663 | + | ||
664 | +**Note** that while many implementations preserve the order of object properties, the | ||
665 | +[ECMAScript Language Specifcation](http://www.ecma-international.org/ecma-262/5.1/#sec-8.6) | ||
666 | +explicitly states that | ||
667 | + | ||
668 | +> The mechanics and order of enumerating the properties is not specified. | ||
669 | + | ||
670 | +So if you rely on the order in which your series of functions are executed, and want | ||
671 | +this to work on all platforms, consider using an array. | ||
672 | + | ||
673 | +__Arguments__ | ||
674 | + | ||
675 | +* `tasks` - An array or object containing functions to run, each function is passed | ||
676 | + a `callback(err, result)` it must call on completion with an error `err` (which can | ||
677 | + be `null`) and an optional `result` value. | ||
678 | +* `callback(err, results)` - An optional callback to run once all the functions | ||
679 | + have completed. This function gets a results array (or object) containing all | ||
680 | + the result arguments passed to the `task` callbacks. | ||
681 | + | ||
682 | +__Example__ | ||
683 | + | ||
684 | +```js | ||
685 | +async.series([ | ||
686 | + function(callback){ | ||
687 | + // do some stuff ... | ||
688 | + callback(null, 'one'); | ||
689 | + }, | ||
690 | + function(callback){ | ||
691 | + // do some more stuff ... | ||
692 | + callback(null, 'two'); | ||
693 | + } | ||
694 | +], | ||
695 | +// optional callback | ||
696 | +function(err, results){ | ||
697 | + // results is now equal to ['one', 'two'] | ||
698 | +}); | ||
699 | + | ||
700 | + | ||
701 | +// an example using an object instead of an array | ||
702 | +async.series({ | ||
703 | + one: function(callback){ | ||
704 | + setTimeout(function(){ | ||
705 | + callback(null, 1); | ||
706 | + }, 200); | ||
707 | + }, | ||
708 | + two: function(callback){ | ||
709 | + setTimeout(function(){ | ||
710 | + callback(null, 2); | ||
711 | + }, 100); | ||
712 | + } | ||
713 | +}, | ||
714 | +function(err, results) { | ||
715 | + // results is now equal to: {one: 1, two: 2} | ||
716 | +}); | ||
717 | +``` | ||
718 | + | ||
719 | +--------------------------------------- | ||
720 | + | ||
721 | +<a name="parallel" /> | ||
722 | +### parallel(tasks, [callback]) | ||
723 | + | ||
724 | +Run the `tasks` array of functions in parallel, without waiting until the previous | ||
725 | +function has completed. If any of the functions pass an error to its | ||
726 | +callback, the main `callback` is immediately called with the value of the error. | ||
727 | +Once the `tasks` have completed, the results are passed to the final `callback` as an | ||
728 | +array. | ||
729 | + | ||
730 | +It is also possible to use an object instead of an array. Each property will be | ||
731 | +run as a function and the results will be passed to the final `callback` as an object | ||
732 | +instead of an array. This can be a more readable way of handling results from | ||
733 | +[`parallel`](#parallel). | ||
734 | + | ||
735 | + | ||
736 | +__Arguments__ | ||
737 | + | ||
738 | +* `tasks` - An array or object containing functions to run. Each function is passed | ||
739 | + a `callback(err, result)` which it must call on completion with an error `err` | ||
740 | + (which can be `null`) and an optional `result` value. | ||
741 | +* `callback(err, results)` - An optional callback to run once all the functions | ||
742 | + have completed. This function gets a results array (or object) containing all | ||
743 | + the result arguments passed to the task callbacks. | ||
744 | + | ||
745 | +__Example__ | ||
746 | + | ||
747 | +```js | ||
748 | +async.parallel([ | ||
749 | + function(callback){ | ||
750 | + setTimeout(function(){ | ||
751 | + callback(null, 'one'); | ||
752 | + }, 200); | ||
753 | + }, | ||
754 | + function(callback){ | ||
755 | + setTimeout(function(){ | ||
756 | + callback(null, 'two'); | ||
757 | + }, 100); | ||
758 | + } | ||
759 | +], | ||
760 | +// optional callback | ||
761 | +function(err, results){ | ||
762 | + // the results array will equal ['one','two'] even though | ||
763 | + // the second function had a shorter timeout. | ||
764 | +}); | ||
765 | + | ||
766 | + | ||
767 | +// an example using an object instead of an array | ||
768 | +async.parallel({ | ||
769 | + one: function(callback){ | ||
770 | + setTimeout(function(){ | ||
771 | + callback(null, 1); | ||
772 | + }, 200); | ||
773 | + }, | ||
774 | + two: function(callback){ | ||
775 | + setTimeout(function(){ | ||
776 | + callback(null, 2); | ||
777 | + }, 100); | ||
778 | + } | ||
779 | +}, | ||
780 | +function(err, results) { | ||
781 | + // results is now equals to: {one: 1, two: 2} | ||
782 | +}); | ||
783 | +``` | ||
784 | + | ||
785 | +--------------------------------------- | ||
786 | + | ||
787 | +<a name="parallelLimit" /> | ||
788 | +### parallelLimit(tasks, limit, [callback]) | ||
789 | + | ||
790 | +The same as [`parallel`](#parallel), only `tasks` are executed in parallel | ||
791 | +with a maximum of `limit` tasks executing at any time. | ||
792 | + | ||
793 | +Note that the `tasks` are not executed in batches, so there is no guarantee that | ||
794 | +the first `limit` tasks will complete before any others are started. | ||
795 | + | ||
796 | +__Arguments__ | ||
797 | + | ||
798 | +* `tasks` - An array or object containing functions to run, each function is passed | ||
799 | + a `callback(err, result)` it must call on completion with an error `err` (which can | ||
800 | + be `null`) and an optional `result` value. | ||
801 | +* `limit` - The maximum number of `tasks` to run at any time. | ||
802 | +* `callback(err, results)` - An optional callback to run once all the functions | ||
803 | + have completed. This function gets a results array (or object) containing all | ||
804 | + the result arguments passed to the `task` callbacks. | ||
805 | + | ||
806 | +--------------------------------------- | ||
807 | + | ||
808 | +<a name="whilst" /> | ||
809 | +### whilst(test, fn, callback) | ||
810 | + | ||
811 | +Repeatedly call `fn`, while `test` returns `true`. Calls `callback` when stopped, | ||
812 | +or an error occurs. | ||
813 | + | ||
814 | +__Arguments__ | ||
815 | + | ||
816 | +* `test()` - synchronous truth test to perform before each execution of `fn`. | ||
817 | +* `fn(callback)` - A function which is called each time `test` passes. The function is | ||
818 | + passed a `callback(err)`, which must be called once it has completed with an | ||
819 | + optional `err` argument. | ||
820 | +* `callback(err)` - A callback which is called after the test fails and repeated | ||
821 | + execution of `fn` has stopped. | ||
822 | + | ||
823 | +__Example__ | ||
824 | + | ||
825 | +```js | ||
826 | +var count = 0; | ||
827 | + | ||
828 | +async.whilst( | ||
829 | + function () { return count < 5; }, | ||
830 | + function (callback) { | ||
831 | + count++; | ||
832 | + setTimeout(callback, 1000); | ||
833 | + }, | ||
834 | + function (err) { | ||
835 | + // 5 seconds have passed | ||
836 | + } | ||
837 | +); | ||
838 | +``` | ||
839 | + | ||
840 | +--------------------------------------- | ||
841 | + | ||
842 | +<a name="doWhilst" /> | ||
843 | +### doWhilst(fn, test, callback) | ||
844 | + | ||
845 | +The post-check version of [`whilst`](#whilst). To reflect the difference in | ||
846 | +the order of operations, the arguments `test` and `fn` are switched. | ||
847 | + | ||
848 | +`doWhilst` is to `whilst` as `do while` is to `while` in plain JavaScript. | ||
849 | + | ||
850 | +--------------------------------------- | ||
851 | + | ||
852 | +<a name="until" /> | ||
853 | +### until(test, fn, callback) | ||
854 | + | ||
855 | +Repeatedly call `fn` until `test` returns `true`. Calls `callback` when stopped, | ||
856 | +or an error occurs. | ||
857 | + | ||
858 | +The inverse of [`whilst`](#whilst). | ||
859 | + | ||
860 | +--------------------------------------- | ||
861 | + | ||
862 | +<a name="doUntil" /> | ||
863 | +### doUntil(fn, test, callback) | ||
864 | + | ||
865 | +Like [`doWhilst`](#doWhilst), except the `test` is inverted. Note the argument ordering differs from `until`. | ||
866 | + | ||
867 | +--------------------------------------- | ||
868 | + | ||
869 | +<a name="forever" /> | ||
870 | +### forever(fn, errback) | ||
871 | + | ||
872 | +Calls the asynchronous function `fn` with a callback parameter that allows it to | ||
873 | +call itself again, in series, indefinitely. | ||
874 | + | ||
875 | +If an error is passed to the callback then `errback` is called with the | ||
876 | +error, and execution stops, otherwise it will never be called. | ||
877 | + | ||
878 | +```js | ||
879 | +async.forever( | ||
880 | + function(next) { | ||
881 | + // next is suitable for passing to things that need a callback(err [, whatever]); | ||
882 | + // it will result in this function being called again. | ||
883 | + }, | ||
884 | + function(err) { | ||
885 | + // if next is called with a value in its first parameter, it will appear | ||
886 | + // in here as 'err', and execution will stop. | ||
887 | + } | ||
888 | +); | ||
889 | +``` | ||
890 | + | ||
891 | +--------------------------------------- | ||
892 | + | ||
893 | +<a name="waterfall" /> | ||
894 | +### waterfall(tasks, [callback]) | ||
895 | + | ||
896 | +Runs the `tasks` array of functions in series, each passing their results to the next in | ||
897 | +the array. However, if any of the `tasks` pass an error to their own callback, the | ||
898 | +next function is not executed, and the main `callback` is immediately called with | ||
899 | +the error. | ||
900 | + | ||
901 | +__Arguments__ | ||
902 | + | ||
903 | +* `tasks` - An array of functions to run, each function is passed a | ||
904 | + `callback(err, result1, result2, ...)` it must call on completion. The first | ||
905 | + argument is an error (which can be `null`) and any further arguments will be | ||
906 | + passed as arguments in order to the next task. | ||
907 | +* `callback(err, [results])` - An optional callback to run once all the functions | ||
908 | + have completed. This will be passed the results of the last task's callback. | ||
909 | + | ||
910 | + | ||
911 | + | ||
912 | +__Example__ | ||
913 | + | ||
914 | +```js | ||
915 | +async.waterfall([ | ||
916 | + function(callback){ | ||
917 | + callback(null, 'one', 'two'); | ||
918 | + }, | ||
919 | + function(arg1, arg2, callback){ | ||
920 | + // arg1 now equals 'one' and arg2 now equals 'two' | ||
921 | + callback(null, 'three'); | ||
922 | + }, | ||
923 | + function(arg1, callback){ | ||
924 | + // arg1 now equals 'three' | ||
925 | + callback(null, 'done'); | ||
926 | + } | ||
927 | +], function (err, result) { | ||
928 | + // result now equals 'done' | ||
929 | +}); | ||
930 | +``` | ||
931 | + | ||
932 | +--------------------------------------- | ||
933 | +<a name="compose" /> | ||
934 | +### compose(fn1, fn2...) | ||
935 | + | ||
936 | +Creates a function which is a composition of the passed asynchronous | ||
937 | +functions. Each function consumes the return value of the function that | ||
938 | +follows. Composing functions `f()`, `g()`, and `h()` would produce the result of | ||
939 | +`f(g(h()))`, only this version uses callbacks to obtain the return values. | ||
940 | + | ||
941 | +Each function is executed with the `this` binding of the composed function. | ||
942 | + | ||
943 | +__Arguments__ | ||
944 | + | ||
945 | +* `functions...` - the asynchronous functions to compose | ||
946 | + | ||
947 | + | ||
948 | +__Example__ | ||
949 | + | ||
950 | +```js | ||
951 | +function add1(n, callback) { | ||
952 | + setTimeout(function () { | ||
953 | + callback(null, n + 1); | ||
954 | + }, 10); | ||
955 | +} | ||
956 | + | ||
957 | +function mul3(n, callback) { | ||
958 | + setTimeout(function () { | ||
959 | + callback(null, n * 3); | ||
960 | + }, 10); | ||
961 | +} | ||
962 | + | ||
963 | +var add1mul3 = async.compose(mul3, add1); | ||
964 | + | ||
965 | +add1mul3(4, function (err, result) { | ||
966 | + // result now equals 15 | ||
967 | +}); | ||
968 | +``` | ||
969 | + | ||
970 | +--------------------------------------- | ||
971 | +<a name="seq" /> | ||
972 | +### seq(fn1, fn2...) | ||
973 | + | ||
974 | +Version of the compose function that is more natural to read. | ||
975 | +Each following function consumes the return value of the latter function. | ||
976 | + | ||
977 | +Each function is executed with the `this` binding of the composed function. | ||
978 | + | ||
979 | +__Arguments__ | ||
980 | + | ||
981 | +* functions... - the asynchronous functions to compose | ||
982 | + | ||
983 | + | ||
984 | +__Example__ | ||
985 | + | ||
986 | +```js | ||
987 | +// Requires lodash (or underscore), express3 and dresende's orm2. | ||
988 | +// Part of an app, that fetches cats of the logged user. | ||
989 | +// This example uses `seq` function to avoid overnesting and error | ||
990 | +// handling clutter. | ||
991 | +app.get('/cats', function(request, response) { | ||
992 | + function handleError(err, data, callback) { | ||
993 | + if (err) { | ||
994 | + console.error(err); | ||
995 | + response.json({ status: 'error', message: err.message }); | ||
996 | + } | ||
997 | + else { | ||
998 | + callback(data); | ||
999 | + } | ||
1000 | + } | ||
1001 | + var User = request.models.User; | ||
1002 | + async.seq( | ||
1003 | + _.bind(User.get, User), // 'User.get' has signature (id, callback(err, data)) | ||
1004 | + handleError, | ||
1005 | + function(user, fn) { | ||
1006 | + user.getCats(fn); // 'getCats' has signature (callback(err, data)) | ||
1007 | + }, | ||
1008 | + handleError, | ||
1009 | + function(cats) { | ||
1010 | + response.json({ status: 'ok', message: 'Cats found', data: cats }); | ||
1011 | + } | ||
1012 | + )(req.session.user_id); | ||
1013 | + } | ||
1014 | +}); | ||
1015 | +``` | ||
1016 | + | ||
1017 | +--------------------------------------- | ||
1018 | +<a name="applyEach" /> | ||
1019 | +### applyEach(fns, args..., callback) | ||
1020 | + | ||
1021 | +Applies the provided arguments to each function in the array, calling | ||
1022 | +`callback` after all functions have completed. If you only provide the first | ||
1023 | +argument, then it will return a function which lets you pass in the | ||
1024 | +arguments as if it were a single function call. | ||
1025 | + | ||
1026 | +__Arguments__ | ||
1027 | + | ||
1028 | +* `fns` - the asynchronous functions to all call with the same arguments | ||
1029 | +* `args...` - any number of separate arguments to pass to the function | ||
1030 | +* `callback` - the final argument should be the callback, called when all | ||
1031 | + functions have completed processing | ||
1032 | + | ||
1033 | + | ||
1034 | +__Example__ | ||
1035 | + | ||
1036 | +```js | ||
1037 | +async.applyEach([enableSearch, updateSchema], 'bucket', callback); | ||
1038 | + | ||
1039 | +// partial application example: | ||
1040 | +async.each( | ||
1041 | + buckets, | ||
1042 | + async.applyEach([enableSearch, updateSchema]), | ||
1043 | + callback | ||
1044 | +); | ||
1045 | +``` | ||
1046 | + | ||
1047 | +--------------------------------------- | ||
1048 | + | ||
1049 | +<a name="applyEachSeries" /> | ||
1050 | +### applyEachSeries(arr, iterator, callback) | ||
1051 | + | ||
1052 | +The same as [`applyEach`](#applyEach) only the functions are applied in series. | ||
1053 | + | ||
1054 | +--------------------------------------- | ||
1055 | + | ||
1056 | +<a name="queue" /> | ||
1057 | +### queue(worker, concurrency) | ||
1058 | + | ||
1059 | +Creates a `queue` object with the specified `concurrency`. Tasks added to the | ||
1060 | +`queue` are processed in parallel (up to the `concurrency` limit). If all | ||
1061 | +`worker`s are in progress, the task is queued until one becomes available. | ||
1062 | +Once a `worker` completes a `task`, that `task`'s callback is called. | ||
1063 | + | ||
1064 | +__Arguments__ | ||
1065 | + | ||
1066 | +* `worker(task, callback)` - An asynchronous function for processing a queued | ||
1067 | + task, which must call its `callback(err)` argument when finished, with an | ||
1068 | + optional `error` as an argument. | ||
1069 | +* `concurrency` - An `integer` for determining how many `worker` functions should be | ||
1070 | + run in parallel. | ||
1071 | + | ||
1072 | +__Queue objects__ | ||
1073 | + | ||
1074 | +The `queue` object returned by this function has the following properties and | ||
1075 | +methods: | ||
1076 | + | ||
1077 | +* `length()` - a function returning the number of items waiting to be processed. | ||
1078 | +* `started` - a function returning whether or not any items have been pushed and processed by the queue | ||
1079 | +* `running()` - a function returning the number of items currently being processed. | ||
1080 | +* `idle()` - a function returning false if there are items waiting or being processed, or true if not. | ||
1081 | +* `concurrency` - an integer for determining how many `worker` functions should be | ||
1082 | + run in parallel. This property can be changed after a `queue` is created to | ||
1083 | + alter the concurrency on-the-fly. | ||
1084 | +* `push(task, [callback])` - add a new task to the `queue`. Calls `callback` once | ||
1085 | + the `worker` has finished processing the task. Instead of a single task, a `tasks` array | ||
1086 | + can be submitted. The respective callback is used for every task in the list. | ||
1087 | +* `unshift(task, [callback])` - add a new task to the front of the `queue`. | ||
1088 | +* `saturated` - a callback that is called when the `queue` length hits the `concurrency` limit, | ||
1089 | + and further tasks will be queued. | ||
1090 | +* `empty` - a callback that is called when the last item from the `queue` is given to a `worker`. | ||
1091 | +* `drain` - a callback that is called when the last item from the `queue` has returned from the `worker`. | ||
1092 | +* `paused` - a boolean for determining whether the queue is in a paused state | ||
1093 | +* `pause()` - a function that pauses the processing of tasks until `resume()` is called. | ||
1094 | +* `resume()` - a function that resumes the processing of queued tasks when the queue is paused. | ||
1095 | +* `kill()` - a function that empties remaining tasks from the queue forcing it to go idle. | ||
1096 | + | ||
1097 | +__Example__ | ||
1098 | + | ||
1099 | +```js | ||
1100 | +// create a queue object with concurrency 2 | ||
1101 | + | ||
1102 | +var q = async.queue(function (task, callback) { | ||
1103 | + console.log('hello ' + task.name); | ||
1104 | + callback(); | ||
1105 | +}, 2); | ||
1106 | + | ||
1107 | + | ||
1108 | +// assign a callback | ||
1109 | +q.drain = function() { | ||
1110 | + console.log('all items have been processed'); | ||
1111 | +} | ||
1112 | + | ||
1113 | +// add some items to the queue | ||
1114 | + | ||
1115 | +q.push({name: 'foo'}, function (err) { | ||
1116 | + console.log('finished processing foo'); | ||
1117 | +}); | ||
1118 | +q.push({name: 'bar'}, function (err) { | ||
1119 | + console.log('finished processing bar'); | ||
1120 | +}); | ||
1121 | + | ||
1122 | +// add some items to the queue (batch-wise) | ||
1123 | + | ||
1124 | +q.push([{name: 'baz'},{name: 'bay'},{name: 'bax'}], function (err) { | ||
1125 | + console.log('finished processing bar'); | ||
1126 | +}); | ||
1127 | + | ||
1128 | +// add some items to the front of the queue | ||
1129 | + | ||
1130 | +q.unshift({name: 'bar'}, function (err) { | ||
1131 | + console.log('finished processing bar'); | ||
1132 | +}); | ||
1133 | +``` | ||
1134 | + | ||
1135 | + | ||
1136 | +--------------------------------------- | ||
1137 | + | ||
1138 | +<a name="priorityQueue" /> | ||
1139 | +### priorityQueue(worker, concurrency) | ||
1140 | + | ||
1141 | +The same as [`queue`](#queue) only tasks are assigned a priority and completed in ascending priority order. There are two differences between `queue` and `priorityQueue` objects: | ||
1142 | + | ||
1143 | +* `push(task, priority, [callback])` - `priority` should be a number. If an array of | ||
1144 | + `tasks` is given, all tasks will be assigned the same priority. | ||
1145 | +* The `unshift` method was removed. | ||
1146 | + | ||
1147 | +--------------------------------------- | ||
1148 | + | ||
1149 | +<a name="cargo" /> | ||
1150 | +### cargo(worker, [payload]) | ||
1151 | + | ||
1152 | +Creates a `cargo` object with the specified payload. Tasks added to the | ||
1153 | +cargo will be processed altogether (up to the `payload` limit). If the | ||
1154 | +`worker` is in progress, the task is queued until it becomes available. Once | ||
1155 | +the `worker` has completed some tasks, each callback of those tasks is called. | ||
1156 | +Check out [this animation](https://camo.githubusercontent.com/6bbd36f4cf5b35a0f11a96dcd2e97711ffc2fb37/68747470733a2f2f662e636c6f75642e6769746875622e636f6d2f6173736574732f313637363837312f36383130382f62626330636662302d356632392d313165322d393734662d3333393763363464633835382e676966) for how `cargo` and `queue` work. | ||
1157 | + | ||
1158 | +While [queue](#queue) passes only one task to one of a group of workers | ||
1159 | +at a time, cargo passes an array of tasks to a single worker, repeating | ||
1160 | +when the worker is finished. | ||
1161 | + | ||
1162 | +__Arguments__ | ||
1163 | + | ||
1164 | +* `worker(tasks, callback)` - An asynchronous function for processing an array of | ||
1165 | + queued tasks, which must call its `callback(err)` argument when finished, with | ||
1166 | + an optional `err` argument. | ||
1167 | +* `payload` - An optional `integer` for determining how many tasks should be | ||
1168 | + processed per round; if omitted, the default is unlimited. | ||
1169 | + | ||
1170 | +__Cargo objects__ | ||
1171 | + | ||
1172 | +The `cargo` object returned by this function has the following properties and | ||
1173 | +methods: | ||
1174 | + | ||
1175 | +* `length()` - A function returning the number of items waiting to be processed. | ||
1176 | +* `payload` - An `integer` for determining how many tasks should be | ||
1177 | + process per round. This property can be changed after a `cargo` is created to | ||
1178 | + alter the payload on-the-fly. | ||
1179 | +* `push(task, [callback])` - Adds `task` to the `queue`. The callback is called | ||
1180 | + once the `worker` has finished processing the task. Instead of a single task, an array of `tasks` | ||
1181 | + can be submitted. The respective callback is used for every task in the list. | ||
1182 | +* `saturated` - A callback that is called when the `queue.length()` hits the concurrency and further tasks will be queued. | ||
1183 | +* `empty` - A callback that is called when the last item from the `queue` is given to a `worker`. | ||
1184 | +* `drain` - A callback that is called when the last item from the `queue` has returned from the `worker`. | ||
1185 | + | ||
1186 | +__Example__ | ||
1187 | + | ||
1188 | +```js | ||
1189 | +// create a cargo object with payload 2 | ||
1190 | + | ||
1191 | +var cargo = async.cargo(function (tasks, callback) { | ||
1192 | + for(var i=0; i<tasks.length; i++){ | ||
1193 | + console.log('hello ' + tasks[i].name); | ||
1194 | + } | ||
1195 | + callback(); | ||
1196 | +}, 2); | ||
1197 | + | ||
1198 | + | ||
1199 | +// add some items | ||
1200 | + | ||
1201 | +cargo.push({name: 'foo'}, function (err) { | ||
1202 | + console.log('finished processing foo'); | ||
1203 | +}); | ||
1204 | +cargo.push({name: 'bar'}, function (err) { | ||
1205 | + console.log('finished processing bar'); | ||
1206 | +}); | ||
1207 | +cargo.push({name: 'baz'}, function (err) { | ||
1208 | + console.log('finished processing baz'); | ||
1209 | +}); | ||
1210 | +``` | ||
1211 | + | ||
1212 | +--------------------------------------- | ||
1213 | + | ||
1214 | +<a name="auto" /> | ||
1215 | +### auto(tasks, [callback]) | ||
1216 | + | ||
1217 | +Determines the best order for running the functions in `tasks`, based on their | ||
1218 | +requirements. Each function can optionally depend on other functions being completed | ||
1219 | +first, and each function is run as soon as its requirements are satisfied. | ||
1220 | + | ||
1221 | +If any of the functions pass an error to their callback, it will not | ||
1222 | +complete (so any other functions depending on it will not run), and the main | ||
1223 | +`callback` is immediately called with the error. Functions also receive an | ||
1224 | +object containing the results of functions which have completed so far. | ||
1225 | + | ||
1226 | +Note, all functions are called with a `results` object as a second argument, | ||
1227 | +so it is unsafe to pass functions in the `tasks` object which cannot handle the | ||
1228 | +extra argument. | ||
1229 | + | ||
1230 | +For example, this snippet of code: | ||
1231 | + | ||
1232 | +```js | ||
1233 | +async.auto({ | ||
1234 | + readData: async.apply(fs.readFile, 'data.txt', 'utf-8') | ||
1235 | +}, callback); | ||
1236 | +``` | ||
1237 | + | ||
1238 | +will have the effect of calling `readFile` with the results object as the last | ||
1239 | +argument, which will fail: | ||
1240 | + | ||
1241 | +```js | ||
1242 | +fs.readFile('data.txt', 'utf-8', cb, {}); | ||
1243 | +``` | ||
1244 | + | ||
1245 | +Instead, wrap the call to `readFile` in a function which does not forward the | ||
1246 | +`results` object: | ||
1247 | + | ||
1248 | +```js | ||
1249 | +async.auto({ | ||
1250 | + readData: function(cb, results){ | ||
1251 | + fs.readFile('data.txt', 'utf-8', cb); | ||
1252 | + } | ||
1253 | +}, callback); | ||
1254 | +``` | ||
1255 | + | ||
1256 | +__Arguments__ | ||
1257 | + | ||
1258 | +* `tasks` - An object. Each of its properties is either a function or an array of | ||
1259 | + requirements, with the function itself the last item in the array. The object's key | ||
1260 | + of a property serves as the name of the task defined by that property, | ||
1261 | + i.e. can be used when specifying requirements for other tasks. | ||
1262 | + The function receives two arguments: (1) a `callback(err, result)` which must be | ||
1263 | + called when finished, passing an `error` (which can be `null`) and the result of | ||
1264 | + the function's execution, and (2) a `results` object, containing the results of | ||
1265 | + the previously executed functions. | ||
1266 | +* `callback(err, results)` - An optional callback which is called when all the | ||
1267 | + tasks have been completed. It receives the `err` argument if any `tasks` | ||
1268 | + pass an error to their callback. Results are always returned; however, if | ||
1269 | + an error occurs, no further `tasks` will be performed, and the results | ||
1270 | + object will only contain partial results. | ||
1271 | + | ||
1272 | + | ||
1273 | +__Example__ | ||
1274 | + | ||
1275 | +```js | ||
1276 | +async.auto({ | ||
1277 | + get_data: function(callback){ | ||
1278 | + console.log('in get_data'); | ||
1279 | + // async code to get some data | ||
1280 | + callback(null, 'data', 'converted to array'); | ||
1281 | + }, | ||
1282 | + make_folder: function(callback){ | ||
1283 | + console.log('in make_folder'); | ||
1284 | + // async code to create a directory to store a file in | ||
1285 | + // this is run at the same time as getting the data | ||
1286 | + callback(null, 'folder'); | ||
1287 | + }, | ||
1288 | + write_file: ['get_data', 'make_folder', function(callback, results){ | ||
1289 | + console.log('in write_file', JSON.stringify(results)); | ||
1290 | + // once there is some data and the directory exists, | ||
1291 | + // write the data to a file in the directory | ||
1292 | + callback(null, 'filename'); | ||
1293 | + }], | ||
1294 | + email_link: ['write_file', function(callback, results){ | ||
1295 | + console.log('in email_link', JSON.stringify(results)); | ||
1296 | + // once the file is written let's email a link to it... | ||
1297 | + // results.write_file contains the filename returned by write_file. | ||
1298 | + callback(null, {'file':results.write_file, 'email':'user@example.com'}); | ||
1299 | + }] | ||
1300 | +}, function(err, results) { | ||
1301 | + console.log('err = ', err); | ||
1302 | + console.log('results = ', results); | ||
1303 | +}); | ||
1304 | +``` | ||
1305 | + | ||
1306 | +This is a fairly trivial example, but to do this using the basic parallel and | ||
1307 | +series functions would look like this: | ||
1308 | + | ||
1309 | +```js | ||
1310 | +async.parallel([ | ||
1311 | + function(callback){ | ||
1312 | + console.log('in get_data'); | ||
1313 | + // async code to get some data | ||
1314 | + callback(null, 'data', 'converted to array'); | ||
1315 | + }, | ||
1316 | + function(callback){ | ||
1317 | + console.log('in make_folder'); | ||
1318 | + // async code to create a directory to store a file in | ||
1319 | + // this is run at the same time as getting the data | ||
1320 | + callback(null, 'folder'); | ||
1321 | + } | ||
1322 | +], | ||
1323 | +function(err, results){ | ||
1324 | + async.series([ | ||
1325 | + function(callback){ | ||
1326 | + console.log('in write_file', JSON.stringify(results)); | ||
1327 | + // once there is some data and the directory exists, | ||
1328 | + // write the data to a file in the directory | ||
1329 | + results.push('filename'); | ||
1330 | + callback(null); | ||
1331 | + }, | ||
1332 | + function(callback){ | ||
1333 | + console.log('in email_link', JSON.stringify(results)); | ||
1334 | + // once the file is written let's email a link to it... | ||
1335 | + callback(null, {'file':results.pop(), 'email':'user@example.com'}); | ||
1336 | + } | ||
1337 | + ]); | ||
1338 | +}); | ||
1339 | +``` | ||
1340 | + | ||
1341 | +For a complicated series of `async` tasks, using the [`auto`](#auto) function makes adding | ||
1342 | +new tasks much easier (and the code more readable). | ||
1343 | + | ||
1344 | + | ||
1345 | +--------------------------------------- | ||
1346 | + | ||
1347 | +<a name="retry" /> | ||
1348 | +### retry([times = 5], task, [callback]) | ||
1349 | + | ||
1350 | +Attempts to get a successful response from `task` no more than `times` times before | ||
1351 | +returning an error. If the task is successful, the `callback` will be passed the result | ||
1352 | +of the successfull task. If all attemps fail, the callback will be passed the error and | ||
1353 | +result (if any) of the final attempt. | ||
1354 | + | ||
1355 | +__Arguments__ | ||
1356 | + | ||
1357 | +* `times` - An integer indicating how many times to attempt the `task` before giving up. Defaults to 5. | ||
1358 | +* `task(callback, results)` - A function which receives two arguments: (1) a `callback(err, result)` | ||
1359 | + which must be called when finished, passing `err` (which can be `null`) and the `result` of | ||
1360 | + the function's execution, and (2) a `results` object, containing the results of | ||
1361 | + the previously executed functions (if nested inside another control flow). | ||
1362 | +* `callback(err, results)` - An optional callback which is called when the | ||
1363 | + task has succeeded, or after the final failed attempt. It receives the `err` and `result` arguments of the last attempt at completing the `task`. | ||
1364 | + | ||
1365 | +The [`retry`](#retry) function can be used as a stand-alone control flow by passing a | ||
1366 | +callback, as shown below: | ||
1367 | + | ||
1368 | +```js | ||
1369 | +async.retry(3, apiMethod, function(err, result) { | ||
1370 | + // do something with the result | ||
1371 | +}); | ||
1372 | +``` | ||
1373 | + | ||
1374 | +It can also be embeded within other control flow functions to retry individual methods | ||
1375 | +that are not as reliable, like this: | ||
1376 | + | ||
1377 | +```js | ||
1378 | +async.auto({ | ||
1379 | + users: api.getUsers.bind(api), | ||
1380 | + payments: async.retry(3, api.getPayments.bind(api)) | ||
1381 | +}, function(err, results) { | ||
1382 | + // do something with the results | ||
1383 | +}); | ||
1384 | +``` | ||
1385 | + | ||
1386 | + | ||
1387 | +--------------------------------------- | ||
1388 | + | ||
1389 | +<a name="iterator" /> | ||
1390 | +### iterator(tasks) | ||
1391 | + | ||
1392 | +Creates an iterator function which calls the next function in the `tasks` array, | ||
1393 | +returning a continuation to call the next one after that. It's also possible to | ||
1394 | +“peek” at the next iterator with `iterator.next()`. | ||
1395 | + | ||
1396 | +This function is used internally by the `async` module, but can be useful when | ||
1397 | +you want to manually control the flow of functions in series. | ||
1398 | + | ||
1399 | +__Arguments__ | ||
1400 | + | ||
1401 | +* `tasks` - An array of functions to run. | ||
1402 | + | ||
1403 | +__Example__ | ||
1404 | + | ||
1405 | +```js | ||
1406 | +var iterator = async.iterator([ | ||
1407 | + function(){ sys.p('one'); }, | ||
1408 | + function(){ sys.p('two'); }, | ||
1409 | + function(){ sys.p('three'); } | ||
1410 | +]); | ||
1411 | + | ||
1412 | +node> var iterator2 = iterator(); | ||
1413 | +'one' | ||
1414 | +node> var iterator3 = iterator2(); | ||
1415 | +'two' | ||
1416 | +node> iterator3(); | ||
1417 | +'three' | ||
1418 | +node> var nextfn = iterator2.next(); | ||
1419 | +node> nextfn(); | ||
1420 | +'three' | ||
1421 | +``` | ||
1422 | + | ||
1423 | +--------------------------------------- | ||
1424 | + | ||
1425 | +<a name="apply" /> | ||
1426 | +### apply(function, arguments..) | ||
1427 | + | ||
1428 | +Creates a continuation function with some arguments already applied. | ||
1429 | + | ||
1430 | +Useful as a shorthand when combined with other control flow functions. Any arguments | ||
1431 | +passed to the returned function are added to the arguments originally passed | ||
1432 | +to apply. | ||
1433 | + | ||
1434 | +__Arguments__ | ||
1435 | + | ||
1436 | +* `function` - The function you want to eventually apply all arguments to. | ||
1437 | +* `arguments...` - Any number of arguments to automatically apply when the | ||
1438 | + continuation is called. | ||
1439 | + | ||
1440 | +__Example__ | ||
1441 | + | ||
1442 | +```js | ||
1443 | +// using apply | ||
1444 | + | ||
1445 | +async.parallel([ | ||
1446 | + async.apply(fs.writeFile, 'testfile1', 'test1'), | ||
1447 | + async.apply(fs.writeFile, 'testfile2', 'test2'), | ||
1448 | +]); | ||
1449 | + | ||
1450 | + | ||
1451 | +// the same process without using apply | ||
1452 | + | ||
1453 | +async.parallel([ | ||
1454 | + function(callback){ | ||
1455 | + fs.writeFile('testfile1', 'test1', callback); | ||
1456 | + }, | ||
1457 | + function(callback){ | ||
1458 | + fs.writeFile('testfile2', 'test2', callback); | ||
1459 | + } | ||
1460 | +]); | ||
1461 | +``` | ||
1462 | + | ||
1463 | +It's possible to pass any number of additional arguments when calling the | ||
1464 | +continuation: | ||
1465 | + | ||
1466 | +```js | ||
1467 | +node> var fn = async.apply(sys.puts, 'one'); | ||
1468 | +node> fn('two', 'three'); | ||
1469 | +one | ||
1470 | +two | ||
1471 | +three | ||
1472 | +``` | ||
1473 | + | ||
1474 | +--------------------------------------- | ||
1475 | + | ||
1476 | +<a name="nextTick" /> | ||
1477 | +### nextTick(callback) | ||
1478 | + | ||
1479 | +Calls `callback` on a later loop around the event loop. In Node.js this just | ||
1480 | +calls `process.nextTick`; in the browser it falls back to `setImmediate(callback)` | ||
1481 | +if available, otherwise `setTimeout(callback, 0)`, which means other higher priority | ||
1482 | +events may precede the execution of `callback`. | ||
1483 | + | ||
1484 | +This is used internally for browser-compatibility purposes. | ||
1485 | + | ||
1486 | +__Arguments__ | ||
1487 | + | ||
1488 | +* `callback` - The function to call on a later loop around the event loop. | ||
1489 | + | ||
1490 | +__Example__ | ||
1491 | + | ||
1492 | +```js | ||
1493 | +var call_order = []; | ||
1494 | +async.nextTick(function(){ | ||
1495 | + call_order.push('two'); | ||
1496 | + // call_order now equals ['one','two'] | ||
1497 | +}); | ||
1498 | +call_order.push('one') | ||
1499 | +``` | ||
1500 | + | ||
1501 | +<a name="times" /> | ||
1502 | +### times(n, callback) | ||
1503 | + | ||
1504 | +Calls the `callback` function `n` times, and accumulates results in the same manner | ||
1505 | +you would use with [`map`](#map). | ||
1506 | + | ||
1507 | +__Arguments__ | ||
1508 | + | ||
1509 | +* `n` - The number of times to run the function. | ||
1510 | +* `callback` - The function to call `n` times. | ||
1511 | + | ||
1512 | +__Example__ | ||
1513 | + | ||
1514 | +```js | ||
1515 | +// Pretend this is some complicated async factory | ||
1516 | +var createUser = function(id, callback) { | ||
1517 | + callback(null, { | ||
1518 | + id: 'user' + id | ||
1519 | + }) | ||
1520 | +} | ||
1521 | +// generate 5 users | ||
1522 | +async.times(5, function(n, next){ | ||
1523 | + createUser(n, function(err, user) { | ||
1524 | + next(err, user) | ||
1525 | + }) | ||
1526 | +}, function(err, users) { | ||
1527 | + // we should now have 5 users | ||
1528 | +}); | ||
1529 | +``` | ||
1530 | + | ||
1531 | +<a name="timesSeries" /> | ||
1532 | +### timesSeries(n, callback) | ||
1533 | + | ||
1534 | +The same as [`times`](#times), only the iterator is applied to each item in `arr` in | ||
1535 | +series. The next `iterator` is only called once the current one has completed. | ||
1536 | +The results array will be in the same order as the original. | ||
1537 | + | ||
1538 | + | ||
1539 | +## Utils | ||
1540 | + | ||
1541 | +<a name="memoize" /> | ||
1542 | +### memoize(fn, [hasher]) | ||
1543 | + | ||
1544 | +Caches the results of an `async` function. When creating a hash to store function | ||
1545 | +results against, the callback is omitted from the hash and an optional hash | ||
1546 | +function can be used. | ||
1547 | + | ||
1548 | +The cache of results is exposed as the `memo` property of the function returned | ||
1549 | +by `memoize`. | ||
1550 | + | ||
1551 | +__Arguments__ | ||
1552 | + | ||
1553 | +* `fn` - The function to proxy and cache results from. | ||
1554 | +* `hasher` - Tn optional function for generating a custom hash for storing | ||
1555 | + results. It has all the arguments applied to it apart from the callback, and | ||
1556 | + must be synchronous. | ||
1557 | + | ||
1558 | +__Example__ | ||
1559 | + | ||
1560 | +```js | ||
1561 | +var slow_fn = function (name, callback) { | ||
1562 | + // do something | ||
1563 | + callback(null, result); | ||
1564 | +}; | ||
1565 | +var fn = async.memoize(slow_fn); | ||
1566 | + | ||
1567 | +// fn can now be used as if it were slow_fn | ||
1568 | +fn('some name', function () { | ||
1569 | + // callback | ||
1570 | +}); | ||
1571 | +``` | ||
1572 | + | ||
1573 | +<a name="unmemoize" /> | ||
1574 | +### unmemoize(fn) | ||
1575 | + | ||
1576 | +Undoes a [`memoize`](#memoize)d function, reverting it to the original, unmemoized | ||
1577 | +form. Handy for testing. | ||
1578 | + | ||
1579 | +__Arguments__ | ||
1580 | + | ||
1581 | +* `fn` - the memoized function | ||
1582 | + | ||
1583 | +<a name="log" /> | ||
1584 | +### log(function, arguments) | ||
1585 | + | ||
1586 | +Logs the result of an `async` function to the `console`. Only works in Node.js or | ||
1587 | +in browsers that support `console.log` and `console.error` (such as FF and Chrome). | ||
1588 | +If multiple arguments are returned from the async function, `console.log` is | ||
1589 | +called on each argument in order. | ||
1590 | + | ||
1591 | +__Arguments__ | ||
1592 | + | ||
1593 | +* `function` - The function you want to eventually apply all arguments to. | ||
1594 | +* `arguments...` - Any number of arguments to apply to the function. | ||
1595 | + | ||
1596 | +__Example__ | ||
1597 | + | ||
1598 | +```js | ||
1599 | +var hello = function(name, callback){ | ||
1600 | + setTimeout(function(){ | ||
1601 | + callback(null, 'hello ' + name); | ||
1602 | + }, 1000); | ||
1603 | +}; | ||
1604 | +``` | ||
1605 | +```js | ||
1606 | +node> async.log(hello, 'world'); | ||
1607 | +'hello world' | ||
1608 | +``` | ||
1609 | + | ||
1610 | +--------------------------------------- | ||
1611 | + | ||
1612 | +<a name="dir" /> | ||
1613 | +### dir(function, arguments) | ||
1614 | + | ||
1615 | +Logs the result of an `async` function to the `console` using `console.dir` to | ||
1616 | +display the properties of the resulting object. Only works in Node.js or | ||
1617 | +in browsers that support `console.dir` and `console.error` (such as FF and Chrome). | ||
1618 | +If multiple arguments are returned from the async function, `console.dir` is | ||
1619 | +called on each argument in order. | ||
1620 | + | ||
1621 | +__Arguments__ | ||
1622 | + | ||
1623 | +* `function` - The function you want to eventually apply all arguments to. | ||
1624 | +* `arguments...` - Any number of arguments to apply to the function. | ||
1625 | + | ||
1626 | +__Example__ | ||
1627 | + | ||
1628 | +```js | ||
1629 | +var hello = function(name, callback){ | ||
1630 | + setTimeout(function(){ | ||
1631 | + callback(null, {hello: name}); | ||
1632 | + }, 1000); | ||
1633 | +}; | ||
1634 | +``` | ||
1635 | +```js | ||
1636 | +node> async.dir(hello, 'world'); | ||
1637 | +{hello: 'world'} | ||
1638 | +``` | ||
1639 | + | ||
1640 | +--------------------------------------- | ||
1641 | + | ||
1642 | +<a name="noConflict" /> | ||
1643 | +### noConflict() | ||
1644 | + | ||
1645 | +Changes the value of `async` back to its original value, returning a reference to the | ||
1646 | +`async` object. |
1 | +{ | ||
2 | + "name": "async", | ||
3 | + "repo": "caolan/async", | ||
4 | + "description": "Higher-order functions and common patterns for asynchronous code", | ||
5 | + "version": "0.1.23", | ||
6 | + "keywords": [], | ||
7 | + "dependencies": {}, | ||
8 | + "development": {}, | ||
9 | + "main": "lib/async.js", | ||
10 | + "scripts": [ "lib/async.js" ] | ||
11 | +} |
1 | +{ | ||
2 | + "name": "async", | ||
3 | + "description": "Higher-order functions and common patterns for asynchronous code", | ||
4 | + "main": "./lib/async", | ||
5 | + "author": { | ||
6 | + "name": "Caolan McMahon" | ||
7 | + }, | ||
8 | + "version": "0.9.0", | ||
9 | + "repository": { | ||
10 | + "type": "git", | ||
11 | + "url": "https://github.com/caolan/async.git" | ||
12 | + }, | ||
13 | + "bugs": { | ||
14 | + "url": "https://github.com/caolan/async/issues" | ||
15 | + }, | ||
16 | + "licenses": [ | ||
17 | + { | ||
18 | + "type": "MIT", | ||
19 | + "url": "https://github.com/caolan/async/raw/master/LICENSE" | ||
20 | + } | ||
21 | + ], | ||
22 | + "devDependencies": { | ||
23 | + "nodeunit": ">0.0.0", | ||
24 | + "uglify-js": "1.2.x", | ||
25 | + "nodelint": ">0.0.0" | ||
26 | + }, | ||
27 | + "jam": { | ||
28 | + "main": "lib/async.js", | ||
29 | + "include": [ | ||
30 | + "lib/async.js", | ||
31 | + "README.md", | ||
32 | + "LICENSE" | ||
33 | + ] | ||
34 | + }, | ||
35 | + "scripts": { | ||
36 | + "test": "nodeunit test/test-async.js" | ||
37 | + }, | ||
38 | + "homepage": "https://github.com/caolan/async", | ||
39 | + "_id": "async@0.9.0", | ||
40 | + "dist": { | ||
41 | + "shasum": "ac3613b1da9bed1b47510bb4651b8931e47146c7", | ||
42 | + "tarball": "http://registry.npmjs.org/async/-/async-0.9.0.tgz" | ||
43 | + }, | ||
44 | + "_from": "async@", | ||
45 | + "_npmVersion": "1.4.3", | ||
46 | + "_npmUser": { | ||
47 | + "name": "caolan", | ||
48 | + "email": "caolan.mcmahon@gmail.com" | ||
49 | + }, | ||
50 | + "maintainers": [ | ||
51 | + { | ||
52 | + "name": "caolan", | ||
53 | + "email": "caolan@caolanmcmahon.com" | ||
54 | + } | ||
55 | + ], | ||
56 | + "directories": {}, | ||
57 | + "_shasum": "ac3613b1da9bed1b47510bb4651b8931e47146c7", | ||
58 | + "_resolved": "https://registry.npmjs.org/async/-/async-0.9.0.tgz", | ||
59 | + "readme": "ERROR: No README data found!" | ||
60 | +} |
1 | +1.17.0 / 2014-10-28 | ||
2 | +================== | ||
3 | + | ||
4 | + * changed: extended compare callback also returns the file names #297 [mastix](https://github.com/mastix) | ||
5 | + * changed: pass spawn crash to callback #306 [medikoo](https://github.com/medikoo) | ||
6 | + * changed: geometry supports arbitary string as first argument #330 [jdiez17](https://github.com/jdiez17) | ||
7 | + * added: support for repage+ option #275 [desigens](https://github.com/desigens) | ||
8 | + * added: added the dissolve command #300 [microadm](https://github.com/microadam) | ||
9 | + * added: composite method #332 [jdiez17](https://github.com/jdiez17) | ||
10 | + * fixed: cannot set tolerance to 0 #302 [rwky](https://github.com/rwky) | ||
11 | + * fixed: handle empty buffers #330 [alcidesv](https://github.com/alcidesv) | ||
12 | + | ||
13 | +1.16.0 / 2014-05-09 | ||
14 | +================== | ||
15 | + | ||
16 | + * fixed; dropped "+" when 0 passed as vertical roll amt #267 [dwtkns](https://github.com/dwtkns) | ||
17 | + * added; highlight-style support #272 [fdecampredon](https://github.com/fdecampredon) | ||
18 | + | ||
19 | +1.15.0 / 2014-05-03 | ||
20 | +=================== | ||
21 | + | ||
22 | + * changed; gm.compare logic to always run the mse comparison as expected #258 [Vokkim](https://github.com/Vokkim) | ||
23 | + * added; `tolerance` to gm.compare options object #258 [Vokkim](https://github.com/Vokkim) | ||
24 | + * added; option to set ImageMagick application path explicitly #250 (akreitals) | ||
25 | + * fixed; gm.compare: support values like 9.51582e-05 #260 [normanrz](https://github.com/normanrz) | ||
26 | + * README: add call for maintainers | ||
27 | + | ||
28 | +1.14.2 / 2013-12-24 | ||
29 | +=================== | ||
30 | + | ||
31 | +* fixed; background is now a setting #246 (PEM--) | ||
32 | + | ||
33 | +1.14.1 / 2013-12-09 | ||
34 | +=================== | ||
35 | + | ||
36 | +* fixed; identify -verbose colon behavior #240 ludow | ||
37 | + | ||
38 | +1.14.0 / 2013-12-04 | ||
39 | +=================== | ||
40 | + | ||
41 | +* added; compare method for imagemagick (longlho) | ||
42 | + | ||
43 | +1.13.3 / 2013-10-22 | ||
44 | +=================== | ||
45 | + | ||
46 | +* fixed; escape diffOptions.file in compare (dwabyick) | ||
47 | + | ||
48 | +1.13.2 / 2013-10-18 | ||
49 | +=================== | ||
50 | + | ||
51 | +* fixed; density is a setting not an operator | ||
52 | + | ||
53 | +1.13.1 / 2013-09-15 | ||
54 | +=================== | ||
55 | + | ||
56 | +* added; boolean for % crop | ||
57 | + | ||
58 | +1.13.0 / 2013-09-07 | ||
59 | +=================== | ||
60 | + | ||
61 | +* added; morph more than two images (overra) | ||
62 | + | ||
63 | +1.12.2 / 2013-08-29 | ||
64 | +=================== | ||
65 | + | ||
66 | +* fixed; fallback to through in node 0.8 | ||
67 | + | ||
68 | +1.12.1 / 2013-08-29 (unpublished) | ||
69 | +=================== | ||
70 | + | ||
71 | +* refactor; replace through with stream.PassThrough | ||
72 | + | ||
73 | +1.12.0 / 2013-08-27 | ||
74 | +=================== | ||
75 | + | ||
76 | +* added; diff image output file (chenglou) | ||
77 | + | ||
78 | +1.11.1 / 2013-08-17 | ||
79 | +=================== | ||
80 | + | ||
81 | +* added; proto.selectFrame(#) | ||
82 | +* fixed; getters should not ignore frame selection | ||
83 | + | ||
84 | +1.11.0 / 2013-07-23 | ||
85 | +=================== | ||
86 | + | ||
87 | +* added; optional formatting string for gm().identify(format, callback) (tornillo) | ||
88 | +* removed; error messages when gm/im binary is not installed | ||
89 | + | ||
90 | +1.10.0 / 2013-06-27 | ||
91 | +=================== | ||
92 | + | ||
93 | +* refactor; use native `-auto-orient` for imagemagick | ||
94 | + | ||
95 | +1.9.2 / 2013-06-12 | ||
96 | +================== | ||
97 | + | ||
98 | + * refactor; move `streamToBuffer` to a separate module | ||
99 | + * fixed; .stream(format) without a callback | ||
100 | + | ||
101 | +1.9.1 / 2013-05-07 | ||
102 | +================== | ||
103 | + | ||
104 | + * fixed; gm().resize(width) always only resizes width | ||
105 | + * fixed; gm('img.gif').format() returns the format of the first frame | ||
106 | + | ||
107 | +1.9.0 / 2013-04-21 | ||
108 | +================== | ||
109 | + | ||
110 | + * added; node v0.10 support | ||
111 | + * removed; node < v0.8 support - `Buffer.concat()` | ||
112 | + * tests; all tests now run on Travis | ||
113 | + * added; gm().stream() returns a stream when no callback is present | ||
114 | + * added; gm().toBuffer(callback) | ||
115 | + * fixed; gm().size() only returns the size of the first frame of a GIF | ||
116 | + | ||
117 | +1.8.2 / 2013-03-07 | ||
118 | +================== | ||
119 | + | ||
120 | + * include source path in identify data #126 [soupdiver](https://github.com/soupdiver) | ||
121 | + | ||
122 | +1.8.1 / 2012-12-21 | ||
123 | +================== | ||
124 | + | ||
125 | + * Avoid losing already set arguments on identify #105 #113 #109 [JNissi](https://github.com/JNissi) | ||
126 | + * tests; add autoOrient + thumb() test | ||
127 | + * tests; add test case for #113 | ||
128 | + * tests; added test for #109 | ||
129 | + * tests; add resize on buffer test | ||
130 | + | ||
131 | +1.8.0 / 2012-12-14 | ||
132 | +================== | ||
133 | + | ||
134 | + * added; geometry support to scale() #98 | ||
135 | + * removed; incorrect/broken dissolve() method (never worked) | ||
136 | + * fixed; handle child_proc error when using Buffer input #109 | ||
137 | + * fixed; use of Buffers with identify() #109 | ||
138 | + * fixed; no longer include -size arg with resize() #98 | ||
139 | + * fixed; remove -size arg from extent() #103 | ||
140 | + * fixed; magnify support | ||
141 | + * fixed; autoOrient to work with all types of exif orientations [dambalah](https://github.com/dambalah) #108 | ||
142 | + * tests; npm test runs unit only (now compatible with travis) | ||
143 | + * tests; fix magnify test on imagemagick | ||
144 | + * tests; added for cmd line args | ||
145 | + | ||
146 | +1.7.0 / 2012-12-06 | ||
147 | +================== | ||
148 | + | ||
149 | + * added; gm.compare support | ||
150 | + * added; passing Buffers directly [danmilon](https://github.com/danmilon) | ||
151 | + | ||
152 | +1.6.1 / 2012-11-13 | ||
153 | +================== | ||
154 | + | ||
155 | + * fixed regression; only pass additional params on error #96 | ||
156 | + | ||
157 | +1.6.0 / 2012-11-10 | ||
158 | +================== | ||
159 | + | ||
160 | + * changed; rename internal buffer to _buffer #88 [kof](https://github.com/kof) | ||
161 | + * changed; optimized identify getters (format, depth, size, color, filesize). #83 please read this for details: https://github.com/aheckmann/gm/commit/8fcf3f8f84a02cc2001da874cbebb89bf7084409 | ||
162 | + * added; visionmedia/debug support | ||
163 | + * added; `gm convert -thumbnail` support. _differs from thumb()._ [danmilon](https://github.com/danmilon) | ||
164 | + * fixed; -rotate 0 support #90 | ||
165 | + * fixed; multi-execution of same gm instance arguments corruption | ||
166 | + * fixed; gracefully handle parser errors #94 [eldilibra](https://github.com/eldilibra) | ||
167 | + | ||
168 | +1.5.1 / 2012-10-02 | ||
169 | +================== | ||
170 | + | ||
171 | + * fixed; passing multiple paths to append() #77 | ||
172 | + | ||
173 | +1.5.0 / 2012-09-15 | ||
174 | +================== | ||
175 | + | ||
176 | + * fixed; callback scope | ||
177 | + * fixed; append() usage #77 | ||
178 | + | ||
179 | +1.4.2 / 2012-08-17 | ||
180 | +================== | ||
181 | + | ||
182 | + * fixed; identify parsing for ImageMagick exif data (#58) | ||
183 | + * fixed; when in imageMagick mode, complain about missing imageMagick [bcherry](https://github.com/bcherry) (#73) | ||
184 | + * added; tests | ||
185 | + | ||
186 | +1.4.1 / 2012-07-31 | ||
187 | +================== | ||
188 | + | ||
189 | + * fixed; scenes() args | ||
190 | + * fixed; accept the left-to-right arg of append() | ||
191 | + * added; _subCommand | ||
192 | + | ||
193 | +## v1.4 - 07/28/2012 | ||
194 | + | ||
195 | + * added; adjoin() [Math-] | ||
196 | + * added; affine() [Math-] | ||
197 | + * added; append() [Math-] | ||
198 | + * added; authenticate() [Math-] | ||
199 | + * added; average() [Math-] | ||
200 | + * added; backdrop() [Math-] | ||
201 | + * added; blackThreshold() [Math-] | ||
202 | + * added; bluePrimary() [Math-] | ||
203 | + * added; border() [Math-] | ||
204 | + * added; borderColor() [Math-] | ||
205 | + * added; box() [Math-] | ||
206 | + * added; channel() [Math-] | ||
207 | + * added; clip() [Math-] | ||
208 | + * added; coalesce() [Math-] | ||
209 | + * added; colorMap() [Math-] | ||
210 | + * added; compose() [Math-] | ||
211 | + * added; compress() [Math-] | ||
212 | + * added; convolve() [Math-] | ||
213 | + * added; createDirectories() [Math-] | ||
214 | + * added; deconstruct() [Math-] | ||
215 | + * added; delay() [Math-] | ||
216 | + * added; define() [Math-] | ||
217 | + * added; displace() [Math-] | ||
218 | + * added; display() [Math-] | ||
219 | + * added; dispose() [Math-] | ||
220 | + * added; disolve() [Math-] | ||
221 | + * added; encoding() [Math-] | ||
222 | + * added; endian() [Math-] | ||
223 | + * added; file() [Math-] | ||
224 | + * added; flatten() [Math-] | ||
225 | + * added; foreground() [Math-] | ||
226 | + * added; frame() [Math-] | ||
227 | + * added; fuzz() [Math-] | ||
228 | + * added; gaussian() [Math-] | ||
229 | + * added; geometry() [Math-] | ||
230 | + * added; greenPrimary() [Math-] | ||
231 | + * added; highlightColor() [Math-] | ||
232 | + * added; highlightStyle() [Math-] | ||
233 | + * added; iconGeometry() [Math-] | ||
234 | + * added; intent() [Math-] | ||
235 | + * added; lat() [Math-] | ||
236 | + * added; level() [Math-] | ||
237 | + * added; list() [Math-] | ||
238 | + * added; log() [Math-] | ||
239 | + * added; map() [Math-] | ||
240 | + * added; matte() [Math-] | ||
241 | + * added; matteColor() [Math-] | ||
242 | + * added; mask() [Math-] | ||
243 | + * added; maximumError() [Math-] | ||
244 | + * added; mode() [Math-] | ||
245 | + * added; monitor() [Math-] | ||
246 | + * added; mosaic() [Math-] | ||
247 | + * added; motionBlur() [Math-] | ||
248 | + * added; name() [Math-] | ||
249 | + * added; noop() [Math-] | ||
250 | + * added; normalize() [Math-] | ||
251 | + * added; opaque() [Math-] | ||
252 | + * added; operator() [Math-] | ||
253 | + * added; orderedDither() [Math-] | ||
254 | + * added; outputDirectory() [Math-] | ||
255 | + * added; page() [Math-] | ||
256 | + * added; pause() [Math-] | ||
257 | + * added; pen() [Math-] | ||
258 | + * added; ping() [Math-] | ||
259 | + * added; pointSize() [Math-] | ||
260 | + * added; preview() [Math-] | ||
261 | + * added; process() [Math-] | ||
262 | + * added; profile() [Math-] | ||
263 | + * added; progress() [Math-] | ||
264 | + * added; rawSize() [Math-] | ||
265 | + * added; randomThreshold() [Math-] | ||
266 | + * added; recolor() [Math-] | ||
267 | + * added; redPrimary() [Math-] | ||
268 | + * added; remote() [Math-] | ||
269 | + * added; render() [Math-] | ||
270 | + * added; repage() [Math-] | ||
271 | + * added; sample() [Math-] | ||
272 | + * added; samplingFactor() [Math-] | ||
273 | + * added; scene() [Math-] | ||
274 | + * added; scenes() [Math-] | ||
275 | + * added; screen() [Math-] | ||
276 | + * added; segment() [Math-] | ||
277 | + * added; set() [Math-] | ||
278 | + * added; shade() [Math-] | ||
279 | + * added; shadow() [Math-] | ||
280 | + * added; sharedMemory() [Math-] | ||
281 | + * added; shave() [Math-] | ||
282 | + * added; shear() [Math-] | ||
283 | + * added; silent() [Math-] | ||
284 | + * added; snaps() [Math-] | ||
285 | + * added; stagano() [Math-] | ||
286 | + * added; stereo() [Math-] | ||
287 | + * added; textFont() [Math-] | ||
288 | + * added; texture() [Math-] | ||
289 | + * added; threshold() [Math-] | ||
290 | + * added; tile() [Math-] | ||
291 | + * added; transform() [Math-] | ||
292 | + * added; transparent() [Math-] | ||
293 | + * added; treeDepth() [Math-] | ||
294 | + * added; update() [Math-] | ||
295 | + * added; units() [Math-] | ||
296 | + * added; unsharp() [Math-] | ||
297 | + * added; usePixmap() [Math-] | ||
298 | + * added; view() [Math-] | ||
299 | + * added; virtualPixel() [Math-] | ||
300 | + * added; visual() [Math-] | ||
301 | + * added; watermark() [Math-] | ||
302 | + * added; wave() [Math-] | ||
303 | + * added; whitePoint() [Math-] | ||
304 | + * added; whiteThreshold() [Math-] | ||
305 | + * added; window() [Math-] | ||
306 | + * added; windowGroup() [Math-] | ||
307 | + | ||
308 | +## v1.3.2 - 06/22/2012 | ||
309 | + | ||
310 | + * added; node >= 0.7/0.8 compat | ||
311 | + | ||
312 | +## v1.3.1 - 06/06/2012 | ||
313 | + | ||
314 | + * fixed; thumb() alignment and cropping [thomaschaaf] | ||
315 | + * added; hint when graphicsmagick is not installed (#62) | ||
316 | + * fixed; minify() (#59) | ||
317 | + | ||
318 | +## v1.3.0 - 04/11/2012 | ||
319 | + | ||
320 | + * added; flatten support [jwarchol] | ||
321 | + * added; background support [jwarchol] | ||
322 | + * fixed; identify parser error [chriso] | ||
323 | + | ||
324 | +## v1.2.0 - 03/30/2012 | ||
325 | + | ||
326 | + * added; extent and gravity support [jwarchol] | ||
327 | + | ||
328 | +## v1.1.0 - 03/15/2012 | ||
329 | + | ||
330 | + * added; filter() support [travisbeck] | ||
331 | + * added; density() [travisbeck] | ||
332 | + * fixed; permit either width or height in resize [dambalah] | ||
333 | + * updated; docs | ||
334 | + | ||
335 | +## v1.0.5 - 02/15/2012 | ||
336 | + | ||
337 | + * added; strip() support [Math-] | ||
338 | + * added; interlace() support [Math-] | ||
339 | + * added; setFormat() support [Math-] | ||
340 | + * fixed; regexps for image types [Math-] | ||
341 | + | ||
342 | +## v1.0.4 - 02/09/2012 | ||
343 | + | ||
344 | + * expose utils | ||
345 | + | ||
346 | +## v1.0.3 - 01/27/2012 | ||
347 | + | ||
348 | + * removed; console.log | ||
349 | + | ||
350 | +## v1.0.2 - 01/24/2012 | ||
351 | + | ||
352 | + * added; debugging info on parser errors | ||
353 | + * fixed; exports.version | ||
354 | + | ||
355 | +## v1.0.1 - 01/12/2012 | ||
356 | + | ||
357 | + * fixed; use of reserved keyword `super` for node v0.5+ | ||
358 | + | ||
359 | +## v1.0.0 - 01/12/2012 | ||
360 | + | ||
361 | + * added; autoOrient support [kainosnoema] (#21) | ||
362 | + * added; orientation support [kainosnoema] (#21) | ||
363 | + * fixed; identify parser now properly JSON formats all data output by `gm identify` such as IPTC, GPS, Make, etc (#20) | ||
364 | + * added; support for running as imagemagick (#23, #29) | ||
365 | + * added; subclassing support; useful for setting default constructor options like one constructor for ImageMagick, the other for GM | ||
366 | + * added; more tests | ||
367 | + * changed; remove redundant `orientation`, `resolution`, and `filesize` from `this.data` in `indentify()`. Use their uppercase equivalents. | ||
368 | + | ||
369 | +## v0.6.0 - 12/14/2011 | ||
370 | + | ||
371 | + * added; stream support [kainosnoema] (#22) | ||
372 | + | ||
373 | +## v0.5.0 - 07/07/2011 | ||
374 | + | ||
375 | + * added; gm#trim() support [lepokle] | ||
376 | + * added; gm#inputIs() support | ||
377 | + * fixed; 'geometry does not contain image' error: gh-17 | ||
378 | + | ||
379 | +## v0.4.3 - 05/17/2011 | ||
380 | + | ||
381 | + * added; bunch of tests | ||
382 | + * fixed; polygon, polyline, bezier drawing bug | ||
383 | + | ||
384 | +## v0.4.2 - 05/10/2011 | ||
385 | + | ||
386 | + * added; resize options support | ||
387 | + | ||
388 | +## v0.4.1 - 04/28/2011 | ||
389 | + | ||
390 | + * shell args are now escaped (thanks @visionmedia) | ||
391 | + * added; gm.in() | ||
392 | + * added; gm.out() | ||
393 | + * various refactoring | ||
394 | + | ||
395 | +## v0.4.0 - 9/21/2010 | ||
396 | + | ||
397 | + * removed deprecated `new` method | ||
398 | + * added drawing docs | ||
399 | + | ||
400 | +## v0.3.2 - 9/06/2010 | ||
401 | + | ||
402 | + * new images are now created using same gm() constructor | ||
403 | + | ||
404 | +## v0.3.1 - 9/06/2010 | ||
405 | + | ||
406 | + * can now create images from scratch | ||
407 | + * add type method | ||
408 | + | ||
409 | +## v0.3.0 - 8/26/2010 | ||
410 | + | ||
411 | + * add drawing api | ||
412 | + | ||
413 | +## v0.2.2 - 8/22/2010 | ||
414 | + | ||
415 | + * add quality option to thumb() | ||
416 | + * add teropa to contributors | ||
417 | + * added support for colorspace() | ||
418 | + | ||
419 | +## v0.2.1 - 7/31/2010 | ||
420 | + | ||
421 | + * fixed naming conflict. depth() manipulation method renamed bitdepth() | ||
422 | + * added better docs | ||
423 | + | ||
424 | +## v0.2.0 - 7/29/2010 | ||
425 | + | ||
426 | +new methods | ||
427 | + | ||
428 | + - swirl | ||
429 | + - spread | ||
430 | + - solarize | ||
431 | + - sharpen | ||
432 | + - roll | ||
433 | + - sepia | ||
434 | + - region | ||
435 | + - raise | ||
436 | + - lower | ||
437 | + - paint | ||
438 | + - noise | ||
439 | + - negative | ||
440 | + - morph | ||
441 | + - median | ||
442 | + - antialias | ||
443 | + - limit | ||
444 | + - label | ||
445 | + - implode | ||
446 | + - gamma | ||
447 | + - enhance | ||
448 | + - equalize | ||
449 | + - emboss | ||
450 | + - edge | ||
451 | + - dither | ||
452 | + - monochrome | ||
453 | + - despeckle | ||
454 | + - depth | ||
455 | + - cycle | ||
456 | + - contrast | ||
457 | + - comment | ||
458 | + - colors | ||
459 | + | ||
460 | +added more default args to several methods | ||
461 | +added more examples | ||
462 | + | ||
463 | + | ||
464 | +## v0.1.2 - 7/28/2010 | ||
465 | + | ||
466 | + * refactor project into separate modules | ||
467 | + | ||
468 | + | ||
469 | +## v0.1.1 - 7/27/2010 | ||
470 | + | ||
471 | + * add modulate method | ||
472 | + * add colorize method | ||
473 | + * add charcoal method | ||
474 | + * add chop method | ||
475 | + * bug fix in write without a callback | ||
476 | + | ||
477 | + | ||
478 | +## v0.1.0 - 6/27/2010 | ||
479 | + | ||
480 | + * no longer supporting mogrify | ||
481 | + * add image data getter methods | ||
482 | + | ||
483 | + * size | ||
484 | + * format | ||
485 | + * color | ||
486 | + * res | ||
487 | + * depth | ||
488 | + * filesize | ||
489 | + * identify | ||
490 | + | ||
491 | + * add new convert methods | ||
492 | + | ||
493 | + * scale | ||
494 | + * resample | ||
495 | + * rotate | ||
496 | + * flip | ||
497 | + * flop | ||
498 | + * crop | ||
499 | + * magnify | ||
500 | + * minify | ||
501 | + * quality | ||
502 | + * blur | ||
503 | + * thumb | ||
504 | + | ||
505 | + | ||
506 | +## v0.0.1 - 6/11/2010 | ||
507 | +Initial release |
1 | + | ||
2 | +# gm v1.17.0 [](https://travis-ci.org/aheckmann/gm) [](https://www.npmjs.org/package/gm) | ||
3 | + | ||
4 | +GraphicsMagick and ImageMagick for node | ||
5 | + | ||
6 | +## Getting started | ||
7 | +First download and install [GraphicsMagick](http://www.graphicsmagick.org/) or [ImageMagick](http://www.imagemagick.org/). In Mac OS X, you can simply use [Homebrew](http://mxcl.github.io/homebrew/) and do: | ||
8 | + | ||
9 | + brew install imagemagick | ||
10 | + brew install graphicsmagick | ||
11 | + | ||
12 | +If you want WebP support with ImageMagick, you must add the WebP option: | ||
13 | + | ||
14 | + brew install imagemagick --with-webp | ||
15 | + | ||
16 | +then either use npm: | ||
17 | + | ||
18 | + npm install gm | ||
19 | + | ||
20 | +or clone the repo: | ||
21 | + | ||
22 | + git clone git://github.com/aheckmann/gm.git | ||
23 | + | ||
24 | + | ||
25 | +## Use ImageMagick instead of gm | ||
26 | + | ||
27 | +Just pass the option `{imageMagick: true}` to enable ImageMagick | ||
28 | + | ||
29 | +```js | ||
30 | +var fs = require('fs') | ||
31 | + , gm = require('./gm'); | ||
32 | + | ||
33 | +// resize and remove EXIF profile data | ||
34 | +gm('/path/to/my/img.jpg') | ||
35 | +.options({imageMagick: true}) | ||
36 | +.resize(240, 240) | ||
37 | +... | ||
38 | +``` | ||
39 | + | ||
40 | + | ||
41 | +## Basic Usage | ||
42 | + | ||
43 | +```js | ||
44 | +var fs = require('fs') | ||
45 | + , gm = require('./gm'); | ||
46 | + | ||
47 | +// resize and remove EXIF profile data | ||
48 | +gm('/path/to/my/img.jpg') | ||
49 | +.resize(240, 240) | ||
50 | +.noProfile() | ||
51 | +.write('/path/to/resize.png', function (err) { | ||
52 | + if (!err) console.log('done'); | ||
53 | +}); | ||
54 | + | ||
55 | +// obtain the size of an image | ||
56 | +gm('/path/to/my/img.jpg') | ||
57 | +.size(function (err, size) { | ||
58 | + if (!err) | ||
59 | + console.log(size.width > size.height ? 'wider' : 'taller than you'); | ||
60 | +}); | ||
61 | + | ||
62 | +// output all available image properties | ||
63 | +gm('/path/to/img.png') | ||
64 | +.identify(function (err, data) { | ||
65 | + if (!err) console.log(data) | ||
66 | +}); | ||
67 | + | ||
68 | +// pull out the first frame of an animated gif and save as png | ||
69 | +gm('/path/to/animated.gif[0]') | ||
70 | +.write('/path/to/firstframe.png', function (err) { | ||
71 | + if (err) console.log('aaw, shucks'); | ||
72 | +}); | ||
73 | + | ||
74 | +// auto-orient an image | ||
75 | +gm('/path/to/img.jpg') | ||
76 | +.autoOrient() | ||
77 | +.write('/path/to/oriented.jpg', function (err) { | ||
78 | + if (err) ... | ||
79 | +}) | ||
80 | + | ||
81 | +// crazytown | ||
82 | +gm('/path/to/my/img.jpg') | ||
83 | +.flip() | ||
84 | +.magnify() | ||
85 | +.rotate('green', 45) | ||
86 | +.blur(7, 3) | ||
87 | +.crop(300, 300, 150, 130) | ||
88 | +.edge(3) | ||
89 | +.write('/path/to/crazy.jpg', function (err) { | ||
90 | + if (!err) console.log('crazytown has arrived'); | ||
91 | +}) | ||
92 | + | ||
93 | +// annotate an image | ||
94 | +gm('/path/to/my/img.jpg') | ||
95 | +.stroke("#ffffff") | ||
96 | +.drawCircle(10, 10, 20, 10) | ||
97 | +.font("Helvetica.ttf", 12) | ||
98 | +.drawText(30, 20, "GMagick!") | ||
99 | +.write("/path/to/drawing.png", function (err) { | ||
100 | + if (!err) console.log('done'); | ||
101 | +}); | ||
102 | + | ||
103 | +// creating an image | ||
104 | +gm(200, 400, "#ddff99f3") | ||
105 | +.drawText(10, 50, "from scratch") | ||
106 | +.write("/path/to/brandNewImg.jpg", function (err) { | ||
107 | + // ... | ||
108 | +}); | ||
109 | +``` | ||
110 | + | ||
111 | +## Streams | ||
112 | + | ||
113 | +```js | ||
114 | +// passing a stream | ||
115 | +var readStream = fs.createReadStream('/path/to/my/img.jpg'); | ||
116 | +gm(readStream, 'img.jpg') | ||
117 | +.write('/path/to/reformat.png', function (err) { | ||
118 | + if (!err) console.log('done'); | ||
119 | +}); | ||
120 | + | ||
121 | +// can also stream output to a ReadableStream | ||
122 | +// (can be piped to a local file or remote server) | ||
123 | +gm('/path/to/my/img.jpg') | ||
124 | +.resize('200', '200') | ||
125 | +.stream(function (err, stdout, stderr) { | ||
126 | + var writeStream = fs.createWriteStream('/path/to/my/resized.jpg'); | ||
127 | + stdout.pipe(writeStream); | ||
128 | +}); | ||
129 | + | ||
130 | +// without a callback, .stream() returns a stream | ||
131 | +// this is just a convenience wrapper for above. | ||
132 | +var writeStream = fs.createWriteStream('/path/to/my/resized.jpg'); | ||
133 | +gm('/path/to/my/img.jpg') | ||
134 | +.resize('200', '200') | ||
135 | +.stream() | ||
136 | +.pipe(writeStream); | ||
137 | + | ||
138 | +// pass a format or filename to stream() and | ||
139 | +// gm will provide image data in that format | ||
140 | +gm('/path/to/my/img.jpg') | ||
141 | +.stream('png', function (err, stdout, stderr) { | ||
142 | + var writeStream = fs.createWriteStream('/path/to/my/reformated.png'); | ||
143 | + stdout.pipe(writeStream); | ||
144 | +}); | ||
145 | + | ||
146 | +// or without the callback | ||
147 | +var writeStream = fs.createWriteStream('/path/to/my/reformated.png'); | ||
148 | +gm('/path/to/my/img.jpg') | ||
149 | +.stream('png') | ||
150 | +.pipe(writeStream); | ||
151 | + | ||
152 | +// combine the two for true streaming image processing | ||
153 | +var readStream = fs.createReadStream('/path/to/my/img.jpg'); | ||
154 | +gm(readStream, 'img.jpg') | ||
155 | +.resize('200', '200') | ||
156 | +.stream(function (err, stdout, stderr) { | ||
157 | + var writeStream = fs.createWriteStream('/path/to/my/resized.jpg'); | ||
158 | + stdout.pipe(writeStream); | ||
159 | +}); | ||
160 | + | ||
161 | +// GOTCHA: | ||
162 | +// when working with input streams and any 'identify' | ||
163 | +// operation (size, format, etc), you must pass "{bufferStream: true}" if | ||
164 | +// you also need to convert (write() or stream()) the image afterwards | ||
165 | +// NOTE: this buffers the readStream in memory! | ||
166 | +var readStream = fs.createReadStream('/path/to/my/img.jpg'); | ||
167 | +gm(readStream, 'img.jpg') | ||
168 | +.size({bufferStream: true}, function(err, size) { | ||
169 | + this.resize(size.width / 2, size.height / 2) | ||
170 | + this.write('/path/to/resized.jpg', function (err) { | ||
171 | + if (!err) console.log('done'); | ||
172 | + }); | ||
173 | +}); | ||
174 | + | ||
175 | +``` | ||
176 | + | ||
177 | +## Buffers | ||
178 | + | ||
179 | +```js | ||
180 | +// A buffer can be passed instead of a filepath as well | ||
181 | +var buf = require('fs').readFileSync('/path/to/image.jpg'); | ||
182 | + | ||
183 | +gm(buf, 'image.jpg') | ||
184 | +.noise('laplacian') | ||
185 | +.write('/path/to/out.jpg', function (err) { | ||
186 | + if (err) return handle(err); | ||
187 | + console.log('Created an image from a Buffer!'); | ||
188 | +}); | ||
189 | + | ||
190 | +/* | ||
191 | +A buffer can also be returned instead of a stream | ||
192 | +The first argument to toBuffer is optional, it specifies the image format | ||
193 | +*/ | ||
194 | +gm('img.jpg') | ||
195 | +.resize(100, 100) | ||
196 | +.toBuffer('PNG',function (err, buffer) { | ||
197 | + if (err) return handle(err); | ||
198 | + console.log('done!'); | ||
199 | +}) | ||
200 | +``` | ||
201 | + | ||
202 | +## Custom Arguments | ||
203 | + | ||
204 | +If `gm` does not supply you with a method you need or does not work as you'd like, you can simply use `gm().in()` or `gm().out()` to set your own arguments. | ||
205 | + | ||
206 | +- `gm().command()` - Custom command such as `identify` or `convert` | ||
207 | +- `gm().in()` - Custom input arguments | ||
208 | +- `gm().out()` - Custom output arguments | ||
209 | + | ||
210 | +The command will be formatted in the following order: | ||
211 | + | ||
212 | +1. `command` - ie `convert` | ||
213 | +2. `in` - the input arguments | ||
214 | +3. `source` - stdin or an image file | ||
215 | +4. `out` - the output arguments | ||
216 | +5. `output` - stdout or the image file to write to | ||
217 | + | ||
218 | +For example, suppose you want the following command: | ||
219 | + | ||
220 | +```bash | ||
221 | +gm "convert" "label:Offline" "PNG:-" | ||
222 | +``` | ||
223 | + | ||
224 | +However, using `gm().label()` may not work as intended for you: | ||
225 | + | ||
226 | +```js | ||
227 | +gm() | ||
228 | +.label('Offline') | ||
229 | +.stream(); | ||
230 | +``` | ||
231 | + | ||
232 | +would yield: | ||
233 | + | ||
234 | +```bash | ||
235 | +gm "convert" "-label" "\"Offline\"" "PNG:-" | ||
236 | +``` | ||
237 | + | ||
238 | +Instead, you can use `gm().out()`: | ||
239 | + | ||
240 | +```js | ||
241 | +gm() | ||
242 | +.out('label:Offline') | ||
243 | +.stream(); | ||
244 | +``` | ||
245 | + | ||
246 | +which correctly yields: | ||
247 | + | ||
248 | +```bash | ||
249 | +gm "convert" "label:Offline" "PNG:-" | ||
250 | +``` | ||
251 | + | ||
252 | +### Custom Identify Format String | ||
253 | + | ||
254 | +When identifying an image, you may want to use a custom formatting string instead of using `-verbose`, which is quite slow. | ||
255 | +You can use your own [formatting string](http://www.imagemagick.org/script/escape.php) when using `gm().identify(format, callback)`. | ||
256 | +For example, | ||
257 | + | ||
258 | +```js | ||
259 | +gm('img.png').format(function (err, format) { | ||
260 | + | ||
261 | +}) | ||
262 | + | ||
263 | +// is equivalent to | ||
264 | + | ||
265 | +gm('img.png').identify('%m', function (err, format) { | ||
266 | + | ||
267 | +}) | ||
268 | +``` | ||
269 | + | ||
270 | +since `%m` is the format option for getting the image file format. | ||
271 | + | ||
272 | +## Platform differences | ||
273 | + | ||
274 | +Please document and refer to any [platform or ImageMagick/GraphicsMagick issues/differences here](https://github.com/aheckmann/gm/wiki/GraphicsMagick-and-ImageMagick-versions). | ||
275 | + | ||
276 | +## Examples: | ||
277 | + | ||
278 | + Check out the [examples](http://github.com/aheckmann/gm/tree/master/examples/) directory to play around. | ||
279 | + Also take a look at the [extending gm](http://wiki.github.com/aheckmann/gm/extending-gm) | ||
280 | + page to see how to customize gm to your own needs. | ||
281 | + | ||
282 | +## Constructor: | ||
283 | + | ||
284 | + There are a few ways you can use the `gm` image constructor. | ||
285 | + | ||
286 | + - 1) `gm(path)` When you pass a string as the first argument it is interpreted as the path to an image you intend to manipulate. | ||
287 | + - 2) `gm(stream || buffer, [filename])` You may also pass a ReadableStream or Buffer as the first argument, with an optional file name for format inference. | ||
288 | + - 3) `gm(width, height, [color])` When you pass two integer arguments, gm will create a new image on the fly with the provided dimensions and an optional background color. And you can still chain just like you do with pre-existing images too. See [here](http://github.com/aheckmann/gm/blob/master/examples/new.js) for an example. | ||
289 | + | ||
290 | +## Methods | ||
291 | + | ||
292 | + - getters | ||
293 | + - [size](http://aheckmann.github.com/gm/docs.html#getters) - returns the size (WxH) of the image | ||
294 | + - [orientation](http://aheckmann.github.com/gm/docs.html#orientation) - returns the EXIF orientation of the image | ||
295 | + - [format](http://aheckmann.github.com/gm/docs.html#getters) - returns the image format (gif, jpeg, png, etc) | ||
296 | + - [depth](http://aheckmann.github.com/gm/docs.html#getters) - returns the image color depth | ||
297 | + - [color](http://aheckmann.github.com/gm/docs.html#getters) - returns the number of colors | ||
298 | + - [res](http://aheckmann.github.com/gm/docs.html#getters) - returns the image resolution | ||
299 | + - [filesize](http://aheckmann.github.com/gm/docs.html#getters) - returns image filesize | ||
300 | + - [identify](http://aheckmann.github.com/gm/docs.html#getters) - returns all image data available. Takes an optional format string. | ||
301 | + | ||
302 | + - manipulation | ||
303 | + - [adjoin](http://aheckmann.github.com/gm/docs.html#adjoin) | ||
304 | + - [affine](http://aheckmann.github.com/gm/docs.html#affine) | ||
305 | + - [antialias](http://aheckmann.github.com/gm/docs.html#antialias) | ||
306 | + - [append](http://aheckmann.github.com/gm/docs.html#append) | ||
307 | + - [authenticate](http://aheckmann.github.com/gm/docs.html#authenticate) | ||
308 | + - [autoOrient](http://aheckmann.github.com/gm/docs.html#autoOrient) | ||
309 | + - [average](http://aheckmann.github.com/gm/docs.html#average) | ||
310 | + - [backdrop](http://aheckmann.github.com/gm/docs.html#backdrop) | ||
311 | + - [bitdepth](http://aheckmann.github.com/gm/docs.html#bitdepth) | ||
312 | + - [blackThreshold](http://aheckmann.github.com/gm/docs.html#blackThreshold) | ||
313 | + - [bluePrimary](http://aheckmann.github.com/gm/docs.html#bluePrimary) | ||
314 | + - [blur](http://aheckmann.github.com/gm/docs.html#blur) | ||
315 | + - [border](http://aheckmann.github.com/gm/docs.html#border) | ||
316 | + - [borderColor](http://aheckmann.github.com/gm/docs.html#borderColor) | ||
317 | + - [box](http://aheckmann.github.com/gm/docs.html#box) | ||
318 | + - [channel](http://aheckmann.github.com/gm/docs.html#channel) | ||
319 | + - [charcoal](http://aheckmann.github.com/gm/docs.html#charcoal) | ||
320 | + - [chop](http://aheckmann.github.com/gm/docs.html#chop) | ||
321 | + - [clip](http://aheckmann.github.com/gm/docs.html#clip) | ||
322 | + - [coalesce](http://aheckmann.github.com/gm/docs.html#coalesce) | ||
323 | + - [colors](http://aheckmann.github.com/gm/docs.html#colors) | ||
324 | + - [colorize](http://aheckmann.github.com/gm/docs.html#colorize) | ||
325 | + - [colorMap](http://aheckmann.github.com/gm/docs.html#colorMap) | ||
326 | + - [colorspace](http://aheckmann.github.com/gm/docs.html#colorspace) | ||
327 | + - [comment](http://aheckmann.github.com/gm/docs.html#comment) | ||
328 | + - [compose](http://aheckmann.github.com/gm/docs.html#compose) | ||
329 | + - [compress](http://aheckmann.github.com/gm/docs.html#compress) | ||
330 | + - [contrast](http://aheckmann.github.com/gm/docs.html#contrast) | ||
331 | + - [convolve](http://aheckmann.github.com/gm/docs.html#convolve) | ||
332 | + - [createDirectories](http://aheckmann.github.com/gm/docs.html#createDirectories) | ||
333 | + - [crop](http://aheckmann.github.com/gm/docs.html#crop) | ||
334 | + - [cycle](http://aheckmann.github.com/gm/docs.html#cycle) | ||
335 | + - [deconstruct](http://aheckmann.github.com/gm/docs.html#deconstruct) | ||
336 | + - [delay](http://aheckmann.github.com/gm/docs.html#delay) | ||
337 | + - [define](http://aheckmann.github.com/gm/docs.html#define) | ||
338 | + - [density](http://aheckmann.github.com/gm/docs.html#density) | ||
339 | + - [despeckle](http://aheckmann.github.com/gm/docs.html#despeckle) | ||
340 | + - [dither](http://aheckmann.github.com/gm/docs.html#dither) | ||
341 | + - [displace](http://aheckmann.github.com/gm/docs.html#dither) | ||
342 | + - [display](http://aheckmann.github.com/gm/docs.html#display) | ||
343 | + - [dispose](http://aheckmann.github.com/gm/docs.html#dispose) | ||
344 | + - [dissolve](http://aheckmann.github.com/gm/docs.html#dissolve) | ||
345 | + - [edge](http://aheckmann.github.com/gm/docs.html#edge) | ||
346 | + - [emboss](http://aheckmann.github.com/gm/docs.html#emboss) | ||
347 | + - [encoding](http://aheckmann.github.com/gm/docs.html#encoding) | ||
348 | + - [enhance](http://aheckmann.github.com/gm/docs.html#enhance) | ||
349 | + - [endian](http://aheckmann.github.com/gm/docs.html#endian) | ||
350 | + - [equalize](http://aheckmann.github.com/gm/docs.html#equalize) | ||
351 | + - [extent](http://aheckmann.github.com/gm/docs.html#extent) | ||
352 | + - [file](http://aheckmann.github.com/gm/docs.html#file) | ||
353 | + - [filter](http://aheckmann.github.com/gm/docs.html#filter) | ||
354 | + - [flatten](http://aheckmann.github.com/gm/docs.html#flatten) | ||
355 | + - [flip](http://aheckmann.github.com/gm/docs.html#flip) | ||
356 | + - [flop](http://aheckmann.github.com/gm/docs.html#flop) | ||
357 | + - [foreground](http://aheckmann.github.com/gm/docs.html#foreground) | ||
358 | + - [frame](http://aheckmann.github.com/gm/docs.html#frame) | ||
359 | + - [fuzz](http://aheckmann.github.com/gm/docs.html#fuzz) | ||
360 | + - [gamma](http://aheckmann.github.com/gm/docs.html#gamma) | ||
361 | + - [gaussian](http://aheckmann.github.com/gm/docs.html#gaussian) | ||
362 | + - [geometry](http://aheckmann.github.com/gm/docs.html#geometry) | ||
363 | + - [gravity](http://aheckmann.github.com/gm/docs.html#gravity) | ||
364 | + - [greenPrimary](http://aheckmann.github.com/gm/docs.html#greenPrimary) | ||
365 | + - [highlightColor](http://aheckmann.github.com/gm/docs.html#highlightColor) | ||
366 | + - [highlightStyle](http://aheckmann.github.com/gm/docs.html#highlightStyle) | ||
367 | + - [iconGeometry](http://aheckmann.github.com/gm/docs.html#iconGeometry) | ||
368 | + - [implode](http://aheckmann.github.com/gm/docs.html#implode) | ||
369 | + - [intent](http://aheckmann.github.com/gm/docs.html#intent) | ||
370 | + - [interlace](http://aheckmann.github.com/gm/docs.html#interlace) | ||
371 | + - [label](http://aheckmann.github.com/gm/docs.html#label) | ||
372 | + - [lat](http://aheckmann.github.com/gm/docs.html#lat) | ||
373 | + - [level](http://aheckmann.github.com/gm/docs.html#level) | ||
374 | + - [list](http://aheckmann.github.com/gm/docs.html#list) | ||
375 | + - [limit](http://aheckmann.github.com/gm/docs.html#limit) | ||
376 | + - [log](http://aheckmann.github.com/gm/docs.html#log) | ||
377 | + - [loop](http://aheckmann.github.com/gm/docs.html#loop) | ||
378 | + - [lower](http://aheckmann.github.com/gm/docs.html#lower) | ||
379 | + - [magnify](http://aheckmann.github.com/gm/docs.html#magnify) | ||
380 | + - [map](http://aheckmann.github.com/gm/docs.html#map) | ||
381 | + - [matte](http://aheckmann.github.com/gm/docs.html#matte) | ||
382 | + - [matteColor](http://aheckmann.github.com/gm/docs.html#matteColor) | ||
383 | + - [mask](http://aheckmann.github.com/gm/docs.html#mask) | ||
384 | + - [maximumError](http://aheckmann.github.com/gm/docs.html#maximumError) | ||
385 | + - [median](http://aheckmann.github.com/gm/docs.html#median) | ||
386 | + - [minify](http://aheckmann.github.com/gm/docs.html#minify) | ||
387 | + - [mode](http://aheckmann.github.com/gm/docs.html#mode) | ||
388 | + - [modulate](http://aheckmann.github.com/gm/docs.html#modulate) | ||
389 | + - [monitor](http://aheckmann.github.com/gm/docs.html#monitor) | ||
390 | + - [monochrome](http://aheckmann.github.com/gm/docs.html#monochrome) | ||
391 | + - [morph](http://aheckmann.github.com/gm/docs.html#morph) | ||
392 | + - [mosaic](http://aheckmann.github.com/gm/docs.html#mosaic) | ||
393 | + - [motionBlur](http://aheckmann.github.com/gm/docs.html#motionBlur) | ||
394 | + - [name](http://aheckmann.github.com/gm/docs.html#name) | ||
395 | + - [negative](http://aheckmann.github.com/gm/docs.html#negative) | ||
396 | + - [noise](http://aheckmann.github.com/gm/docs.html#noise) | ||
397 | + - [noop](http://aheckmann.github.com/gm/docs.html#noop) | ||
398 | + - [normalize](http://aheckmann.github.com/gm/docs.html#normalize) | ||
399 | + - [noProfile](http://aheckmann.github.com/gm/docs.html#profile) | ||
400 | + - [opaque](http://aheckmann.github.com/gm/docs.html#opaque) | ||
401 | + - [operator](http://aheckmann.github.com/gm/docs.html#operator) | ||
402 | + - [orderedDither](http://aheckmann.github.com/gm/docs.html#orderedDither) | ||
403 | + - [outputDirectory](http://aheckmann.github.com/gm/docs.html#outputDirectory) | ||
404 | + - [paint](http://aheckmann.github.com/gm/docs.html#paint) | ||
405 | + - [page](http://aheckmann.github.com/gm/docs.html#page) | ||
406 | + - [pause](http://aheckmann.github.com/gm/docs.html#pause) | ||
407 | + - [pen](http://aheckmann.github.com/gm/docs.html#pen) | ||
408 | + - [ping](http://aheckmann.github.com/gm/docs.html#ping) | ||
409 | + - [pointSize](http://aheckmann.github.com/gm/docs.html#pointSize) | ||
410 | + - [preview](http://aheckmann.github.com/gm/docs.html#preview) | ||
411 | + - [process](http://aheckmann.github.com/gm/docs.html#process) | ||
412 | + - [profile](http://aheckmann.github.com/gm/docs.html#profile) | ||
413 | + - [progress](http://aheckmann.github.com/gm/docs.html#progress) | ||
414 | + - [quality](http://aheckmann.github.com/gm/docs.html#quality) | ||
415 | + - [raise](http://aheckmann.github.com/gm/docs.html#raise) | ||
416 | + - [rawSize](http://aheckmann.github.com/gm/docs.html#rawSize) | ||
417 | + - [randomThreshold](http://aheckmann.github.com/gm/docs.html#randomThreshold) | ||
418 | + - [recolor](http://aheckmann.github.com/gm/docs.html#recolor) | ||
419 | + - [redPrimary](http://aheckmann.github.com/gm/docs.html#redPrimary) | ||
420 | + - [region](http://aheckmann.github.com/gm/docs.html#region) | ||
421 | + - [remote](http://aheckmann.github.com/gm/docs.html#remote) | ||
422 | + - [render](http://aheckmann.github.com/gm/docs.html#render) | ||
423 | + - [repage](http://aheckmann.github.com/gm/docs.html#repage) | ||
424 | + - [resample](http://aheckmann.github.com/gm/docs.html#resample) | ||
425 | + - [resize](http://aheckmann.github.com/gm/docs.html#resize) | ||
426 | + - [roll](http://aheckmann.github.com/gm/docs.html#roll) | ||
427 | + - [rotate](http://aheckmann.github.com/gm/docs.html#rotate) | ||
428 | + - [sample](http://aheckmann.github.com/gm/docs.html#sample) | ||
429 | + - [samplingFactor](http://aheckmann.github.com/gm/docs.html#samplingFactor) | ||
430 | + - [scale](http://aheckmann.github.com/gm/docs.html#scale) | ||
431 | + - [scene](http://aheckmann.github.com/gm/docs.html#scene) | ||
432 | + - [scenes](http://aheckmann.github.com/gm/docs.html#scenes) | ||
433 | + - [screen](http://aheckmann.github.com/gm/docs.html#screen) | ||
434 | + - [segment](http://aheckmann.github.com/gm/docs.html#segment) | ||
435 | + - [sepia](http://aheckmann.github.com/gm/docs.html#sepia) | ||
436 | + - [set](http://aheckmann.github.com/gm/docs.html#set) | ||
437 | + - [setFormat](http://aheckmann.github.com/gm/docs.html#setformat) | ||
438 | + - [shade](http://aheckmann.github.com/gm/docs.html#shade) | ||
439 | + - [shadow](http://aheckmann.github.com/gm/docs.html#shadow) | ||
440 | + - [sharedMemory](http://aheckmann.github.com/gm/docs.html#sharedMemory) | ||
441 | + - [sharpen](http://aheckmann.github.com/gm/docs.html#sharpen) | ||
442 | + - [shave](http://aheckmann.github.com/gm/docs.html#shave) | ||
443 | + - [shear](http://aheckmann.github.com/gm/docs.html#shear) | ||
444 | + - [silent](http://aheckmann.github.com/gm/docs.html#silent) | ||
445 | + - [solarize](http://aheckmann.github.com/gm/docs.html#solarize) | ||
446 | + - [snaps](http://aheckmann.github.com/gm/docs.html#snaps) | ||
447 | + - [stegano](http://aheckmann.github.com/gm/docs.html#stegano) | ||
448 | + - [stereo](http://aheckmann.github.com/gm/docs.html#stereo) | ||
449 | + - [strip](http://aheckmann.github.com/gm/docs.html#strip) _imagemagick only_ | ||
450 | + - [spread](http://aheckmann.github.com/gm/docs.html#spread) | ||
451 | + - [swirl](http://aheckmann.github.com/gm/docs.html#swirl) | ||
452 | + - [textFont](http://aheckmann.github.com/gm/docs.html#textFont) | ||
453 | + - [texture](http://aheckmann.github.com/gm/docs.html#texture) | ||
454 | + - [threshold](http://aheckmann.github.com/gm/docs.html#threshold) | ||
455 | + - [thumb](http://aheckmann.github.com/gm/docs.html#thumb) | ||
456 | + - [tile](http://aheckmann.github.com/gm/docs.html#tile) | ||
457 | + - [transform](http://aheckmann.github.com/gm/docs.html#transform) | ||
458 | + - [transparent](http://aheckmann.github.com/gm/docs.html#transparent) | ||
459 | + - [treeDepth](http://aheckmann.github.com/gm/docs.html#treeDepth) | ||
460 | + - [trim](http://aheckmann.github.com/gm/docs.html#trim) | ||
461 | + - [type](http://aheckmann.github.com/gm/docs.html#type) | ||
462 | + - [update](http://aheckmann.github.com/gm/docs.html#update) | ||
463 | + - [units](http://aheckmann.github.com/gm/docs.html#units) | ||
464 | + - [unsharp](http://aheckmann.github.com/gm/docs.html#unsharp) | ||
465 | + - [usePixmap](http://aheckmann.github.com/gm/docs.html#usePixmap) | ||
466 | + - [view](http://aheckmann.github.com/gm/docs.html#view) | ||
467 | + - [virtualPixel](http://aheckmann.github.com/gm/docs.html#virtualPixel) | ||
468 | + - [visual](http://aheckmann.github.com/gm/docs.html#visual) | ||
469 | + - [watermark](http://aheckmann.github.com/gm/docs.html#watermark) | ||
470 | + - [wave](http://aheckmann.github.com/gm/docs.html#wave) | ||
471 | + - [whitePoint](http://aheckmann.github.com/gm/docs.html#whitePoint) | ||
472 | + - [whiteThreshold](http://aheckmann.github.com/gm/docs.html#whiteThreshold) | ||
473 | + - [window](http://aheckmann.github.com/gm/docs.html#window) | ||
474 | + - [windowGroup](http://aheckmann.github.com/gm/docs.html#windowGroup) | ||
475 | + | ||
476 | + - drawing primitives | ||
477 | + - [draw](http://aheckmann.github.com/gm/docs.html#draw) | ||
478 | + - [drawArc](http://aheckmann.github.com/gm/docs.html#drawArc) | ||
479 | + - [drawBezier](http://aheckmann.github.com/gm/docs.html#drawBezier) | ||
480 | + - [drawCircle](http://aheckmann.github.com/gm/docs.html#drawCircle) | ||
481 | + - [drawEllipse](http://aheckmann.github.com/gm/docs.html#drawEllipse) | ||
482 | + - [drawLine](http://aheckmann.github.com/gm/docs.html#drawLine) | ||
483 | + - [drawPoint](http://aheckmann.github.com/gm/docs.html#drawPoint) | ||
484 | + - [drawPolygon](http://aheckmann.github.com/gm/docs.html#drawPolygon) | ||
485 | + - [drawPolyline](http://aheckmann.github.com/gm/docs.html#drawPolyline) | ||
486 | + - [drawRectangle](http://aheckmann.github.com/gm/docs.html#drawRectangle) | ||
487 | + - [drawText](http://aheckmann.github.com/gm/docs.html#drawText) | ||
488 | + - [fill](http://aheckmann.github.com/gm/docs.html#fill) | ||
489 | + - [font](http://aheckmann.github.com/gm/docs.html#font) | ||
490 | + - [fontSize](http://aheckmann.github.com/gm/docs.html#fontSize) | ||
491 | + - [stroke](http://aheckmann.github.com/gm/docs.html#stroke) | ||
492 | + - [strokeWidth](http://aheckmann.github.com/gm/docs.html#strokeWidth) | ||
493 | + - [setDraw](http://aheckmann.github.com/gm/docs.html#setDraw) | ||
494 | + | ||
495 | + - image output | ||
496 | + - **write** - writes the processed image data to the specified filename | ||
497 | + - **stream** - provides a `ReadableStream` with the processed image data | ||
498 | + - **toBuffer** - returns the image as a `Buffer` instead of a stream | ||
499 | + | ||
500 | +##compare | ||
501 | + | ||
502 | +Graphicsmagicks `compare` command is exposed through `gm.compare()`. This allows us to determine if two images can be considered "equal". | ||
503 | + | ||
504 | +Currently `gm.compare` only accepts file paths. | ||
505 | + | ||
506 | + gm.compare(path1, path2 [, options], callback) | ||
507 | + | ||
508 | +```js | ||
509 | +gm.compare('/path/to/image1.jpg', '/path/to/another.png', function (err, isEqual, equality, raw, path1, path2) { | ||
510 | + if (err) return handle(err); | ||
511 | + | ||
512 | + // if the images were considered equal, `isEqual` will be true, otherwise, false. | ||
513 | + console.log('The images were equal: %s', isEqual); | ||
514 | + | ||
515 | + // to see the total equality returned by graphicsmagick we can inspect the `equality` argument. | ||
516 | + console.log('Actual equality: %d', equality); | ||
517 | + | ||
518 | + // inspect the raw output | ||
519 | + console.log(raw); | ||
520 | + | ||
521 | + // print file paths | ||
522 | + console.log(path1, path2); | ||
523 | +}) | ||
524 | +``` | ||
525 | + | ||
526 | +You may wish to pass a custom tolerance threshold to increase or decrease the default level of `0.4`. | ||
527 | + | ||
528 | + | ||
529 | +```js | ||
530 | +gm.compare('/path/to/image1.jpg', '/path/to/another.png', 1.2, function (err, isEqual) { | ||
531 | + ... | ||
532 | +}) | ||
533 | +``` | ||
534 | + | ||
535 | +To output a diff image, pass a configuration object to define the diff options and tolerance. | ||
536 | + | ||
537 | + | ||
538 | +```js | ||
539 | +var options = { | ||
540 | + file: '/path/to/diff.png', | ||
541 | + highlightColor: 'yellow', | ||
542 | + tolerance: 0.02 | ||
543 | +} | ||
544 | +gm.compare('/path/to/image1.jpg', '/path/to/another.png', options, function (err, isEqual, equality, raw) { | ||
545 | + ... | ||
546 | +}) | ||
547 | +``` | ||
548 | + | ||
549 | +##composite | ||
550 | + | ||
551 | +GraphicsMagick supports compositing one image on top of another. This is exposed through `gm.composite()`. Its first argument is an image path with the changes to the base image, and an optional mask image. | ||
552 | + | ||
553 | +Currently, `gm.composite()` only accepts file paths. | ||
554 | + | ||
555 | + gm.composite(other [, mask]) | ||
556 | + | ||
557 | +```js | ||
558 | +gm('/path/to/image.jpg') | ||
559 | +.composite('/path/to/second_image.jpg') | ||
560 | +.geometry('+100+150') | ||
561 | +.write('/path/to/composite.png', function(err) { | ||
562 | + if(!err) console.log("Written composite image."); | ||
563 | +}); | ||
564 | +``` | ||
565 | + | ||
566 | +## Contributors | ||
567 | +[https://github.com/aheckmann/gm/contributors](https://github.com/aheckmann/gm/contributors) | ||
568 | + | ||
569 | +## Inspiration | ||
570 | +http://github.com/quiiver/magickal-node | ||
571 | + | ||
572 | +## Plugins | ||
573 | +[https://github.com/aheckmann/gm/wiki](https://github.com/aheckmann/gm/wiki) | ||
574 | + | ||
575 | +## License | ||
576 | + | ||
577 | +(The MIT License) | ||
578 | + | ||
579 | +Copyright (c) 2010 [Aaron Heckmann](aaron.heckmann+github@gmail.com) | ||
580 | + | ||
581 | +Permission is hereby granted, free of charge, to any person obtaining | ||
582 | +a copy of this software and associated documentation files (the | ||
583 | +'Software'), to deal in the Software without restriction, including | ||
584 | +without limitation the rights to use, copy, modify, merge, publish, | ||
585 | +distribute, sublicense, and/or sell copies of the Software, and to | ||
586 | +permit persons to whom the Software is furnished to do so, subject to | ||
587 | +the following conditions: | ||
588 | + | ||
589 | +The above copyright notice and this permission notice shall be | ||
590 | +included in all copies or substantial portions of the Software. | ||
591 | + | ||
592 | +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, | ||
593 | +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
594 | +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | ||
595 | +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | ||
596 | +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | ||
597 | +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | ||
598 | +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
1 | + | ||
2 | +/** | ||
3 | + * Module dependencies. | ||
4 | + */ | ||
5 | + | ||
6 | +var Stream = require('stream').Stream; | ||
7 | +var EventEmitter = require('events').EventEmitter; | ||
8 | +var util = require('util'); | ||
9 | + | ||
10 | +util.inherits(gm, EventEmitter); | ||
11 | + | ||
12 | +/** | ||
13 | + * Constructor. | ||
14 | + * | ||
15 | + * @param {String|Number} path - path to img source or ReadableStream or width of img to create | ||
16 | + * @param {Number} [height] - optional filename of ReadableStream or height of img to create | ||
17 | + * @param {String} [color] - optional hex background color of created img | ||
18 | + */ | ||
19 | + | ||
20 | +function gm (source, height, color) { | ||
21 | + var width; | ||
22 | + | ||
23 | + if (!(this instanceof gm)) { | ||
24 | + return new gm(source, height, color); | ||
25 | + } | ||
26 | + | ||
27 | + EventEmitter.call(this); | ||
28 | + | ||
29 | + this._options = {}; | ||
30 | + this.options(this.__proto__._options); | ||
31 | + | ||
32 | + this.data = {}; | ||
33 | + this._in = []; | ||
34 | + this._out = []; | ||
35 | + this._outputFormat = null; | ||
36 | + this._subCommand = 'convert'; | ||
37 | + | ||
38 | + if (source instanceof Stream) { | ||
39 | + this.sourceStream = source; | ||
40 | + source = height || 'unknown.jpg'; | ||
41 | + } else if (Buffer.isBuffer(source)) { | ||
42 | + this.sourceBuffer = source; | ||
43 | + source = height || 'unknown.jpg'; | ||
44 | + } else if (height) { | ||
45 | + // new images | ||
46 | + width = source; | ||
47 | + source = ""; | ||
48 | + | ||
49 | + this.in("-size", width + "x" + height); | ||
50 | + | ||
51 | + if (color) { | ||
52 | + this.in("xc:"+ color); | ||
53 | + } | ||
54 | + } | ||
55 | + | ||
56 | + if (typeof source === "string") { | ||
57 | + // then source is a path | ||
58 | + | ||
59 | + // parse out gif frame brackets from filename | ||
60 | + // since stream doesn't use source path | ||
61 | + // eg. "filename.gif[0]" | ||
62 | + var frames = source.match(/(\[.+\])$/); | ||
63 | + if (frames) { | ||
64 | + this.sourceFrames = source.substr(frames.index, frames[0].length); | ||
65 | + source = source.substr(0, frames.index); | ||
66 | + } | ||
67 | + } | ||
68 | + | ||
69 | + this.source = source; | ||
70 | + | ||
71 | + this.addSrcFormatter(function (src) { | ||
72 | + // must be first source formatter | ||
73 | + | ||
74 | + var inputFromStdin = this.sourceStream || this.sourceBuffer; | ||
75 | + var ret = inputFromStdin ? '-' : this.source; | ||
76 | + | ||
77 | + if (ret && this.sourceFrames) ret += this.sourceFrames; | ||
78 | + | ||
79 | + src.length = 0; | ||
80 | + src[0] = ret; | ||
81 | + }); | ||
82 | +} | ||
83 | + | ||
84 | +/** | ||
85 | + * Subclasses the gm constructor with custom options. | ||
86 | + * | ||
87 | + * @param {options} options | ||
88 | + * @return {gm} the subclasses gm constructor | ||
89 | + */ | ||
90 | + | ||
91 | +var parent = gm; | ||
92 | +gm.subClass = function subClass (options) { | ||
93 | + function gm (source, height, color) { | ||
94 | + if (!(this instanceof parent)) { | ||
95 | + return new gm(source, height, color); | ||
96 | + } | ||
97 | + | ||
98 | + parent.call(this, source, height, color); | ||
99 | + } | ||
100 | + | ||
101 | + gm.prototype.__proto__ = parent.prototype; | ||
102 | + gm.prototype._options = {}; | ||
103 | + gm.prototype.options(options); | ||
104 | + | ||
105 | + return gm; | ||
106 | +} | ||
107 | + | ||
108 | +/** | ||
109 | + * Augment the prototype. | ||
110 | + */ | ||
111 | + | ||
112 | +require("./lib/options")(gm.prototype); | ||
113 | +require("./lib/getters")(gm); | ||
114 | +require("./lib/args")(gm.prototype); | ||
115 | +require("./lib/drawing")(gm.prototype); | ||
116 | +require("./lib/convenience")(gm.prototype); | ||
117 | +require("./lib/command")(gm.prototype); | ||
118 | +require("./lib/compare")(gm.prototype); | ||
119 | +require("./lib/composite")(gm.prototype); | ||
120 | + | ||
121 | +/** | ||
122 | + * Expose. | ||
123 | + */ | ||
124 | + | ||
125 | +module.exports = exports = gm; | ||
126 | +module.exports.utils = require('./lib/utils'); | ||
127 | +module.exports.compare = require('./lib/compare')(); | ||
128 | +module.exports.version = JSON.parse( | ||
129 | + require('fs').readFileSync(__dirname + '/package.json', 'utf8') | ||
130 | +).version; | ||
131 | + |
1 | +# Compiled source # | ||
2 | +################### | ||
3 | +*.com | ||
4 | +*.class | ||
5 | +*.dll | ||
6 | +*.exe | ||
7 | +*.o | ||
8 | +*.so | ||
9 | + | ||
10 | +# Packages # | ||
11 | +############ | ||
12 | +# it's better to unpack these files and commit the raw source | ||
13 | +# git has its own built in compression methods | ||
14 | +*.7z | ||
15 | +*.dmg | ||
16 | +*.gz | ||
17 | +*.iso | ||
18 | +*.jar | ||
19 | +*.rar | ||
20 | +*.tar | ||
21 | +*.zip | ||
22 | + | ||
23 | +# Logs and databases # | ||
24 | +###################### | ||
25 | +*.log | ||
26 | +*.sql | ||
27 | +*.sqlite | ||
28 | + | ||
29 | +# OS generated files # | ||
30 | +###################### | ||
31 | +.DS_Store* | ||
32 | +ehthumbs.db | ||
33 | +Icon? | ||
34 | +Thumbs.db | ||
35 | + | ||
36 | +# Node.js # | ||
37 | +########### | ||
38 | +lib-cov | ||
39 | +*.seed | ||
40 | +*.log | ||
41 | +*.csv | ||
42 | +*.dat | ||
43 | +*.out | ||
44 | +*.pid | ||
45 | +*.gz | ||
46 | + | ||
47 | +pids | ||
48 | +logs | ||
49 | +results | ||
50 | + | ||
51 | +node_modules | ||
52 | +npm-debug.log | ||
53 | + | ||
54 | +# Components # | ||
55 | +############## | ||
56 | + | ||
57 | +/build | ||
58 | +/components | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +# Array Series [](https://travis-ci.org/component/array-parallel) | ||
2 | + | ||
3 | +Call an array of asynchronous functions in parallel | ||
4 | + | ||
5 | +### API | ||
6 | + | ||
7 | +#### parallel(fns[, context[, callback]]) | ||
8 | + | ||
9 | +```js | ||
10 | +var parallel = require('array-parallel') | ||
11 | + | ||
12 | +parallel([ | ||
13 | + function (done) { | ||
14 | + done() | ||
15 | + } | ||
16 | +], this, function (err) { | ||
17 | + | ||
18 | +}) | ||
19 | +``` | ||
20 | + | ||
21 | +#### fns | ||
22 | + | ||
23 | +`fns` is an array of functions to call in parallel. | ||
24 | +The argument signature should be: | ||
25 | + | ||
26 | +```js | ||
27 | +function (done) { | ||
28 | + done(new Error()) | ||
29 | + // or | ||
30 | + done(null, result) | ||
31 | +} | ||
32 | +``` | ||
33 | + | ||
34 | +That is, each function should only take a `done` as an argument. | ||
35 | +Each callback should only take an `Error` as the first argument, | ||
36 | +or a value as the second. | ||
37 | + | ||
38 | +#### context | ||
39 | + | ||
40 | +Optional context to pass to each `fn`. | ||
41 | +Basically `fn.call(context, done)`. | ||
42 | + | ||
43 | +#### callback(err, results) | ||
44 | + | ||
45 | +```js | ||
46 | +function (err, results) { | ||
47 | + | ||
48 | +} | ||
49 | +``` | ||
50 | + | ||
51 | +Only argument is an `Error` argument. | ||
52 | +It will be the first error retrieved from all the `fns`. | ||
53 | +`results` will be an array of results from each `fn`, | ||
54 | +thus this could be considered an asynchronous version of `[].map`. | ||
55 | + | ||
56 | +### License | ||
57 | + | ||
58 | +The MIT License (MIT) | ||
59 | + | ||
60 | +Copyright (c) 2013 Jonathan Ong me@jongleberry.com | ||
61 | + | ||
62 | +Permission is hereby granted, free of charge, to any person obtaining a copy | ||
63 | +of this software and associated documentation files (the "Software"), to deal | ||
64 | +in the Software without restriction, including without limitation the rights | ||
65 | +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
66 | +copies of the Software, and to permit persons to whom the Software is | ||
67 | +furnished to do so, subject to the following conditions: | ||
68 | + | ||
69 | +The above copyright notice and this permission notice shall be included in | ||
70 | +all copies or substantial portions of the Software. | ||
71 | + | ||
72 | +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
73 | +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
74 | +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
75 | +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
76 | +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
77 | +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
78 | +THE SOFTWARE. |
1 | +{ | ||
2 | + "name": "array-parallel", | ||
3 | + "description": "Call an array of asynchronous functions in parallel", | ||
4 | + "repo": "array-parallel", | ||
5 | + "version": "0.1.3", | ||
6 | + "main": "index.js", | ||
7 | + "scripts": [ | ||
8 | + "index.js" | ||
9 | + ], | ||
10 | + "license": "MIT" | ||
11 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +module.exports = function parallel(fns, context, callback) { | ||
2 | + if (!callback) { | ||
3 | + if (typeof context === 'function') { | ||
4 | + callback = context | ||
5 | + context = null | ||
6 | + } else { | ||
7 | + callback = noop | ||
8 | + } | ||
9 | + } | ||
10 | + | ||
11 | + var pending = fns && fns.length | ||
12 | + if (!pending) return callback(null, []); | ||
13 | + | ||
14 | + var finished = false | ||
15 | + var results = new Array(pending) | ||
16 | + | ||
17 | + fns.forEach(context ? function (fn, i) { | ||
18 | + fn.call(context, maybeDone(i)) | ||
19 | + } : function (fn, i) { | ||
20 | + fn(maybeDone(i)) | ||
21 | + }) | ||
22 | + | ||
23 | + function maybeDone(i) { | ||
24 | + return function (err, result) { | ||
25 | + if (finished) return; | ||
26 | + | ||
27 | + if (err) { | ||
28 | + callback(err, results) | ||
29 | + finished = true | ||
30 | + return | ||
31 | + } | ||
32 | + | ||
33 | + results[i] = result | ||
34 | + | ||
35 | + if (!--pending) callback(null, results); | ||
36 | + } | ||
37 | + } | ||
38 | +} | ||
39 | + | ||
40 | +function noop() {} |
1 | +{ | ||
2 | + "name": "array-parallel", | ||
3 | + "description": "Call an array of asynchronous functions in parallel", | ||
4 | + "version": "0.1.3", | ||
5 | + "scripts": { | ||
6 | + "test": "node test" | ||
7 | + }, | ||
8 | + "author": { | ||
9 | + "name": "Jonathan Ong", | ||
10 | + "email": "me@jongleberry.com", | ||
11 | + "url": "http://jongleberry.com" | ||
12 | + }, | ||
13 | + "repository": { | ||
14 | + "type": "git", | ||
15 | + "url": "https://github.com/component/array-parallel.git" | ||
16 | + }, | ||
17 | + "bugs": { | ||
18 | + "url": "https://github.com/component/array-parallel/issues", | ||
19 | + "email": "me@jongleberry.com" | ||
20 | + }, | ||
21 | + "license": "MIT", | ||
22 | + "homepage": "https://github.com/component/array-parallel", | ||
23 | + "_id": "array-parallel@0.1.3", | ||
24 | + "dist": { | ||
25 | + "shasum": "8f785308926ed5aa478c47e64d1b334b6c0c947d", | ||
26 | + "tarball": "http://registry.npmjs.org/array-parallel/-/array-parallel-0.1.3.tgz" | ||
27 | + }, | ||
28 | + "_from": "array-parallel@~0.1.0", | ||
29 | + "_npmVersion": "1.3.17", | ||
30 | + "_npmUser": { | ||
31 | + "name": "jongleberry", | ||
32 | + "email": "jonathanrichardong@gmail.com" | ||
33 | + }, | ||
34 | + "maintainers": [ | ||
35 | + { | ||
36 | + "name": "jongleberry", | ||
37 | + "email": "jonathanrichardong@gmail.com" | ||
38 | + } | ||
39 | + ], | ||
40 | + "directories": {}, | ||
41 | + "_shasum": "8f785308926ed5aa478c47e64d1b334b6c0c947d", | ||
42 | + "_resolved": "https://registry.npmjs.org/array-parallel/-/array-parallel-0.1.3.tgz" | ||
43 | +} |
1 | +var assert = require('assert') | ||
2 | +var parallel = require('./') | ||
3 | + | ||
4 | +var a, b, c | ||
5 | +parallel([ | ||
6 | + function (done) { | ||
7 | + setTimeout(function () { | ||
8 | + done(null, a = 0) | ||
9 | + }, 5) | ||
10 | + }, | ||
11 | + function (done) { | ||
12 | + setTimeout(function () { | ||
13 | + done(null, b = 1) | ||
14 | + }, 10) | ||
15 | + }, | ||
16 | + function (done) { | ||
17 | + setTimeout(function () { | ||
18 | + done(null, c = 2) | ||
19 | + }, 15) | ||
20 | + } | ||
21 | +], function (err, results) { | ||
22 | + assert.equal(a, 0) | ||
23 | + assert.equal(b, 1) | ||
24 | + assert.equal(c, 2) | ||
25 | + | ||
26 | + assert.deepEqual(results, [0, 1, 2]) | ||
27 | +}) | ||
28 | + | ||
29 | +var d, e | ||
30 | +parallel([ | ||
31 | + function (done) { | ||
32 | + setTimeout(function () { | ||
33 | + d = 1 | ||
34 | + done(new Error('message')) | ||
35 | + }, 5) | ||
36 | + }, | ||
37 | + function (done) { | ||
38 | + setTimeout(function () { | ||
39 | + e = 2 | ||
40 | + done() | ||
41 | + }, 10) | ||
42 | + } | ||
43 | +], function (err) { | ||
44 | + assert.equal(err.message, 'message') | ||
45 | + assert.equal(d, 1) | ||
46 | + assert.equal(e, undefined) | ||
47 | +}) | ||
48 | + | ||
49 | +var context = 'hello' | ||
50 | +parallel([function (done) { | ||
51 | + assert.equal(this, context) | ||
52 | +}], context) | ||
53 | + | ||
54 | +var f | ||
55 | +parallel([function (done) { | ||
56 | + f = true | ||
57 | + done() | ||
58 | +}]) | ||
59 | + | ||
60 | +process.nextTick(function () { | ||
61 | + assert.equal(f, true) | ||
62 | +}) | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +# Compiled source # | ||
2 | +################### | ||
3 | +*.com | ||
4 | +*.class | ||
5 | +*.dll | ||
6 | +*.exe | ||
7 | +*.o | ||
8 | +*.so | ||
9 | + | ||
10 | +# Packages # | ||
11 | +############ | ||
12 | +# it's better to unpack these files and commit the raw source | ||
13 | +# git has its own built in compression methods | ||
14 | +*.7z | ||
15 | +*.dmg | ||
16 | +*.gz | ||
17 | +*.iso | ||
18 | +*.jar | ||
19 | +*.rar | ||
20 | +*.tar | ||
21 | +*.zip | ||
22 | + | ||
23 | +# Logs and databases # | ||
24 | +###################### | ||
25 | +*.log | ||
26 | +*.sql | ||
27 | +*.sqlite | ||
28 | + | ||
29 | +# OS generated files # | ||
30 | +###################### | ||
31 | +.DS_Store* | ||
32 | +ehthumbs.db | ||
33 | +Icon? | ||
34 | +Thumbs.db | ||
35 | + | ||
36 | +# Node.js # | ||
37 | +########### | ||
38 | +lib-cov | ||
39 | +*.seed | ||
40 | +*.log | ||
41 | +*.csv | ||
42 | +*.dat | ||
43 | +*.out | ||
44 | +*.pid | ||
45 | +*.gz | ||
46 | + | ||
47 | +pids | ||
48 | +logs | ||
49 | +results | ||
50 | + | ||
51 | +node_modules | ||
52 | +npm-debug.log | ||
53 | + | ||
54 | +# Components # | ||
55 | +############## | ||
56 | + | ||
57 | +/build | ||
58 | +/components | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +# Array Series [](https://travis-ci.org/component/array-series) | ||
2 | + | ||
3 | +Call an array of asynchronous functions in series | ||
4 | + | ||
5 | +### API | ||
6 | + | ||
7 | +#### series(fns[, context[, callback]]) | ||
8 | + | ||
9 | +```js | ||
10 | +var series = require('array-series') | ||
11 | + | ||
12 | +series([ | ||
13 | + function (done) { | ||
14 | + done() | ||
15 | + } | ||
16 | +], this, function (err) { | ||
17 | + | ||
18 | +}) | ||
19 | +``` | ||
20 | + | ||
21 | +#### fns | ||
22 | + | ||
23 | +`fns` is an array of functions to call in series. | ||
24 | +The argument signature should be: | ||
25 | + | ||
26 | +```js | ||
27 | +function (done) { | ||
28 | + done(new Error()) | ||
29 | + // or | ||
30 | + done() | ||
31 | +} | ||
32 | +``` | ||
33 | + | ||
34 | +That is, each function should only take a `done` as an argument. | ||
35 | +Each callback should only take an optional `Error` as an argument. | ||
36 | + | ||
37 | +#### context | ||
38 | + | ||
39 | +Optional context to pass to each `fn`. | ||
40 | +Basically `fn.call(context, done)`. | ||
41 | + | ||
42 | +#### callback(err) | ||
43 | + | ||
44 | +```js | ||
45 | +function (err) { | ||
46 | + | ||
47 | +} | ||
48 | +``` | ||
49 | + | ||
50 | +Only argument is an `Error` argument. | ||
51 | +It will return the first error in the series of functions that returns an error, | ||
52 | +and no function after will be called. | ||
53 | + | ||
54 | +### License | ||
55 | + | ||
56 | +The MIT License (MIT) | ||
57 | + | ||
58 | +Copyright (c) 2013 Jonathan Ong me@jongleberry.com | ||
59 | + | ||
60 | +Permission is hereby granted, free of charge, to any person obtaining a copy | ||
61 | +of this software and associated documentation files (the "Software"), to deal | ||
62 | +in the Software without restriction, including without limitation the rights | ||
63 | +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
64 | +copies of the Software, and to permit persons to whom the Software is | ||
65 | +furnished to do so, subject to the following conditions: | ||
66 | + | ||
67 | +The above copyright notice and this permission notice shall be included in | ||
68 | +all copies or substantial portions of the Software. | ||
69 | + | ||
70 | +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
71 | +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
72 | +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
73 | +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
74 | +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
75 | +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
76 | +THE SOFTWARE. | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +{ | ||
2 | + "name": "array-series", | ||
3 | + "description": "Call an array of asynchronous functions in series", | ||
4 | + "repo": "component/array-series", | ||
5 | + "version": "0.1.5", | ||
6 | + "main": "index.js", | ||
7 | + "scripts": [ | ||
8 | + "index.js" | ||
9 | + ], | ||
10 | + "license": "MIT" | ||
11 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +module.exports = function series(fns, context, callback) { | ||
2 | + if (!callback) { | ||
3 | + if (typeof context === 'function') { | ||
4 | + callback = context | ||
5 | + context = null | ||
6 | + } else { | ||
7 | + callback = noop | ||
8 | + } | ||
9 | + } | ||
10 | + | ||
11 | + if (!(fns && fns.length)) return callback(); | ||
12 | + | ||
13 | + fns = fns.slice(0) | ||
14 | + | ||
15 | + var call = context | ||
16 | + ? function () { | ||
17 | + fns.length | ||
18 | + ? fns.shift().call(context, next) | ||
19 | + : callback() | ||
20 | + } | ||
21 | + : function () { | ||
22 | + fns.length | ||
23 | + ? fns.shift()(next) | ||
24 | + : callback() | ||
25 | + } | ||
26 | + | ||
27 | + call() | ||
28 | + | ||
29 | + function next(err) { | ||
30 | + err ? callback(err) : call() | ||
31 | + } | ||
32 | +} | ||
33 | + | ||
34 | +function noop() {} |
1 | +{ | ||
2 | + "name": "array-series", | ||
3 | + "description": "Call an array of asynchronous functions in series", | ||
4 | + "version": "0.1.5", | ||
5 | + "scripts": { | ||
6 | + "test": "node test" | ||
7 | + }, | ||
8 | + "author": { | ||
9 | + "name": "Jonathan Ong", | ||
10 | + "email": "me@jongleberry.com", | ||
11 | + "url": "http://jongleberry.com" | ||
12 | + }, | ||
13 | + "repository": { | ||
14 | + "type": "git", | ||
15 | + "url": "https://github.com/component/array-series.git" | ||
16 | + }, | ||
17 | + "bugs": { | ||
18 | + "url": "https://github.com/component/array-series/issues", | ||
19 | + "email": "me@jongleberry.com" | ||
20 | + }, | ||
21 | + "license": "MIT", | ||
22 | + "homepage": "https://github.com/component/array-series", | ||
23 | + "_id": "array-series@0.1.5", | ||
24 | + "dist": { | ||
25 | + "shasum": "df5d37bfc5c2ef0755e2aa4f92feae7d4b5a972f", | ||
26 | + "tarball": "http://registry.npmjs.org/array-series/-/array-series-0.1.5.tgz" | ||
27 | + }, | ||
28 | + "_from": "array-series@~0.1.0", | ||
29 | + "_npmVersion": "1.3.17", | ||
30 | + "_npmUser": { | ||
31 | + "name": "jongleberry", | ||
32 | + "email": "jonathanrichardong@gmail.com" | ||
33 | + }, | ||
34 | + "maintainers": [ | ||
35 | + { | ||
36 | + "name": "jongleberry", | ||
37 | + "email": "jonathanrichardong@gmail.com" | ||
38 | + } | ||
39 | + ], | ||
40 | + "directories": {}, | ||
41 | + "_shasum": "df5d37bfc5c2ef0755e2aa4f92feae7d4b5a972f", | ||
42 | + "_resolved": "https://registry.npmjs.org/array-series/-/array-series-0.1.5.tgz" | ||
43 | +} |
1 | +var assert = require('assert') | ||
2 | +var series = require('./') | ||
3 | + | ||
4 | +var a, b, c | ||
5 | + | ||
6 | +series([ | ||
7 | + function (done) { | ||
8 | + a = 1 | ||
9 | + process.nextTick(done) | ||
10 | + check('a') | ||
11 | + }, | ||
12 | + function (done) { | ||
13 | + b = 2 | ||
14 | + process.nextTick(done) | ||
15 | + check('b') | ||
16 | + }, | ||
17 | + function (done) { | ||
18 | + c = 3 | ||
19 | + process.nextTick(done) | ||
20 | + check('c') | ||
21 | + } | ||
22 | +], function (err) { | ||
23 | + assert.ifError(err) | ||
24 | + assert.equal(a, 1) | ||
25 | + assert.equal(b, 2) | ||
26 | + assert.equal(c, 3) | ||
27 | +}) | ||
28 | + | ||
29 | +function check(x) { | ||
30 | + switch (x) { | ||
31 | + case 'a': | ||
32 | + assert.equal(a, 1) | ||
33 | + assert.equal(b, undefined) | ||
34 | + assert.equal(c, undefined) | ||
35 | + break | ||
36 | + case 'b': | ||
37 | + assert.equal(a, 1) | ||
38 | + assert.equal(b, 2) | ||
39 | + assert.equal(c, undefined) | ||
40 | + break | ||
41 | + case 'c': | ||
42 | + assert.equal(a, 1) | ||
43 | + assert.equal(b, 2) | ||
44 | + assert.equal(c, 3) | ||
45 | + break | ||
46 | + } | ||
47 | +} | ||
48 | + | ||
49 | +var context = 'hello' | ||
50 | +series([function (done) { | ||
51 | + assert.equal(this, context) | ||
52 | + done() | ||
53 | +}], context) | ||
54 | + | ||
55 | +var finished | ||
56 | +series([], function (err) { | ||
57 | + finished = true | ||
58 | +}) | ||
59 | + | ||
60 | +process.nextTick(function () { | ||
61 | + if (!finished) | ||
62 | + throw new Error('Failed with no functions.'); | ||
63 | +}) | ||
64 | + | ||
65 | +var r, d, o | ||
66 | +series([ | ||
67 | + function (done) { | ||
68 | + r = 1 | ||
69 | + process.nextTick(done) | ||
70 | + }, | ||
71 | + function (done) { | ||
72 | + d = 0 | ||
73 | + process.nextTick(function () { | ||
74 | + done(new Error('message')) | ||
75 | + }) | ||
76 | + }, | ||
77 | + function (done) { | ||
78 | + o = 0 | ||
79 | + process.nextTick(done) | ||
80 | + } | ||
81 | +], function (err) { | ||
82 | + assert.equal(err.message, 'message') | ||
83 | + assert.equal(r, 1) | ||
84 | + assert.equal(d, 0) | ||
85 | + assert.equal(o, undefined) | ||
86 | +}) | ||
87 | + | ||
88 | +console.log('Array series tests pass!') | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | + | ||
2 | +0.7.0 / 2012-05-04 | ||
3 | +================== | ||
4 | + | ||
5 | + * Added .component to package.json | ||
6 | + * Added debug.component.js build | ||
7 | + | ||
8 | +0.6.0 / 2012-03-16 | ||
9 | +================== | ||
10 | + | ||
11 | + * Added support for "-" prefix in DEBUG [Vinay Pulim] | ||
12 | + * Added `.enabled` flag to the node version [TooTallNate] | ||
13 | + | ||
14 | +0.5.0 / 2012-02-02 | ||
15 | +================== | ||
16 | + | ||
17 | + * Added: humanize diffs. Closes #8 | ||
18 | + * Added `debug.disable()` to the CS variant | ||
19 | + * Removed padding. Closes #10 | ||
20 | + * Fixed: persist client-side variant again. Closes #9 | ||
21 | + | ||
22 | +0.4.0 / 2012-02-01 | ||
23 | +================== | ||
24 | + | ||
25 | + * Added browser variant support for older browsers [TooTallNate] | ||
26 | + * Added `debug.enable('project:*')` to browser variant [TooTallNate] | ||
27 | + * Added padding to diff (moved it to the right) | ||
28 | + | ||
29 | +0.3.0 / 2012-01-26 | ||
30 | +================== | ||
31 | + | ||
32 | + * Added millisecond diff when isatty, otherwise UTC string | ||
33 | + | ||
34 | +0.2.0 / 2012-01-22 | ||
35 | +================== | ||
36 | + | ||
37 | + * Added wildcard support | ||
38 | + | ||
39 | +0.1.0 / 2011-12-02 | ||
40 | +================== | ||
41 | + | ||
42 | + * Added: remove colors unless stderr isatty [TooTallNate] | ||
43 | + | ||
44 | +0.0.1 / 2010-01-03 | ||
45 | +================== | ||
46 | + | ||
47 | + * Initial release |
1 | + | ||
2 | +# debug | ||
3 | + | ||
4 | + tiny node.js debugging utility. | ||
5 | + | ||
6 | +## Installation | ||
7 | + | ||
8 | +``` | ||
9 | +$ npm install debug | ||
10 | +``` | ||
11 | + | ||
12 | +## Example | ||
13 | + | ||
14 | + This module is modelled after node core's debugging technique, allowing you to enable one or more topic-specific debugging functions, for example core does the following within many modules: | ||
15 | + | ||
16 | +```js | ||
17 | +var debug; | ||
18 | +if (process.env.NODE_DEBUG && /cluster/.test(process.env.NODE_DEBUG)) { | ||
19 | + debug = function(x) { | ||
20 | + var prefix = process.pid + ',' + | ||
21 | + (process.env.NODE_WORKER_ID ? 'Worker' : 'Master'); | ||
22 | + console.error(prefix, x); | ||
23 | + }; | ||
24 | +} else { | ||
25 | + debug = function() { }; | ||
26 | +} | ||
27 | +``` | ||
28 | + | ||
29 | + This concept is extremely simple but it works well. With `debug` you simply invoke the exported function to generate your debug function, passing it a name which will determine if a noop function is returned, or a decorated `console.error`, so all of the `console` format string goodies you're used to work fine. A unique color is selected per-function for visibility. | ||
30 | + | ||
31 | +Example _app.js_: | ||
32 | + | ||
33 | +```js | ||
34 | +var debug = require('debug')('http') | ||
35 | + , http = require('http') | ||
36 | + , name = 'My App'; | ||
37 | + | ||
38 | +// fake app | ||
39 | + | ||
40 | +debug('booting %s', name); | ||
41 | + | ||
42 | +http.createServer(function(req, res){ | ||
43 | + debug(req.method + ' ' + req.url); | ||
44 | + res.end('hello\n'); | ||
45 | +}).listen(3000, function(){ | ||
46 | + debug('listening'); | ||
47 | +}); | ||
48 | + | ||
49 | +// fake worker of some kind | ||
50 | + | ||
51 | +require('./worker'); | ||
52 | +``` | ||
53 | + | ||
54 | +Example _worker.js_: | ||
55 | + | ||
56 | +```js | ||
57 | +var debug = require('debug')('worker'); | ||
58 | + | ||
59 | +setInterval(function(){ | ||
60 | + debug('doing some work'); | ||
61 | +}, 1000); | ||
62 | +``` | ||
63 | + | ||
64 | + The __DEBUG__ environment variable is then used to enable these based on space or comma-delimited names. Here are some examples: | ||
65 | + | ||
66 | +  | ||
67 | + | ||
68 | +  | ||
69 | + | ||
70 | +## Millisecond diff | ||
71 | + | ||
72 | + When actively developing an application it can be useful to see when the time spent between one `debug()` call and the next. Suppose for example you invoke `debug()` before requesting a resource, and after as well, the "+NNNms" will show you how much time was spent between calls. | ||
73 | + | ||
74 | +  | ||
75 | + | ||
76 | + When stdout is not a TTY, `Date#toUTCString()` is used, making it more useful for logging the debug information as shown below: | ||
77 | + | ||
78 | +  | ||
79 | + | ||
80 | +## Conventions | ||
81 | + | ||
82 | + If you're using this in one or more of your libraries, you _should_ use the name of your library so that developers may toggle debugging as desired without guessing names. If you have more than one debuggers you _should_ prefix them with your library name and use ":" to separate features. For example "bodyParser" from Connect would then be "connect:bodyParser". | ||
83 | + | ||
84 | +## Wildcards | ||
85 | + | ||
86 | + The "*" character may be used as a wildcard. Suppose for example your library has debuggers named "connect:bodyParser", "connect:compress", "connect:session", instead of listing all three with `DEBUG=connect:bodyParser,connect.compress,connect:session`, you may simply do `DEBUG=connect:*`, or to run everything using this module simply use `DEBUG=*`. | ||
87 | + | ||
88 | + You can also exclude specific debuggers by prefixing them with a "-" character. For example, `DEBUG=* -connect:*` would include all debuggers except those starting with "connect:". | ||
89 | + | ||
90 | +## Browser support | ||
91 | + | ||
92 | + Debug works in the browser as well, currently persisted by `localStorage`. For example if you have `worker:a` and `worker:b` as shown below, and wish to debug both type `debug.enable('worker:*')` in the console and refresh the page, this will remain until you disable with `debug.disable()`. | ||
93 | + | ||
94 | +```js | ||
95 | +a = debug('worker:a'); | ||
96 | +b = debug('worker:b'); | ||
97 | + | ||
98 | +setInterval(function(){ | ||
99 | + a('doing some work'); | ||
100 | +}, 1000); | ||
101 | + | ||
102 | +setInterval(function(){ | ||
103 | + a('doing some work'); | ||
104 | +}, 1200); | ||
105 | +``` | ||
106 | + | ||
107 | +## License | ||
108 | + | ||
109 | +(The MIT License) | ||
110 | + | ||
111 | +Copyright (c) 2011 TJ Holowaychuk <tj@vision-media.ca> | ||
112 | + | ||
113 | +Permission is hereby granted, free of charge, to any person obtaining | ||
114 | +a copy of this software and associated documentation files (the | ||
115 | +'Software'), to deal in the Software without restriction, including | ||
116 | +without limitation the rights to use, copy, modify, merge, publish, | ||
117 | +distribute, sublicense, and/or sell copies of the Software, and to | ||
118 | +permit persons to whom the Software is furnished to do so, subject to | ||
119 | +the following conditions: | ||
120 | + | ||
121 | +The above copyright notice and this permission notice shall be | ||
122 | +included in all copies or substantial portions of the Software. | ||
123 | + | ||
124 | +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, | ||
125 | +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
126 | +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | ||
127 | +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | ||
128 | +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | ||
129 | +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | ||
130 | +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +;(function(){ | ||
2 | + | ||
3 | +/** | ||
4 | + * Create a debugger with the given `name`. | ||
5 | + * | ||
6 | + * @param {String} name | ||
7 | + * @return {Type} | ||
8 | + * @api public | ||
9 | + */ | ||
10 | + | ||
11 | +function debug(name) { | ||
12 | + if (!debug.enabled(name)) return function(){}; | ||
13 | + | ||
14 | + return function(fmt){ | ||
15 | + var curr = new Date; | ||
16 | + var ms = curr - (debug[name] || curr); | ||
17 | + debug[name] = curr; | ||
18 | + | ||
19 | + fmt = name | ||
20 | + + ' ' | ||
21 | + + fmt | ||
22 | + + ' +' + debug.humanize(ms); | ||
23 | + | ||
24 | + // This hackery is required for IE8 | ||
25 | + // where `console.log` doesn't have 'apply' | ||
26 | + window.console | ||
27 | + && console.log | ||
28 | + && Function.prototype.apply.call(console.log, console, arguments); | ||
29 | + } | ||
30 | +} | ||
31 | + | ||
32 | +/** | ||
33 | + * The currently active debug mode names. | ||
34 | + */ | ||
35 | + | ||
36 | +debug.names = []; | ||
37 | +debug.skips = []; | ||
38 | + | ||
39 | +/** | ||
40 | + * Enables a debug mode by name. This can include modes | ||
41 | + * separated by a colon and wildcards. | ||
42 | + * | ||
43 | + * @param {String} name | ||
44 | + * @api public | ||
45 | + */ | ||
46 | + | ||
47 | +debug.enable = function(name) { | ||
48 | + localStorage.debug = name; | ||
49 | + | ||
50 | + var split = (name || '').split(/[\s,]+/) | ||
51 | + , len = split.length; | ||
52 | + | ||
53 | + for (var i = 0; i < len; i++) { | ||
54 | + name = split[i].replace('*', '.*?'); | ||
55 | + if (name[0] === '-') { | ||
56 | + debug.skips.push(new RegExp('^' + name.substr(1) + '$')); | ||
57 | + } | ||
58 | + else { | ||
59 | + debug.names.push(new RegExp('^' + name + '$')); | ||
60 | + } | ||
61 | + } | ||
62 | +}; | ||
63 | + | ||
64 | +/** | ||
65 | + * Disable debug output. | ||
66 | + * | ||
67 | + * @api public | ||
68 | + */ | ||
69 | + | ||
70 | +debug.disable = function(){ | ||
71 | + debug.enable(''); | ||
72 | +}; | ||
73 | + | ||
74 | +/** | ||
75 | + * Humanize the given `ms`. | ||
76 | + * | ||
77 | + * @param {Number} m | ||
78 | + * @return {String} | ||
79 | + * @api private | ||
80 | + */ | ||
81 | + | ||
82 | +debug.humanize = function(ms) { | ||
83 | + var sec = 1000 | ||
84 | + , min = 60 * 1000 | ||
85 | + , hour = 60 * min; | ||
86 | + | ||
87 | + if (ms >= hour) return (ms / hour).toFixed(1) + 'h'; | ||
88 | + if (ms >= min) return (ms / min).toFixed(1) + 'm'; | ||
89 | + if (ms >= sec) return (ms / sec | 0) + 's'; | ||
90 | + return ms + 'ms'; | ||
91 | +}; | ||
92 | + | ||
93 | +/** | ||
94 | + * Returns true if the given mode name is enabled, false otherwise. | ||
95 | + * | ||
96 | + * @param {String} name | ||
97 | + * @return {Boolean} | ||
98 | + * @api public | ||
99 | + */ | ||
100 | + | ||
101 | +debug.enabled = function(name) { | ||
102 | + for (var i = 0, len = debug.skips.length; i < len; i++) { | ||
103 | + if (debug.skips[i].test(name)) { | ||
104 | + return false; | ||
105 | + } | ||
106 | + } | ||
107 | + for (var i = 0, len = debug.names.length; i < len; i++) { | ||
108 | + if (debug.names[i].test(name)) { | ||
109 | + return true; | ||
110 | + } | ||
111 | + } | ||
112 | + return false; | ||
113 | +}; | ||
114 | + | ||
115 | +// persist | ||
116 | + | ||
117 | +if (window.localStorage) debug.enable(localStorage.debug); | ||
118 | + module.exports = debug; | ||
119 | + | ||
120 | +})(); | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | + | ||
2 | +/** | ||
3 | + * Create a debugger with the given `name`. | ||
4 | + * | ||
5 | + * @param {String} name | ||
6 | + * @return {Type} | ||
7 | + * @api public | ||
8 | + */ | ||
9 | + | ||
10 | +function debug(name) { | ||
11 | + if (!debug.enabled(name)) return function(){}; | ||
12 | + | ||
13 | + return function(fmt){ | ||
14 | + var curr = new Date; | ||
15 | + var ms = curr - (debug[name] || curr); | ||
16 | + debug[name] = curr; | ||
17 | + | ||
18 | + fmt = name | ||
19 | + + ' ' | ||
20 | + + fmt | ||
21 | + + ' +' + debug.humanize(ms); | ||
22 | + | ||
23 | + // This hackery is required for IE8 | ||
24 | + // where `console.log` doesn't have 'apply' | ||
25 | + window.console | ||
26 | + && console.log | ||
27 | + && Function.prototype.apply.call(console.log, console, arguments); | ||
28 | + } | ||
29 | +} | ||
30 | + | ||
31 | +/** | ||
32 | + * The currently active debug mode names. | ||
33 | + */ | ||
34 | + | ||
35 | +debug.names = []; | ||
36 | +debug.skips = []; | ||
37 | + | ||
38 | +/** | ||
39 | + * Enables a debug mode by name. This can include modes | ||
40 | + * separated by a colon and wildcards. | ||
41 | + * | ||
42 | + * @param {String} name | ||
43 | + * @api public | ||
44 | + */ | ||
45 | + | ||
46 | +debug.enable = function(name) { | ||
47 | + localStorage.debug = name; | ||
48 | + | ||
49 | + var split = (name || '').split(/[\s,]+/) | ||
50 | + , len = split.length; | ||
51 | + | ||
52 | + for (var i = 0; i < len; i++) { | ||
53 | + name = split[i].replace('*', '.*?'); | ||
54 | + if (name[0] === '-') { | ||
55 | + debug.skips.push(new RegExp('^' + name.substr(1) + '$')); | ||
56 | + } | ||
57 | + else { | ||
58 | + debug.names.push(new RegExp('^' + name + '$')); | ||
59 | + } | ||
60 | + } | ||
61 | +}; | ||
62 | + | ||
63 | +/** | ||
64 | + * Disable debug output. | ||
65 | + * | ||
66 | + * @api public | ||
67 | + */ | ||
68 | + | ||
69 | +debug.disable = function(){ | ||
70 | + debug.enable(''); | ||
71 | +}; | ||
72 | + | ||
73 | +/** | ||
74 | + * Humanize the given `ms`. | ||
75 | + * | ||
76 | + * @param {Number} m | ||
77 | + * @return {String} | ||
78 | + * @api private | ||
79 | + */ | ||
80 | + | ||
81 | +debug.humanize = function(ms) { | ||
82 | + var sec = 1000 | ||
83 | + , min = 60 * 1000 | ||
84 | + , hour = 60 * min; | ||
85 | + | ||
86 | + if (ms >= hour) return (ms / hour).toFixed(1) + 'h'; | ||
87 | + if (ms >= min) return (ms / min).toFixed(1) + 'm'; | ||
88 | + if (ms >= sec) return (ms / sec | 0) + 's'; | ||
89 | + return ms + 'ms'; | ||
90 | +}; | ||
91 | + | ||
92 | +/** | ||
93 | + * Returns true if the given mode name is enabled, false otherwise. | ||
94 | + * | ||
95 | + * @param {String} name | ||
96 | + * @return {Boolean} | ||
97 | + * @api public | ||
98 | + */ | ||
99 | + | ||
100 | +debug.enabled = function(name) { | ||
101 | + for (var i = 0, len = debug.skips.length; i < len; i++) { | ||
102 | + if (debug.skips[i].test(name)) { | ||
103 | + return false; | ||
104 | + } | ||
105 | + } | ||
106 | + for (var i = 0, len = debug.names.length; i < len; i++) { | ||
107 | + if (debug.names[i].test(name)) { | ||
108 | + return true; | ||
109 | + } | ||
110 | + } | ||
111 | + return false; | ||
112 | +}; | ||
113 | + | ||
114 | +// persist | ||
115 | + | ||
116 | +if (window.localStorage) debug.enable(localStorage.debug); | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | + | ||
2 | +var debug = require('../')('http') | ||
3 | + , http = require('http') | ||
4 | + , name = 'My App'; | ||
5 | + | ||
6 | +// fake app | ||
7 | + | ||
8 | +debug('booting %s', name); | ||
9 | + | ||
10 | +http.createServer(function(req, res){ | ||
11 | + debug(req.method + ' ' + req.url); | ||
12 | + res.end('hello\n'); | ||
13 | +}).listen(3000, function(){ | ||
14 | + debug('listening'); | ||
15 | +}); | ||
16 | + | ||
17 | +// fake worker of some kind | ||
18 | + | ||
19 | +require('./worker'); | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +<html> | ||
2 | + <head> | ||
3 | + <title>debug()</title> | ||
4 | + <script src="../debug.js"></script> | ||
5 | + <script> | ||
6 | + // type debug.enable('*') in | ||
7 | + // the console and refresh :) | ||
8 | + | ||
9 | + a = debug('worker:a'); | ||
10 | + b = debug('worker:b'); | ||
11 | + | ||
12 | + setInterval(function(){ | ||
13 | + a('doing some work'); | ||
14 | + }, 1000); | ||
15 | + | ||
16 | + setInterval(function(){ | ||
17 | + a('doing some work'); | ||
18 | + }, 1200); | ||
19 | + </script> | ||
20 | + </head> | ||
21 | + <body> | ||
22 | + | ||
23 | + </body> | ||
24 | +</html> |
1 | + | ||
2 | +// DEBUG=* node example/worker | ||
3 | +// DEBUG=worker:* node example/worker | ||
4 | +// DEBUG=worker:a node example/worker | ||
5 | +// DEBUG=worker:b node example/worker | ||
6 | + | ||
7 | +var a = require('../')('worker:a') | ||
8 | + , b = require('../')('worker:b'); | ||
9 | + | ||
10 | +function work() { | ||
11 | + a('doing lots of uninteresting work'); | ||
12 | + setTimeout(work, Math.random() * 1000); | ||
13 | +} | ||
14 | + | ||
15 | +work(); | ||
16 | + | ||
17 | +function workb() { | ||
18 | + b('doing some work'); | ||
19 | + setTimeout(workb, Math.random() * 2000); | ||
20 | +} | ||
21 | + | ||
22 | +workb(); | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | +;(function(){ |
1 | +{ | ||
2 | + "name": "debug", | ||
3 | + "version": "0.7.0", | ||
4 | + "description": "small debugging utility", | ||
5 | + "keywords": [ | ||
6 | + "debug", | ||
7 | + "log", | ||
8 | + "debugger" | ||
9 | + ], | ||
10 | + "author": { | ||
11 | + "name": "TJ Holowaychuk", | ||
12 | + "email": "tj@vision-media.ca" | ||
13 | + }, | ||
14 | + "dependencies": {}, | ||
15 | + "devDependencies": { | ||
16 | + "mocha": "*" | ||
17 | + }, | ||
18 | + "main": "index", | ||
19 | + "browserify": "debug.component.js", | ||
20 | + "engines": { | ||
21 | + "node": "*" | ||
22 | + }, | ||
23 | + "component": { | ||
24 | + "scripts": { | ||
25 | + "debug": "debug.component.js" | ||
26 | + } | ||
27 | + }, | ||
28 | + "_id": "debug@0.7.0", | ||
29 | + "dist": { | ||
30 | + "shasum": "f5be05ec0434c992d79940e50b2695cfb2e01b08", | ||
31 | + "tarball": "http://registry.npmjs.org/debug/-/debug-0.7.0.tgz" | ||
32 | + }, | ||
33 | + "maintainers": [ | ||
34 | + { | ||
35 | + "name": "tjholowaychuk", | ||
36 | + "email": "tj@vision-media.ca" | ||
37 | + } | ||
38 | + ], | ||
39 | + "directories": {}, | ||
40 | + "_shasum": "f5be05ec0434c992d79940e50b2695cfb2e01b08", | ||
41 | + "_from": "debug@0.7.0", | ||
42 | + "_resolved": "https://registry.npmjs.org/debug/-/debug-0.7.0.tgz" | ||
43 | +} |
1 | +Apache License, Version 2.0 | ||
2 | + | ||
3 | +Copyright (c) 2011 Dominic Tarr | ||
4 | + | ||
5 | +Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | +you may not use this file except in compliance with the License. | ||
7 | +You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | +Unless required by applicable law or agreed to in writing, software | ||
12 | +distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | +See the License for the specific language governing permissions and | ||
15 | +limitations under the License. |
1 | +The MIT License | ||
2 | + | ||
3 | +Copyright (c) 2011 Dominic Tarr | ||
4 | + | ||
5 | +Permission is hereby granted, free of charge, | ||
6 | +to any person obtaining a copy of this software and | ||
7 | +associated documentation files (the "Software"), to | ||
8 | +deal in the Software without restriction, including | ||
9 | +without limitation the rights to use, copy, modify, | ||
10 | +merge, publish, distribute, sublicense, and/or sell | ||
11 | +copies of the Software, and to permit persons to whom | ||
12 | +the Software is furnished to do so, | ||
13 | +subject to the following conditions: | ||
14 | + | ||
15 | +The above copyright notice and this permission notice | ||
16 | +shall be included in all copies or substantial portions of the Software. | ||
17 | + | ||
18 | +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
19 | +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES | ||
20 | +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | ||
21 | +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR | ||
22 | +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | ||
23 | +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | ||
24 | +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
1 | +var Stream = require('stream') | ||
2 | + | ||
3 | +// through | ||
4 | +// | ||
5 | +// a stream that does nothing but re-emit the input. | ||
6 | +// useful for aggregating a series of changing but not ending streams into one stream) | ||
7 | + | ||
8 | +exports = module.exports = through | ||
9 | +through.through = through | ||
10 | + | ||
11 | +//create a readable writable stream. | ||
12 | + | ||
13 | +function through (write, end, opts) { | ||
14 | + write = write || function (data) { this.queue(data) } | ||
15 | + end = end || function () { this.queue(null) } | ||
16 | + | ||
17 | + var ended = false, destroyed = false, buffer = [], _ended = false | ||
18 | + var stream = new Stream() | ||
19 | + stream.readable = stream.writable = true | ||
20 | + stream.paused = false | ||
21 | + | ||
22 | +// stream.autoPause = !(opts && opts.autoPause === false) | ||
23 | + stream.autoDestroy = !(opts && opts.autoDestroy === false) | ||
24 | + | ||
25 | + stream.write = function (data) { | ||
26 | + write.call(this, data) | ||
27 | + return !stream.paused | ||
28 | + } | ||
29 | + | ||
30 | + function drain() { | ||
31 | + while(buffer.length && !stream.paused) { | ||
32 | + var data = buffer.shift() | ||
33 | + if(null === data) | ||
34 | + return stream.emit('end') | ||
35 | + else | ||
36 | + stream.emit('data', data) | ||
37 | + } | ||
38 | + } | ||
39 | + | ||
40 | + stream.queue = stream.push = function (data) { | ||
41 | +// console.error(ended) | ||
42 | + if(_ended) return stream | ||
43 | + if(data == null) _ended = true | ||
44 | + buffer.push(data) | ||
45 | + drain() | ||
46 | + return stream | ||
47 | + } | ||
48 | + | ||
49 | + //this will be registered as the first 'end' listener | ||
50 | + //must call destroy next tick, to make sure we're after any | ||
51 | + //stream piped from here. | ||
52 | + //this is only a problem if end is not emitted synchronously. | ||
53 | + //a nicer way to do this is to make sure this is the last listener for 'end' | ||
54 | + | ||
55 | + stream.on('end', function () { | ||
56 | + stream.readable = false | ||
57 | + if(!stream.writable && stream.autoDestroy) | ||
58 | + process.nextTick(function () { | ||
59 | + stream.destroy() | ||
60 | + }) | ||
61 | + }) | ||
62 | + | ||
63 | + function _end () { | ||
64 | + stream.writable = false | ||
65 | + end.call(stream) | ||
66 | + if(!stream.readable && stream.autoDestroy) | ||
67 | + stream.destroy() | ||
68 | + } | ||
69 | + | ||
70 | + stream.end = function (data) { | ||
71 | + if(ended) return | ||
72 | + ended = true | ||
73 | + if(arguments.length) stream.write(data) | ||
74 | + _end() // will emit or queue | ||
75 | + return stream | ||
76 | + } | ||
77 | + | ||
78 | + stream.destroy = function () { | ||
79 | + if(destroyed) return | ||
80 | + destroyed = true | ||
81 | + ended = true | ||
82 | + buffer.length = 0 | ||
83 | + stream.writable = stream.readable = false | ||
84 | + stream.emit('close') | ||
85 | + return stream | ||
86 | + } | ||
87 | + | ||
88 | + stream.pause = function () { | ||
89 | + if(stream.paused) return | ||
90 | + stream.paused = true | ||
91 | + return stream | ||
92 | + } | ||
93 | + | ||
94 | + stream.resume = function () { | ||
95 | + if(stream.paused) { | ||
96 | + stream.paused = false | ||
97 | + stream.emit('resume') | ||
98 | + } | ||
99 | + drain() | ||
100 | + //may have become paused again, | ||
101 | + //as drain emits 'data'. | ||
102 | + if(!stream.paused) | ||
103 | + stream.emit('drain') | ||
104 | + return stream | ||
105 | + } | ||
106 | + return stream | ||
107 | +} | ||
108 | + |
1 | +{ | ||
2 | + "name": "through", | ||
3 | + "version": "2.3.6", | ||
4 | + "description": "simplified stream construction", | ||
5 | + "main": "index.js", | ||
6 | + "scripts": { | ||
7 | + "test": "set -e; for t in test/*.js; do node $t; done" | ||
8 | + }, | ||
9 | + "devDependencies": { | ||
10 | + "stream-spec": "~0.3.5", | ||
11 | + "tape": "~2.3.2", | ||
12 | + "from": "~0.1.3" | ||
13 | + }, | ||
14 | + "keywords": [ | ||
15 | + "stream", | ||
16 | + "streams", | ||
17 | + "user-streams", | ||
18 | + "pipe" | ||
19 | + ], | ||
20 | + "author": { | ||
21 | + "name": "Dominic Tarr", | ||
22 | + "email": "dominic.tarr@gmail.com", | ||
23 | + "url": "dominictarr.com" | ||
24 | + }, | ||
25 | + "license": "MIT", | ||
26 | + "repository": { | ||
27 | + "type": "git", | ||
28 | + "url": "https://github.com/dominictarr/through.git" | ||
29 | + }, | ||
30 | + "homepage": "http://github.com/dominictarr/through", | ||
31 | + "testling": { | ||
32 | + "browsers": [ | ||
33 | + "ie/8..latest", | ||
34 | + "ff/15..latest", | ||
35 | + "chrome/20..latest", | ||
36 | + "safari/5.1..latest" | ||
37 | + ], | ||
38 | + "files": "test/*.js" | ||
39 | + }, | ||
40 | + "gitHead": "19ed9b7e84efe7c3e3c8be80f29390b1620e13c0", | ||
41 | + "bugs": { | ||
42 | + "url": "https://github.com/dominictarr/through/issues" | ||
43 | + }, | ||
44 | + "_id": "through@2.3.6", | ||
45 | + "_shasum": "26681c0f524671021d4e29df7c36bce2d0ecf2e8", | ||
46 | + "_from": "through@~2.3.1", | ||
47 | + "_npmVersion": "1.4.26", | ||
48 | + "_npmUser": { | ||
49 | + "name": "dominictarr", | ||
50 | + "email": "dominic.tarr@gmail.com" | ||
51 | + }, | ||
52 | + "maintainers": [ | ||
53 | + { | ||
54 | + "name": "dominictarr", | ||
55 | + "email": "dominic.tarr@gmail.com" | ||
56 | + } | ||
57 | + ], | ||
58 | + "dist": { | ||
59 | + "shasum": "26681c0f524671021d4e29df7c36bce2d0ecf2e8", | ||
60 | + "tarball": "http://registry.npmjs.org/through/-/through-2.3.6.tgz" | ||
61 | + }, | ||
62 | + "directories": {}, | ||
63 | + "_resolved": "https://registry.npmjs.org/through/-/through-2.3.6.tgz" | ||
64 | +} |
1 | +#through | ||
2 | + | ||
3 | +[](http://travis-ci.org/dominictarr/through) | ||
4 | +[](https://ci.testling.com/dominictarr/through) | ||
5 | + | ||
6 | +Easy way to create a `Stream` that is both `readable` and `writable`. | ||
7 | + | ||
8 | +* Pass in optional `write` and `end` methods. | ||
9 | +* `through` takes care of pause/resume logic if you use `this.queue(data)` instead of `this.emit('data', data)`. | ||
10 | +* Use `this.pause()` and `this.resume()` to manage flow. | ||
11 | +* Check `this.paused` to see current flow state. (`write` always returns `!this.paused`). | ||
12 | + | ||
13 | +This function is the basis for most of the synchronous streams in | ||
14 | +[event-stream](http://github.com/dominictarr/event-stream). | ||
15 | + | ||
16 | +``` js | ||
17 | +var through = require('through') | ||
18 | + | ||
19 | +through(function write(data) { | ||
20 | + this.queue(data) //data *must* not be null | ||
21 | + }, | ||
22 | + function end () { //optional | ||
23 | + this.queue(null) | ||
24 | + }) | ||
25 | +``` | ||
26 | + | ||
27 | +Or, can also be used _without_ buffering on pause, use `this.emit('data', data)`, | ||
28 | +and this.emit('end') | ||
29 | + | ||
30 | +``` js | ||
31 | +var through = require('through') | ||
32 | + | ||
33 | +through(function write(data) { | ||
34 | + this.emit('data', data) | ||
35 | + //this.pause() | ||
36 | + }, | ||
37 | + function end () { //optional | ||
38 | + this.emit('end') | ||
39 | + }) | ||
40 | +``` | ||
41 | + | ||
42 | +## Extended Options | ||
43 | + | ||
44 | +You will probably not need these 99% of the time. | ||
45 | + | ||
46 | +### autoDestroy=false | ||
47 | + | ||
48 | +By default, `through` emits close when the writable | ||
49 | +and readable side of the stream has ended. | ||
50 | +If that is not desired, set `autoDestroy=false`. | ||
51 | + | ||
52 | +``` js | ||
53 | +var through = require('through') | ||
54 | + | ||
55 | +//like this | ||
56 | +var ts = through(write, end, {autoDestroy: false}) | ||
57 | +//or like this | ||
58 | +var ts = through(write, end) | ||
59 | +ts.autoDestroy = false | ||
60 | +``` | ||
61 | + | ||
62 | +## License | ||
63 | + | ||
64 | +MIT / Apache2 |
1 | +var from = require('from') | ||
2 | +var through = require('../') | ||
3 | + | ||
4 | +var tape = require('tape') | ||
5 | + | ||
6 | +tape('simple async example', function (t) { | ||
7 | + | ||
8 | + var n = 0, expected = [1,2,3,4,5], actual = [] | ||
9 | + from(expected) | ||
10 | + .pipe(through(function(data) { | ||
11 | + this.pause() | ||
12 | + n ++ | ||
13 | + setTimeout(function(){ | ||
14 | + console.log('pushing data', data) | ||
15 | + this.push(data) | ||
16 | + this.resume() | ||
17 | + }.bind(this), 300) | ||
18 | + })).pipe(through(function(data) { | ||
19 | + console.log('pushing data second time', data); | ||
20 | + this.push(data) | ||
21 | + })).on('data', function (d) { | ||
22 | + actual.push(d) | ||
23 | + }).on('end', function() { | ||
24 | + t.deepEqual(actual, expected) | ||
25 | + t.end() | ||
26 | + }) | ||
27 | + | ||
28 | +}) |
1 | +var test = require('tape') | ||
2 | +var through = require('../') | ||
3 | + | ||
4 | +// must emit end before close. | ||
5 | + | ||
6 | +test('end before close', function (assert) { | ||
7 | + var ts = through() | ||
8 | + ts.autoDestroy = false | ||
9 | + var ended = false, closed = false | ||
10 | + | ||
11 | + ts.on('end', function () { | ||
12 | + assert.ok(!closed) | ||
13 | + ended = true | ||
14 | + }) | ||
15 | + ts.on('close', function () { | ||
16 | + assert.ok(ended) | ||
17 | + closed = true | ||
18 | + }) | ||
19 | + | ||
20 | + ts.write(1) | ||
21 | + ts.write(2) | ||
22 | + ts.write(3) | ||
23 | + ts.end() | ||
24 | + assert.ok(ended) | ||
25 | + assert.notOk(closed) | ||
26 | + ts.destroy() | ||
27 | + assert.ok(closed) | ||
28 | + assert.end() | ||
29 | +}) | ||
30 | + |
1 | +var test = require('tape') | ||
2 | +var through = require('../') | ||
3 | + | ||
4 | +// must emit end before close. | ||
5 | + | ||
6 | +test('buffering', function(assert) { | ||
7 | + var ts = through(function (data) { | ||
8 | + this.queue(data) | ||
9 | + }, function () { | ||
10 | + this.queue(null) | ||
11 | + }) | ||
12 | + | ||
13 | + var ended = false, actual = [] | ||
14 | + | ||
15 | + ts.on('data', actual.push.bind(actual)) | ||
16 | + ts.on('end', function () { | ||
17 | + ended = true | ||
18 | + }) | ||
19 | + | ||
20 | + ts.write(1) | ||
21 | + ts.write(2) | ||
22 | + ts.write(3) | ||
23 | + assert.deepEqual(actual, [1, 2, 3]) | ||
24 | + ts.pause() | ||
25 | + ts.write(4) | ||
26 | + ts.write(5) | ||
27 | + ts.write(6) | ||
28 | + assert.deepEqual(actual, [1, 2, 3]) | ||
29 | + ts.resume() | ||
30 | + assert.deepEqual(actual, [1, 2, 3, 4, 5, 6]) | ||
31 | + ts.pause() | ||
32 | + ts.end() | ||
33 | + assert.ok(!ended) | ||
34 | + ts.resume() | ||
35 | + assert.ok(ended) | ||
36 | + assert.end() | ||
37 | +}) | ||
38 | + | ||
39 | +test('buffering has data in queue, when ends', function (assert) { | ||
40 | + | ||
41 | + /* | ||
42 | + * If stream ends while paused with data in the queue, | ||
43 | + * stream should still emit end after all data is written | ||
44 | + * on resume. | ||
45 | + */ | ||
46 | + | ||
47 | + var ts = through(function (data) { | ||
48 | + this.queue(data) | ||
49 | + }, function () { | ||
50 | + this.queue(null) | ||
51 | + }) | ||
52 | + | ||
53 | + var ended = false, actual = [] | ||
54 | + | ||
55 | + ts.on('data', actual.push.bind(actual)) | ||
56 | + ts.on('end', function () { | ||
57 | + ended = true | ||
58 | + }) | ||
59 | + | ||
60 | + ts.pause() | ||
61 | + ts.write(1) | ||
62 | + ts.write(2) | ||
63 | + ts.write(3) | ||
64 | + ts.end() | ||
65 | + assert.deepEqual(actual, [], 'no data written yet, still paused') | ||
66 | + assert.ok(!ended, 'end not emitted yet, still paused') | ||
67 | + ts.resume() | ||
68 | + assert.deepEqual(actual, [1, 2, 3], 'resumed, all data should be delivered') | ||
69 | + assert.ok(ended, 'end should be emitted once all data was delivered') | ||
70 | + assert.end(); | ||
71 | +}) |
1 | +var test = require('tape') | ||
2 | +var through = require('../') | ||
3 | + | ||
4 | +// must emit end before close. | ||
5 | + | ||
6 | +test('end before close', function (assert) { | ||
7 | + var ts = through() | ||
8 | + var ended = false, closed = false | ||
9 | + | ||
10 | + ts.on('end', function () { | ||
11 | + assert.ok(!closed) | ||
12 | + ended = true | ||
13 | + }) | ||
14 | + ts.on('close', function () { | ||
15 | + assert.ok(ended) | ||
16 | + closed = true | ||
17 | + }) | ||
18 | + | ||
19 | + ts.write(1) | ||
20 | + ts.write(2) | ||
21 | + ts.write(3) | ||
22 | + ts.end() | ||
23 | + assert.ok(ended) | ||
24 | + assert.ok(closed) | ||
25 | + assert.end() | ||
26 | +}) | ||
27 | + | ||
28 | +test('end only once', function (t) { | ||
29 | + | ||
30 | + var ts = through() | ||
31 | + var ended = false, closed = false | ||
32 | + | ||
33 | + ts.on('end', function () { | ||
34 | + t.equal(ended, false) | ||
35 | + ended = true | ||
36 | + }) | ||
37 | + | ||
38 | + ts.queue(null) | ||
39 | + ts.queue(null) | ||
40 | + ts.queue(null) | ||
41 | + | ||
42 | + ts.resume() | ||
43 | + | ||
44 | + t.end() | ||
45 | +}) |
1 | + | ||
2 | +var test = require('tape') | ||
3 | +var spec = require('stream-spec') | ||
4 | +var through = require('../') | ||
5 | + | ||
6 | +/* | ||
7 | + I'm using these two functions, and not streams and pipe | ||
8 | + so there is less to break. if this test fails it must be | ||
9 | + the implementation of _through_ | ||
10 | +*/ | ||
11 | + | ||
12 | +function write(array, stream) { | ||
13 | + array = array.slice() | ||
14 | + function next() { | ||
15 | + while(array.length) | ||
16 | + if(stream.write(array.shift()) === false) | ||
17 | + return stream.once('drain', next) | ||
18 | + | ||
19 | + stream.end() | ||
20 | + } | ||
21 | + | ||
22 | + next() | ||
23 | +} | ||
24 | + | ||
25 | +function read(stream, callback) { | ||
26 | + var actual = [] | ||
27 | + stream.on('data', function (data) { | ||
28 | + actual.push(data) | ||
29 | + }) | ||
30 | + stream.once('end', function () { | ||
31 | + callback(null, actual) | ||
32 | + }) | ||
33 | + stream.once('error', function (err) { | ||
34 | + callback(err) | ||
35 | + }) | ||
36 | +} | ||
37 | + | ||
38 | +test('simple defaults', function(assert) { | ||
39 | + | ||
40 | + var l = 1000 | ||
41 | + , expected = [] | ||
42 | + | ||
43 | + while(l--) expected.push(l * Math.random()) | ||
44 | + | ||
45 | + var t = through() | ||
46 | + var s = spec(t).through().pausable() | ||
47 | + | ||
48 | + read(t, function (err, actual) { | ||
49 | + assert.ifError(err) | ||
50 | + assert.deepEqual(actual, expected) | ||
51 | + assert.end() | ||
52 | + }) | ||
53 | + | ||
54 | + t.on('close', s.validate) | ||
55 | + | ||
56 | + write(expected, t) | ||
57 | +}); | ||
58 | + | ||
59 | +test('simple functions', function(assert) { | ||
60 | + | ||
61 | + var l = 1000 | ||
62 | + , expected = [] | ||
63 | + | ||
64 | + while(l--) expected.push(l * Math.random()) | ||
65 | + | ||
66 | + var t = through(function (data) { | ||
67 | + this.emit('data', data*2) | ||
68 | + }) | ||
69 | + var s = spec(t).through().pausable() | ||
70 | + | ||
71 | + | ||
72 | + read(t, function (err, actual) { | ||
73 | + assert.ifError(err) | ||
74 | + assert.deepEqual(actual, expected.map(function (data) { | ||
75 | + return data*2 | ||
76 | + })) | ||
77 | + assert.end() | ||
78 | + }) | ||
79 | + | ||
80 | + t.on('close', s.validate) | ||
81 | + | ||
82 | + write(expected, t) | ||
83 | +}) | ||
84 | + | ||
85 | +test('pauses', function(assert) { | ||
86 | + | ||
87 | + var l = 1000 | ||
88 | + , expected = [] | ||
89 | + | ||
90 | + while(l--) expected.push(l) //Math.random()) | ||
91 | + | ||
92 | + var t = through() | ||
93 | + | ||
94 | + var s = spec(t) | ||
95 | + .through() | ||
96 | + .pausable() | ||
97 | + | ||
98 | + t.on('data', function () { | ||
99 | + if(Math.random() > 0.1) return | ||
100 | + t.pause() | ||
101 | + process.nextTick(function () { | ||
102 | + t.resume() | ||
103 | + }) | ||
104 | + }) | ||
105 | + | ||
106 | + read(t, function (err, actual) { | ||
107 | + assert.ifError(err) | ||
108 | + assert.deepEqual(actual, expected) | ||
109 | + }) | ||
110 | + | ||
111 | + t.on('close', function () { | ||
112 | + s.validate() | ||
113 | + assert.end() | ||
114 | + }) | ||
115 | + | ||
116 | + write(expected, t) | ||
117 | +}) |
1 | +{ | ||
2 | + "name": "gm", | ||
3 | + "description": "GraphicsMagick and ImageMagick for node.js", | ||
4 | + "version": "1.17.0", | ||
5 | + "author": { | ||
6 | + "name": "Aaron Heckmann", | ||
7 | + "email": "aaron.heckmann+github@gmail.com" | ||
8 | + }, | ||
9 | + "keywords": [ | ||
10 | + "graphics", | ||
11 | + "magick", | ||
12 | + "image", | ||
13 | + "graphicsmagick", | ||
14 | + "imagemagick", | ||
15 | + "gm", | ||
16 | + "convert", | ||
17 | + "identify", | ||
18 | + "compare" | ||
19 | + ], | ||
20 | + "engines": { | ||
21 | + "node": ">= 0.8.0" | ||
22 | + }, | ||
23 | + "bugs": { | ||
24 | + "url": "http://github.com/aheckmann/gm/issues" | ||
25 | + }, | ||
26 | + "licenses": [ | ||
27 | + { | ||
28 | + "type": "MIT", | ||
29 | + "url": "http://www.opensource.org/licenses/mit-license.php" | ||
30 | + } | ||
31 | + ], | ||
32 | + "main": "./index", | ||
33 | + "scripts": { | ||
34 | + "test": "make test-unit; make test;" | ||
35 | + }, | ||
36 | + "repository": { | ||
37 | + "type": "git", | ||
38 | + "url": "https://github.com/aheckmann/gm.git" | ||
39 | + }, | ||
40 | + "license": "MIT", | ||
41 | + "devDependencies": { | ||
42 | + "gleak": "0.4.0", | ||
43 | + "async": "~0.2.7" | ||
44 | + }, | ||
45 | + "dependencies": { | ||
46 | + "debug": "0.7.0", | ||
47 | + "array-series": "~0.1.0", | ||
48 | + "array-parallel": "~0.1.0", | ||
49 | + "through": "~2.3.1" | ||
50 | + }, | ||
51 | + "gitHead": "2fb65bac7c09ab8b09e87b791b146b82c209076e", | ||
52 | + "homepage": "https://github.com/aheckmann/gm", | ||
53 | + "_id": "gm@1.17.0", | ||
54 | + "_shasum": "27a261e0bdfee3d373d24b5a27bd249057355068", | ||
55 | + "_from": "gm@", | ||
56 | + "_npmVersion": "1.4.28", | ||
57 | + "_npmUser": { | ||
58 | + "name": "rwky", | ||
59 | + "email": "admin@rwky.net" | ||
60 | + }, | ||
61 | + "maintainers": [ | ||
62 | + { | ||
63 | + "name": "aaron", | ||
64 | + "email": "aaron.heckmann+github@gmail.com" | ||
65 | + }, | ||
66 | + { | ||
67 | + "name": "jongleberry", | ||
68 | + "email": "me@jongleberry.com" | ||
69 | + }, | ||
70 | + { | ||
71 | + "name": "rwky", | ||
72 | + "email": "admin@rwky.net" | ||
73 | + }, | ||
74 | + { | ||
75 | + "name": "fragphace", | ||
76 | + "email": "fragphace@gmail.com" | ||
77 | + } | ||
78 | + ], | ||
79 | + "dist": { | ||
80 | + "shasum": "27a261e0bdfee3d373d24b5a27bd249057355068", | ||
81 | + "tarball": "http://registry.npmjs.org/gm/-/gm-1.17.0.tgz" | ||
82 | + }, | ||
83 | + "directories": {}, | ||
84 | + "_resolved": "https://registry.npmjs.org/gm/-/gm-1.17.0.tgz" | ||
85 | +} |
samples/s3/input.json
0 → 100644
1 | +{ | ||
2 | + "Records":[ | ||
3 | + { | ||
4 | + "eventVersion":"2.0", | ||
5 | + "eventSource":"aws:s3", | ||
6 | + "awsRegion":"us-east-1", | ||
7 | + "eventTime":"1970-01-01T00:00:00.000Z", | ||
8 | + "eventName":"ObjectCreated:Put", | ||
9 | + "userIdentity":{ | ||
10 | + "principalId":"AIDAJDPLRKLG7UEXAMPLE" | ||
11 | + }, | ||
12 | + "requestParameters":{ | ||
13 | + "sourceIPAddress":"127.0.0.1" | ||
14 | + }, | ||
15 | + "responseElements":{ | ||
16 | + "x-amz-request-id":"C3D13FE58DE4C810", | ||
17 | + "x-amz-id-2":"FMyUVURIY8/IgAtTv8xRjskZQpcIZ9KG4V5Wp6S7S/JRWeUWerMUE5JgHvANOjpD" | ||
18 | + }, | ||
19 | + "s3":{ | ||
20 | + "s3SchemaVersion":"1.0", | ||
21 | + "configurationId":"testConfigRule", | ||
22 | + "bucket":{ | ||
23 | + "name":"sourcebucket", | ||
24 | + "ownerIdentity":{ | ||
25 | + "principalId":"A3NL1KOZZKExample" | ||
26 | + }, | ||
27 | + "arn":"arn:aws:s3:::sourcebucket" | ||
28 | + }, | ||
29 | + "object":{ | ||
30 | + "key":"HappyFace.jpg", | ||
31 | + "size":1024, | ||
32 | + "eTag":"d41d8cd98f00b204e9800998ecf8427e", | ||
33 | + "versionId":"096fKKXTRTtl3on89fVO.nfljtsv6qko" | ||
34 | + } | ||
35 | + } | ||
36 | + } | ||
37 | + ] | ||
38 | +} |
samples/s3/roles.cf
0 → 100644
1 | +{ | ||
2 | + "AWSTemplateFormatVersion": "2010-09-09", | ||
3 | + "Resources": { | ||
4 | + "ExecRole": { | ||
5 | + "Type": "AWS::IAM::Role", | ||
6 | + "Properties": { | ||
7 | + "AssumeRolePolicyDocument": { | ||
8 | + "Version" : "2012-10-17", | ||
9 | + "Statement": [ { | ||
10 | + "Effect": "Allow", | ||
11 | + "Principal": { | ||
12 | + "Service": [ "lambda.amazonaws.com" ] | ||
13 | + }, | ||
14 | + "Action": [ "sts:AssumeRole" ] | ||
15 | + } ] | ||
16 | + } | ||
17 | + } | ||
18 | + }, | ||
19 | + "ExecRolePolicies": { | ||
20 | + "Type": "AWS::IAM::Policy", | ||
21 | + "Properties": { | ||
22 | + "PolicyName": "ExecRolePolicy", | ||
23 | + "PolicyDocument": { | ||
24 | + "Version" : "2012-10-17", | ||
25 | + "Statement": [ { | ||
26 | + "Effect": "Allow", | ||
27 | + "Action": [ | ||
28 | + "logs:*" | ||
29 | + ], | ||
30 | + "Resource": "arn:aws:logs:*:*:*" | ||
31 | + }, | ||
32 | + { | ||
33 | + "Effect": "Allow", | ||
34 | + "Action": [ | ||
35 | + "s3:GetObject", | ||
36 | + "s3:PutObject" | ||
37 | + ], | ||
38 | + "Resource": [ | ||
39 | + "arn:aws:s3:::*" | ||
40 | + ] | ||
41 | + } ] | ||
42 | + }, | ||
43 | + "Roles": [ { "Ref": "ExecRole" } ] | ||
44 | + } | ||
45 | + }, | ||
46 | + "InvokeRole": { | ||
47 | + "Type": "AWS::IAM::Role", | ||
48 | + "Properties": { | ||
49 | + "AssumeRolePolicyDocument": { | ||
50 | + "Version" : "2012-10-17", | ||
51 | + "Statement": [ { | ||
52 | + "Effect": "Allow", | ||
53 | + "Principal": { | ||
54 | + "Service": [ "s3.amazonaws.com" ] | ||
55 | + }, | ||
56 | + "Action": [ "sts:AssumeRole" ], | ||
57 | + "Condition": { | ||
58 | + "ArnLike": { | ||
59 | + "sts:ExternalId": "arn:aws:s3:::*" | ||
60 | + } | ||
61 | + } | ||
62 | + } ] | ||
63 | + } | ||
64 | + } | ||
65 | + }, | ||
66 | + "InvokeRolePolicies": { | ||
67 | + "Type": "AWS::IAM::Policy", | ||
68 | + "Properties": { | ||
69 | + "PolicyName": "ExecRolePolicy", | ||
70 | + "PolicyDocument": { | ||
71 | + "Version" : "2012-10-17", | ||
72 | + "Statement": [ | ||
73 | + { | ||
74 | + "Effect": "Allow", | ||
75 | + "Resource": [ | ||
76 | + "*" | ||
77 | + ], | ||
78 | + "Action": [ | ||
79 | + "lambda:InvokeFunction" | ||
80 | + ] | ||
81 | + } | ||
82 | + ] | ||
83 | + }, | ||
84 | + "Roles": [ { "Ref": "InvokeRole" } ] | ||
85 | + } | ||
86 | + } | ||
87 | + } | ||
88 | +} |
-
Please register or login to post a comment