Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
stefanpenner committed Aug 22, 2015
2 parents dbe260b + 6f74151 commit e2eff83
Show file tree
Hide file tree
Showing 24 changed files with 1,272 additions and 122 deletions.
16 changes: 16 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# top-most EditorConfig file
root = true

# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true

# I like my 2-space indents, grr
[*]
indent_style = space
indent_size = 2

# It's the universal encoding of love
[*]
charset = utf-8
26 changes: 26 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,27 @@
# Logs
logs
*.log

# Runtime data
pids
*.pid
*.seed

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release

# Dependency directory
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
node_modules
4 changes: 4 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*
!lib/*.js
!index.js
!LICENSE
14 changes: 14 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
language: node_js
node_js:
- '0.10'
- '0.12'
- 'iojs'

install:
- time npm install

script:
- npm run test:coverage

after_success:
- cat ./coverage/lcov.info | npm run coveralls
83 changes: 83 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# master

# 1.2.0

* Replace with @caitp's cauliflower-filter implementation and @stefanpenner's tests

# 1.1.0

* Add `name` and `annotation` options

# 1.0.0

* Bump without change

# 0.2.0

* Derive from new broccoli-plugin base class. Notably, this means that
subclasses always must call `Filter.call(this, inputTree)` in their
constructors, instead of settings `this.inputTree = inputTree`.

# 0.1.14

* Improve performance by symlinking when possible

# 0.1.13

* Improve error message when `processString` isn't overridden in subclass

# 0.1.12

* Throw on undefined `inputTree`

# 0.1.11

* Update dependencies

# 0.1.10

* Do not override this.inputEncoding/this.outputEncoding if not provided

# 0.1.9

* Fix inputEncoding/outputEncoding defaults

# 0.1.8

* Add `inputEncoding` and `outputEncoding` options

# 0.1.7

* Update dependency to deal with symlinks correctly

# 0.1.6

* Copy instead of hardlinking

# 0.1.5

* Use new broccoli-writer base class

# 0.1.4

* Use broccoli-kitchen-sink-helpers instead of larger broccoli dependency

# 0.1.3

* Remove stray `console.log` O_O

# 0.1.2

* Augment error objects for better error reporting

# 0.1.1

* Update `broccoli` dependency

# 0.1.0

* Pass relativePath argument to `processFile`

# 0.0.1

* Initial release
22 changes: 22 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
The MIT License (MIT)

Copyright (c) 2015 Jo Liss
Copyright (c) 2015 Caitlin Potter & Contributors.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
166 changes: 166 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# broccoli-filter

[![Build Status](https://travis-ci.org/broccolijs/broccoli-filter.svg?branch=master)](https://travis-ci.org/broccolijs/broccoli-filter)
[![Build status](https://ci.appveyor.com/api/projects/status/hc68s0vbn9di4ehi/branch/master?svg=true)](https://ci.appveyor.com/project/joliss/broccoli-filter/branch/master)

Helper base class for Broccoli plugins that map input files into output files
one-to-one.

## API

```js
class Filter {
/**
* Abstract base-class for filtering purposes.
*
* Enforces that it is invoked on an instance of a class which prototypically
* inherits from Filter, and which is not itself Filter.
*/
constructor(inputNode: BroccoliNode, options: FilterOptions): Filter;

/**
* Abstract method `processString`: must be implemented on subclasses of
* Filter.
*
* The return value is written as the contents of the output file
*/
abstract processString(contents: string, relativePath: string): string;

/**
* Virtual method `getDestFilePath`: determine whether the source file should
* be processed, and optionally rename the output file when processing occurs.
*
* Return `null` to pass the file through without processing. Return
* `relativePath` to process the file with `processString`. Return a
* different path to process the file with `processString` and rename it.
*
* By default, if the options passed into the `Filter` constructor contain a
* property `extensions`, and `targetExtension` is supplied, the first matching
* extension in the list is replaced with the `targetExtension` option's value.
*/
virtual getDestFilePath(relativePath: string): string;
}
```
### Options
* `extensions`: An array of file extensions to process, e.g. `['md', 'markdown']`.
* `targetExtension`: The file extension of the corresponding output files, e.g.
`'html'`.
* `inputEncoding`: The character encoding used for reading input files to be
processed (default: `'utf8'`). For binary files, pass `null` to receive a
`Buffer` object in `processString`.
* `outputEncoding`: The character encoding used for writing output files after
processing (default: `'utf8'`). For binary files, pass `null` and return a
`Buffer` object from `processString`.
* `name`, `annotation`: Same as
[broccoli-plugin](https://github.com/broccolijs/broccoli-plugin#new-plugininputnodes-options);
see there.
All options except `name` and `annotation` can also be set on the prototype
instead of being passed into the constructor.
### Example Usage
```js
var Filter = require('broccoli-filter');

Awk.prototype = Object.create(Filter.prototype);
Awk.prototype.constructor = Awk;
function Awk(inputNode, search, replace, options) {
options = options || {};
Filter.call(this, inputNode, {
annotation: options.annotation
});
this.search = search;
this.replace = replace;
}

Awk.prototype.extensions = ['txt'];
Awk.prototype.targetExtension = 'txt';

Awk.prototype.processString = function(content, relativePath) {
return content.replace(this.search, this.replace);
};
```
In `Brocfile.js`, use your new `Awk` plugin like so:
```
var node = new Awk('docs', 'ES6', 'ECMAScript 2015');

module.exports = node;
```
## Persistent Cache
__Note: This feature is experimental and is only available on Unix based systems.__
Adding persist flag allows a subclass to persist state across restarts. This exists to mitigate the upfront cost of some more expensive transforms on warm boot. __It does not aim to improve incremental build performance, if it does, it should indicate something is wrong with the filter or input filter in question.__
### How does it work?
It does so but establishing a 2 layer file cache. The first layer, is the entire bucket.
The second, `cacheKeyProcessString` is a per file cache key.
Together, these two layers should provide the right balance of speed and sensibility.
The bucket level cacheKey must be stable but also never become stale. If the key is not
stable, state between restarts will be lost and performance will suffer. On the flip-side,
if the cacheKey becomes stale changes may not be correctly reflected.
It is configured by subclassing and refining `cacheKey` method. A good key here, is
likely the name of the plugin, its version and the actual versions of its dependencies.
```js
Subclass.prototype.cacheKey = function() {
return md5(Filter.prototype.call(this) + inputOptionsChecksum + dependencyVersionChecksum);
}
```
The second key, represents the contents of the file. Typically the base-class's functionality
is sufficient, as it merely generates a checksum of the file contents. If for some reason this
is not sufficient, it can be re-configured via subclassing.
```js
Subclass.prototype.cacheKeyProcessString = function(string, relativePath) {
return superAwesomeDigest(string);
}
```
It is recommended that persistent re-builds is opt-in by the consumer as it does not currently work on all systems.
```js
var myTree = new SomePlugin('lib', { persist: true });
```
## FAQ
### Upgrading from 0.1.x to 1.x
You must now call the base class constructor. For example:
```js
// broccoli-filter 0.1.x:
function MyPlugin(inputTree) {
this.inputTree = inputTree;
}

// broccoli-filter 1.x:
function MyPlugin(inputNode) {
Filter.call(this, inputNode);
}
```
Note that "node" is simply new terminology for "tree".
### Source Maps
**Can this help with compilers that are almost 1:1, like a minifier that takes
a `.js` and `.js.map` file and outputs a `.js` and `.js.map` file?**
Not at the moment. I don't know yet how to implement this and still have the
API look beautiful. We also have to make sure that caching works correctly, as
we have to invalidate if either the `.js` or the `.js.map` file changes. My
plan is to write a source-map-aware uglifier plugin to understand this use
case better, and then extract common code back into this `Filter` base class.
35 changes: 35 additions & 0 deletions appveyor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# http://www.appveyor.com/docs/appveyor-yml

# Fix line endings in Windows. (runs before repo cloning)
init:
- git config --global core.autocrlf true

# Test against these versions of Node.js.
environment:
matrix:
- nodejs_version: "0.12"

# Install scripts. (runs after repo cloning)
install:
# Get the latest stable version of Node 0.STABLE.latest
- ps: Install-Product node $env:nodejs_version
# Typical npm stuff.
- md C:\nc
- npm install -g npm@latest
# Workaround https://github.com/npm/npm/wiki/Troubleshooting#upgrading-on-windows
- set PATH=%APPDATA%\npm;%PATH%
- npm config set cache C:\nc
- npm version
- npm install

# Post-install test scripts.
test_script:
# Output useful info for debugging.
- npm version
- cmd: npm test

# Don't actually build.
build: off

# Set build version format here instead of in the admin panel.
version: "{build}"
Loading

0 comments on commit e2eff83

Please sign in to comment.