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

Four functionality additions to make roots-contentful more flexible #65

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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
88 changes: 77 additions & 11 deletions lib/index.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ contentful = require 'contentful'
pluralize = require 'pluralize'
RootsUtil = require 'roots-util'
querystring = require 'querystring'
nodefn = require 'when/node'
fs = require 'fs'
mkdirp = require 'mkdirp'

errors =
no_token: 'Missing required options for roots-contentful. Please ensure
Expand All @@ -20,6 +23,10 @@ hosts =
production: 'cdn.contentful.com'

module.exports = (opts) ->
# default namespace
opts.namespace ?= 'contentful'
opts.cache ?= false

# throw error if missing required config
if not (opts.access_token && opts.space_id)
throw new Error errors.no_token
Expand All @@ -37,16 +44,15 @@ module.exports = (opts) ->
constructor: (@roots) ->
@util = new RootsUtil(@roots)
@roots.config.locals ?= {}
@roots.config.locals.contentful ?= {}
@roots.config.locals.asset = asset_view_helper

setup: ->
configure_content(opts.content_types).with(@)
.then(get_all_content)
.tap(set_urls)
.then(transform_entries)
.then(sort_entries)
.tap(set_locals)
.tap(set_urls)
.tap(compile_entries)
.tap(write_entries)

Expand Down Expand Up @@ -84,18 +90,56 @@ module.exports = (opts) ->
res
, []


###*
* Fetches data from Contentful for content types, and formats the raw data
* @param {Array} types - configured content_type objects
* @return {Promise} - returns formatted locals object with all content
###

get_all_content = (types) ->
W.map types, (t) ->
fetch_content(t)
.then(format_content)
.then((c) -> t.content = c)
.yield(t)
W.map types, (t) =>

cache = t.cache ? opts.cache

if typeof cache == 'string'
t.write_raw ?= cache
t.read_raw ?= cache
else if typeof cache == 'function'
cacheFilePaths = cache(t)
t.write_raw ?= cacheFilePaths.write
t.read_raw ?= cacheFilePaths.read
else if typeof cache == 'boolean' and cache == true
t.write_raw ?= path.join(@roots.config.output_path(), "#{t.name}.json")
t.read_raw ?= path.join(@roots.config.output_path(), "#{t.name}.json")


read_file_exists = W(false)
if t.read_raw
readFrom = if typeof t.read_raw == 'function' then t.read_raw(t) else t.read_raw
read_file_exists = nodefn.call(fs.stat, readFrom)

read_file_exists.then (exists) ->

if not exists
return throw new Error('Cache file does not exists. Fetching from External.')

#no need to write cache as its the same data..
delete t.write_raw

nodefn.call(fs.readFile, readFrom)
.then (data) ->
JSON.parse(data)

#fetch data from external if cache file not found
.catch ->
fetch_content(t)

.then(format_content)
.then((c) -> t.content = c)
.yield(t)
.tap(write_raw)


###*
* Fetch entries for a single content type object
Expand Down Expand Up @@ -137,9 +181,9 @@ module.exports = (opts) ->
###

set_urls = (types) ->
W.map types, (t) ->
if t.template then W.map t.content, (entry) ->
paths = t.path(entry)
W.map types, (t) =>
if t.template then W.map t.content, (entry) =>
paths = t.path(entry, _.cloneDeep(@roots.config.locals))
paths = [paths] if _.isString(paths)
entry._urls = ("/#{p}.html" for p in paths)
entry._url = if entry._urls.length is 1 then entry._urls[0] else null
Expand All @@ -152,7 +196,14 @@ module.exports = (opts) ->

set_locals = (types) ->
W.map types, (t) =>
@roots.config.locals.contentful[t.name] = t.content

namespace = if t.namespace then t.namespace else opts.namespace
@roots.config.locals[namespace] ?= {}

locals_data = t.content
if t.set_locals and typeof t.set_locals == 'function'
locals_data = t.set_locals(t)
@roots.config.locals[namespace][t.name] = locals_data

###*
* Transforms every type with content with the user provided callback
Expand Down Expand Up @@ -213,6 +264,21 @@ module.exports = (opts) ->
if not t.write then return W.resolve()
@util.write(t.write, JSON.stringify(t.content))

###*
* Writes all data returned from contentful as json
* @param {Array} types - Populated content type objects
* @return {Promise} - promise for when compilation is finished
###

write_raw = (t) ->
if not t.write_raw then return W.resolve()

writeTo = if typeof t.write_raw == 'function' then t.write_raw(t) else t.write_raw

nodefn.call(mkdirp, path.dirname(writeTo))
.then(-> nodefn.call(fs.writeFile, writeTo , JSON.stringify(t)))


###*
* View helper for accessing the actual url from a Contentful asset
* and appends any query string params
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"dependencies": {
"contentful": "^1.1.5",
"lodash": "^3.10.1",
"mkdirp": "^0.5.1",
"pluralize": "^1.2.1",
"roots-util": "0.1.x",
"string": "^3.1.3",
Expand Down
1 change: 1 addition & 0 deletions raw_posts.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"id":"6BYT1gNiIEyIw8Og8aQAO6","write_raw":"raw_posts.json","filters":{"content_type":"6BYT1gNiIEyIw8Og8aQAO6","include":10},"name":"blog_posts","content":[{"title":"Throw Some Ds","body":"Rich Boy selling crick"}]}
25 changes: 25 additions & 0 deletions test/fixtures/cache/app.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
contentful = require '../../..'

module.exports =
ignores: ["**/_*", "**/.DS_Store"]
extensions: [
contentful(
access_token: 'YOUR_ACCESS_TOKEN'
space_id: 'aqzq2qya2jm4'
cache: true
content_types:
index:
id: '6BYT1gNiIEyIw8Og8aQAO6',
read:
id: '6BYT1gNiIEyIw8Og8aQAO2',
read_raw: 'test/fixtures/cache/index.json'
write_raw: 'test/fixtures/cache/public/raw_posts.json'
cache:
id: '6BYT1gNiIEyIw8Og8aQAO2',
cache: (type) ->
return {
read: 'test/fixtures/cache/index.json'
write: 'test/fixtures/cache/public/cache_posts.json'
}
)
]
10 changes: 10 additions & 0 deletions test/fixtures/cache/index.jade
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
ul
- for p in contentful.index
li
h1= p.title
p= p.body

- for p in contentful.read
li
h1= p.title
p= p.body
6 changes: 6 additions & 0 deletions test/fixtures/cache/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "test",
"dependencies": {
"jade": "*"
}
}
20 changes: 20 additions & 0 deletions test/fixtures/namespace/app.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
contentful = require '../../..'

module.exports =
ignores: ["**/_*", "**/.DS_Store"]
extensions: [
contentful(
namespace: 'custom'
access_token: 'YOUR_ACCESS_TOKEN'
space_id: 'aqzq2qya2jm4'
content_types: [
{
id: '6BYT1gNiIEyIw8Og8aQAO6',
},
{
namespace: 'custom2'
id: '6BYT1gNiIEyIw8Og8aQAO6',
}
]
)
]
10 changes: 10 additions & 0 deletions test/fixtures/namespace/index.jade
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
ul
- for p in custom.blog_posts
li
h1= p.title
p= p.body

- for p in custom2.blog_posts
li
h1= p.title_2
p= p.body_2
6 changes: 6 additions & 0 deletions test/fixtures/namespace/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "test",
"dependencies": {
"jade": "*"
}
}
19 changes: 19 additions & 0 deletions test/fixtures/set_locals/app.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
contentful = require '../../..'

module.exports =
ignores: ["**/_*", "**/.DS_Store"]
extensions: [
contentful(
access_token: 'YOUR_ACCESS_TOKEN'
space_id: 'aqzq2qya2jm4'
content_types: [
{
name: 'local_test'
id: '6BYT1gNiIEyIw8Og8aQAO6',
write: 'posts.json',
set_locals: (content_type) ->
return {specific_local: content_type.content[0].body}
},
]
)
]
1 change: 1 addition & 0 deletions test/fixtures/set_locals/index.jade
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
h1= contentful.local_test.specific_local
6 changes: 6 additions & 0 deletions test/fixtures/set_locals/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "test",
"dependencies": {
"jade": "*"
}
}
4 changes: 3 additions & 1 deletion test/fixtures/single_entry_custom/app.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ module.exports =
id: '6BYT1gNiIEyIw8Og8aQAO6'
name: 'blog_posts'
template: 'views/_blog_post.jade'
path: (e) -> "blogging/#{e.category}/#{S(e.title).slugify().s}"
path: (e, locals) ->
category = locals.contentful['blog_posts'][0].category
return "blogging/#{category}/#{S(e.title).slugify().s}"
}
]
)
Expand Down
68 changes: 68 additions & 0 deletions test/test.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,34 @@ describe 'write as json', ->

after -> unmock_contentful()

describe 'data caching', ->
before (done) ->
@title = 'Throw Some Ds'
@body = 'Rich Boy selling crick'
mock_contentful(entries: [{fields: {title: @title, body: @body}}])
compile_fixture.call(@, 'cache').then(-> done()).catch(done)

it 'compiles project', ->
p = path.join(@public, 'index.html')
h.file.exists(p).should.be.ok

it 'writes and reads cache files correctly', ->
p_index = path.join(@public, 'index.json')
p_posts = path.join(@public, 'raw_posts.json')
h.file.exists(p_index).should.be.ok
h.file.exists(p_posts).should.be.ok
h.file.contains(p_index, @title).should.be.true
h.file.contains(p_index, @body).should.be.true

it 'cache function should read exists and write new cache file', ->
p_cache_posts = path.join(@public, 'cache_posts.json')
h.file.exists(p_cache_posts).should.be.ok
h.file.contains(p_cache_posts, @title).should.be.true
h.file.contains(p_cache_posts, @body).should.be.true


after -> unmock_contentful()

describe 'data manipulation', ->
describe 'sort', ->
before (done) ->
Expand Down Expand Up @@ -186,6 +214,46 @@ describe 'data manipulation', ->

after -> unmock_contentful()

describe 'custom namespace', ->
before (done) ->
@title = 'Throw Some Ds'
@body = 'Rich Boy selling crack'
@title_2 = 'Yes sir'
@body_2 = 'No mum'

mock_contentful
entries: [
{fields: {title: @title, body: @body}}
{fields: {title: @title_2, body: @body_2}}
]
compile_fixture.call(@, 'namespace').then(-> done()).catch(done)

it 'has contentful data available in views under a custom global namespace', ->
p = path.join(@public, 'index.html')
h.file.contains(p, @title).should.be.true
h.file.contains(p, @body).should.be.true

it 'has contentful data available in views under a custom local(type) namespace', ->
p = path.join(@public, 'index.html')
h.file.contains(p, @title_2).should.be.true
h.file.contains(p, @body_2).should.be.true

after -> unmock_contentful()

describe 'set_locals', ->
before (done) ->
@title = 'Throw Some Ds'
@body = 'Rich Boy selling crack'

mock_contentful entries: [{fields: {title: @title, body: @body}}]
compile_fixture.call(@, 'set_locals').then(-> done()).catch(done)

it 'can set custom content_type locals', ->
p = path.join(@public, 'index.html')
h.file.contains(p, @body).should.be.true

after -> unmock_contentful()

describe 'custom name for view helper local', ->
before (done) ->
@title = 'Throw Some Ds'
Expand Down