Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Directory parameter extension - Path Template #116

Merged
merged 16 commits into from
Mar 8, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,17 @@ It's copy of our example file `config.json.sample`. More or less it looks like:
| acl | - | String | Permission of S3 object. [See AWS ACL documentation](http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#putObject-property). |
| backup | - | Object | Backup original file setting. |
| | bucket | String | Destination bucket to override. If not supplied, it will use `bucket` setting. |
| | directory | String | Image directory path. When starts with `./` relative to the source, otherwise creates a new tree. |
| | directory | String | Image directory path. Supports relative and absolute paths. Mode details in [DIRECTORY.md](doc/DIRECTORY.md/#directory) |
| | template | Object | Map representing pattern substitution pair. Mode details in [DIRECTORY.md](doc/DIRECTORY.md/#template) |
| | prefix | String | Prepend filename prefix if supplied. |
| | suffix | String | Append filename suffix if supplied. |
| | acl | String | Permission of S3 object. [See AWS ACL documentation](http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#putObject-property). |
| reduce | - | Object | Reduce setting following fields. |
| | quality | Number | Determine reduced image quality ( only `JPG` ). |
| | jpegOptimizer | String | Determine optimiser that should be used `mozjpeg` (default) or `jpegoptim` ( only `JPG` ). |
| | bucket | String | Destination bucket to override. If not supplied, it will use `bucket` setting. |
| | directory | String | Image directory path. When starts with `./` relative to the source, otherwise creates a new tree. |
| | directory | String | Image directory path. Supports relative and absolute paths. Mode details in [DIRECTORY.md](doc/DIRECTORY.md/#directory) |
| | template | Object | Map representing pattern substitution pair. Mode details in [DIRECTORY.md](doc/DIRECTORY.md/#template) |
| | prefix | String | Prepend filename prefix if supplied. |
| | suffix | String | Append filename suffix if supplied. |
| | acl | String | Permission of S3 object. [See AWS ACL documentation](http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#putObject-property). |
Expand All @@ -105,7 +107,8 @@ It's copy of our example file `config.json.sample`. More or less it looks like:
| | jpegOptimizer | String | Determine optimiser that should be used `mozjpeg` (default) or `jpegoptim` ( only `JPG` ). |
| | orientation | Boolean | Auto orientation if value is `true`. |
| | bucket | String | Destination bucket to override. If not supplied, it will use `bucket` setting. |
| | directory | String | Image directory path. When starts with `./` relative to the source, otherwise creates a new tree. |
| | directory | String | Image directory path. Supports relative and absolute paths. Mode details in [DIRECTORY.md](doc/DIRECTORY.md/#directory) |
| | template | Object | Map representing pattern substitution pair. Mode details in [DIRECTORY.md](doc/DIRECTORY.md/#template) |
| | prefix | String | Prepend filename prefix if supplied. |
| | suffix | String | Append filename suffix if supplied. |
| | acl | String | Permission of S3 object. [See AWS ACL documentation](http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#putObject-property). |
Expand Down
13 changes: 8 additions & 5 deletions bin/configtest
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ var reset = '\u001b[0m';
stdout.write("--------------------------------\r\n");
if ( "backup" in config ) {
var backup = config.backup || {};
validateDestination(stdout, bucket, backup.bucket, backup.directory);
validateDestination(stdout, bucket, backup.bucket, backup.directory, backup.template);
validatePrefixAndSuffix(stdout, backup.prefix, backup.suffix);
validateAcl(stdout, acl, backup.acl);
} else {
Expand All @@ -70,7 +70,7 @@ var reset = '\u001b[0m';
var reduce = config.reduce || {};
validateQuality(stdout, reduce.quality);
validateOptimizer(stdout, reduce.jpegOptimizer || jpegOptimizer);
validateDestination(stdout, bucket, reduce.bucket, reduce.directory);
validateDestination(stdout, bucket, reduce.bucket, reduce.directory, reduce.template);
validatePrefixAndSuffix(stdout, reduce.prefix, reduce.suffix);
validateAcl(stdout, acl, reduce.acl);
} else {
Expand All @@ -90,7 +90,7 @@ var reset = '\u001b[0m';
validateFormat(stdout, resize.format);
validateQuality(stdout, resize.quality);
validateOptimizer(stdout, resize.jpegOptimizer || jpegOptimizer);
validateDestination(stdout, bucket, resize.bucket, resize.directory);
validateDestination(stdout, bucket, resize.bucket, resize.directory, resize.template);
validatePrefixAndSuffix(stdout, resize.prefix, resize.suffix);
validateAcl(stdout, acl, resize.acl);
stdout.write("\r\n");
Expand Down Expand Up @@ -158,9 +158,9 @@ var reset = '\u001b[0m';
}
}

function validateDestination(stdout, globalBucket, bucket, directory) {
function validateDestination(stdout, globalBucket, bucket, directory, template) {
var color = reset;
if ( ! bucket && ! globalBucket && (! directory || /^\.\//.test(directory))) {
if ( ! bucket && ! globalBucket && (! directory || /^\.\//.test(directory)) && (! template || ! template.pattern)) {
warning.push(" Saving image to the same or relative directory may cause infinite Lambda process loop.");
color = red;
}
Expand All @@ -172,6 +172,9 @@ var reset = '\u001b[0m';
if ( directory ) {
stdout.write(directory);
stdout.write( /^\.\.?/.test(directory) ? " [Relative]" : "");
} else if ( template && template.pattern ) {
stdout.write(template.output || "/");
stdout.write(" [Pattern]");
} else {
stdout.write("[Same directory]");
}
Expand Down
143 changes: 143 additions & 0 deletions doc/DIRECTORY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# Directory configuration

There are few ways of setting the output directory for processed files. All
of them work in the same way for resized, reduced and archived images.

## Nothing

You are allowed to choose to do not setup any output directory configuration and
use only `prefix` and/or `suffix` parameters. Just bare in mind that in such
case all output files will be saved in same directory as input file -
[S3 event notification limitations](#s3-event-notification-limitations).

## Directory

| Parameter | Type | Required |
|:---------:|:------:|:--------:|
| directory | String | no |

`directory` parameter should be a `String` representing output path. It could be
an absolute (ie. `output/`) or relative (ie. `../output/`, `./output`) path. If
you decide to use relative path, bare in mind that this could lead to situation
where all output files will be saved in same directory structure as input file -
[S3 event notification limitations](#s3-event-notification-limitations).

## Template

| Parameter | Type | Required |
|:---------:|:------:|:--------:|
| template | Object | no |

`template` parameter is a `Map` with two keys: `pattern` and `output`, ie.:

```
{
template: {
pattern: "*path/c",
output: "*path/d"
}
}
```

`pattern` defines a pattern that describe path of input file directory. It's
used for matching and and parsing, which allows you to store parts of parsed
input directory as variables. More details in [Syntax](#template-syntax)
section.

In case the input file directory will not match the `pattern`, it will be
skipped and the [`directory`](#directory) parameter will be processed, if
present.

`output` defines a pattern that describe output directory path. It allows you to
reuse variables parsed from input directory, like in example above. More details
in [Syntax](#template-syntax) section.

If you decide to use `template` parameter, bare in mind to avoid situation
where output files will be saved in same directory structure as input file -
[S3 event notification limitations](#s3-event-notification-limitations).

### Template syntax

**Source**: [path-template](https://github.com/matsadler/path-template/blob/master/readme.md#template-syntax)

The characters `:`, `*`, `(`, and `)` have special meanings.

`:` indicates the following segment is the name of a variable
`*` indicates the following segment is the splat/glob
`(` starts an optional segment
`)` ends an optional segment

additionally `/` and `.` will start a new segment.

##### Static Segments

"/foo/bar.baz"
^ ^ ^
| | Starts a segment, matching ".baz"
| |
| Starts a segment, matching "/bar"
|
Starts a segment, matching "/foo"

##### Variables

"/foo/:bar.baz"
^ ^ ^
| | Starts a new segment, that matches ".baz"
| |
| Matches anything up to the start of the next segment, with the value
| being stored in the "bar" parameter of the returned match object
|
Starts a segment, matching "/foo"

##### Splat/Glob

"/foo/*bar"
^ ^
| Matches any number of segments, the values being stored as an array
| in the "bar" parameter of the returned match object
|
Starts a segment, matching "/foo"

###### Anonymous Splat/Glob

"/foo/*"
^ ^
| Matches any number of segments, the values will not appear in the
| returned match object
|
Starts a segment, matching "/foo"

##### Optional Segments

"/foo(/baz)/baz"
^ ^ ^^
| | |Starts a new segment, that matches "/baz"
| | |
| | Ends the optional segment
| |
| Starts an optional segment, this segment need not be in the path being
| matched for the match to be successful
|
Starts a segment, matching "/foo"

### More examples

Examples of `template` usage cases you can find in our
[test files](../test/image-data.js).

## S3 event notification limitations

S3 event notifications are limited to filter file events only by predefined
`prefix` and `suffix`. This could be problematic in situation where you decide
to store output files in the same directory structure as the input image. This
could cause S3 to fire new event notification for each output image saved in
that path. In extreme case this could lead to situation where Lambda
functions are executed in never ending loop. But, we are prepared to prevent
such incidents, or maybe I should rather say, we are prepared to minimise the
potential damage.

Each processed file is stored with additional
`Metadata: { img-processed: true }`. Also, each input file that we process is
checked against this `flag`, and if it's present, we will stop the processing
flow with `"Object was already processed."` error message.
7 changes: 6 additions & 1 deletion lib/ImageArchiver.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@ class ImageArchiver {

resolve(
new ImageData(
image.combineWithDirectory(option.directory, option.prefix, option.suffix),
image.combineWithDirectory({
directory: option.directory,
template: option.template,
prefix: option.prefix,
suffix: option.suffix
}),
option.bucket || image.bucketName,
image.data,
image.headers,
Expand Down
31 changes: 24 additions & 7 deletions lib/ImageData.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"use strict";

const path = require("path");
const imageType = require("image-type");
const path = require("path");
const imageType = require("image-type");
const PathTemplate = require("path-template");

class ImageType {

Expand Down Expand Up @@ -156,21 +157,37 @@ class ImageData {
* @param String fileSuffix (from options)
* @return String
*/
combineWithDirectory(directory, filePrefix, fileSuffix) {
const prefix = filePrefix || "";
const suffix = fileSuffix || "";
combineWithDirectory(output) {
const prefix = output.prefix || "";
const suffix = output.suffix || "";
const fileName = path.parse(this.baseName).name;
const extension = "." + this.type.ext;

const template = output.template;
if ( typeof template === "object" && template.pattern ) {
const inputTemplate = PathTemplate.parse(template.pattern);
const outputTemplate = PathTemplate.parse(template.output || "");

const match = PathTemplate.match(inputTemplate, this.dirName);
if ( match ) {
const outputPath = PathTemplate.format(outputTemplate, match);
return path.join(outputPath, prefix + fileName + suffix + extension);
} else {
console.log( "Directory " + this.dirName + " didn't match template " + template.pattern );
}
}

const directory = output.directory;
if ( typeof directory === "string" ) {
// ./X , ../X , . , ..
if ( directory.match(/^\.\.?\//) || directory.match(/^\.\.?$/) ) {
return path.join(this.dirName, directory, prefix + fileName + suffix + extension);
} else {
return path.join(directory, prefix + fileName + suffix + extension);
}
} else {
return path.join(this.dirName, prefix + fileName + suffix + extension);
}

return path.join(this.dirName, prefix + fileName + suffix + extension);
}
}

Expand Down
7 changes: 6 additions & 1 deletion lib/ImageReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,12 @@ class ImageReducer {
return chain.pipes(streams).run()
.then((buffer) => {
return new ImageData(
image.combineWithDirectory(option.directory, option.prefix, option.suffix),
image.combineWithDirectory({
directory: option.directory,
template: option.template,
prefix: option.prefix,
suffix: option.suffix
}),
option.bucket || image.bucketName,
buffer,
image.headers,
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
"dependencies": {
"aws-sdk": "^2.24.0",
"gm": "^1.23.0",
"image-type": "^3.0.0"
"image-type": "^3.0.0",
"path-template": "0.0.0"
},
"devDependencies": {
"ava": "^0.18.2",
Expand Down
Loading