contentful_middleman 0.0.4 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ccfe1bc9ea0a8cfa76de3d65aa382141e3d81da0
4
- data.tar.gz: 5929f5f756b15d636a17e575a40c81bf3f0b1230
3
+ metadata.gz: e5976bd4bf0b3954a33f737d13b8d9e2b928a9f8
4
+ data.tar.gz: 27378bfcc1245e25474fa1ccb26ccdbee82ba203
5
5
  SHA512:
6
- metadata.gz: ece6120ab1f940ce2bd6dac04a5ef093d322caf057e4f17a247362445b7a61b6eac19f35da0eef2e9c3f00b56f9417a19ebc1fab5fc8440c2e2a0dd16ea5bcc8
7
- data.tar.gz: d20282b168ad58b84b9d77400dbaa01d9527d776119dd2510263f8de3b32cd824bb330ade6045ad17e77221185032938d94d51b93a8d71213e77e2d6e975d1ff
6
+ metadata.gz: 687bf3399611ae1cabf559862d375914afc69fdb93df7bcad0258875b5b42c46cb6f8a3e7540639f464ec71d892df862ea6d9f0dd246c41cab0a46f79d65d04c
7
+ data.tar.gz: 007f63c5d2fb64088ef48590e17eb5f1539afcb6ff1eeaf85d6f7fcb758a0ab470216ba61211046943cac18aa24ad25c22c6f992c4281a94a6467782fa568d94
@@ -1,15 +1,28 @@
1
- 0.0.4
2
- ===
1
+ # Change Log
3
2
 
3
+ ## 1.0.
4
+ ### Other
5
+ This release brings breaking changes that are not compatible with extension configurations in
6
+ previous versions. For more information about the supported configuration please read the
7
+ README file.
8
+
9
+ Changes in this release:
10
+
11
+ * Support multiple activations of the extension. Import from multiple spaces
12
+ * Decouple mapping of entries from blog post layout. Support custom mappers
13
+ * Store imported entries as local data
14
+ * Optionally rebuild static site only when there are changes in the imported data
15
+
16
+ ## 0.0.4
17
+ ### Other
4
18
  * Publish first Gem version
5
19
 
6
- 0.0.3
7
- ===
20
+ ## 0.0.3
8
21
 
22
+ ### Other
9
23
  * Minor updates
10
24
 
11
25
 
12
- 0.0.2
13
- ===
14
-
26
+ ## 0.0.2
27
+ ### Other
15
28
  * First release
data/README.md CHANGED
@@ -2,12 +2,10 @@
2
2
 
3
3
  [![Build Status](https://travis-ci.org/contentful/contentful_middleman.png)](https://travis-ci.org/contentful/contentful_middleman)
4
4
 
5
- Contentful Middleman is a [Middleman](http://middlemanapp.com/) extension to use the Middleman static side generator together with the API-driven [Contentful CMS](https://www.contentful.com). It is powered by the [Contentful Ruby Gem](https://github.com/contentful/contentful.rb).
5
+ Contentful Middleman is a [Middleman](http://middlemanapp.com/) extension to use the Middleman static site generator together with the API-driven [Contentful CMS](https://www.contentful.com). It is powered by the [Contentful Ruby Gem](https://github.com/contentful/contentful.rb).
6
6
 
7
7
  Experience the power of Middleman while staying sane as a developer by letting end-users edit content in a web-based interface.
8
8
 
9
- This extensions supports both page-based content as well as blog posts through middleman-blog.
10
-
11
9
  ## Installation
12
10
 
13
11
  Add the following line to the Gemfile of your Middleman project:
@@ -22,62 +20,138 @@ Then as usual, run:
22
20
  bundle install
23
21
  ```
24
22
 
23
+ ## Usage
24
+
25
+ Run `middleman contentful` in your terminal. This will fetch entries for the configured
26
+ spaces and content types and put the resulting data in the
27
+ [local data folder](https://middlemanapp.com/advanced/local-data/) as yaml files.
28
+
29
+ ### --rebuild option
30
+
31
+ The `contentful` command has a `--rebuild` option which will trigger a rebuild of your site only if there were changes between the last
32
+ and the current import.
33
+
25
34
  ## Configuration
26
35
 
27
36
  To configure the extension, add the following configuration block to Middleman's config.rb:
28
37
 
38
+ ```ruby
39
+ activate :contentful do |f|
40
+ f.space = SPACE
41
+ f.access_token = ACCESS_TOKEN
42
+ f.cda_query = QUERY
43
+ f.content_types = CONTENT_TYPES_MAPPINGS
44
+ end
45
+ ```
46
+
47
+ Parameter | Description
48
+ ---------- |------------
49
+ space | Hash with an user choosen name for the space as key and the space id as value
50
+ access_token | Contentful Delivery API access token
51
+ cda_query | Hash describing query configuration. See [contentful.rb](https://github.com/contentful/contentful.rb) for more info
52
+ content_types | Hash describing the mapping applied to entries of the imported content types
53
+
54
+ You can activate the extension multiple times to import entries from different spaces.
55
+ ## Entry mapping
56
+
57
+ The extension will transform every fetched entry before storing it as a yaml file in the local
58
+ data folder. If a custom mapper is not specified a default one will be used.
59
+
60
+ The default mapper will map fields, assets and linked entries.
61
+
62
+ ### Custom mappers
63
+
64
+ You can create your own mappers if you need so. The only requirement for a class to behave as a
65
+ mapper is to have a `map(context, entry)` instance method. This method will take as parameters:
66
+
67
+ * A context object. All properties set on this object will be written to the yaml file
68
+ * An entry
69
+
70
+ Following is an example of such custom mapper:
71
+
72
+ ```ruby
73
+ class MyAwesomeMapper
74
+ def map(context, entry)
75
+ context.slug = entry.title.parameterize
76
+ #... more transformations
77
+ end
78
+ end
79
+ ```
80
+
81
+ If you don't want to map all the fields by hand inherit from the Base mappper:
82
+
83
+ ```ruby
84
+ class MyAwesomeMapper < ContentfulMiddleman::Mappers::Base
85
+ def map(context, entry)
86
+ super
87
+ # After calling super the context object
88
+ # will have a property for every field in the
89
+ # entry
90
+ end
91
+ end
29
92
  ```
93
+
94
+ ## Configuration: examples
95
+
96
+ ```ruby
30
97
  activate :contentful do |f|
31
- # The Space ID of your Contentful space
32
- f.space = 'YOUR_SPACE_ID'
33
-
34
- # The access token (API Key) for the Content Delivery API
35
- f.access_token = 'YOUR_CONTENT_DELIVERY_API_ACCESS_TOKEN'
36
-
37
- # Optional: Options for middleman-blog
38
-
39
- # Filter Entries for your blog posts. See Contentful gem and Content Delivery API documentation.
40
- f.blog_posts_query = {content_type: "6LbnqgnwA08qYaU", category: "news" }
41
-
42
- # Which keys to use in the article template for blog posts
43
- # Key: template variable
44
- # Value: Entry method or block
45
- f.blog_post_mappings = {
46
- slug: :id,
47
- date: :created_at,
48
- body: :id,
49
- tags: :tags,
50
- title: ->(e){"#{e.id}XXXX"}
51
- }
52
-
53
- # Define your own template for blog posts
54
- f.new_article_template = "/my_templates/article.tt"
55
-
56
- # Automatically synchronize blog posts before building with "middleman build"
57
- f.sync_blog_before_build = true # default: false
98
+ f.space = {partners: 'space-id'}
99
+ f.access_token = 'some_access_token'
100
+ f.cda_query = { content_type: 'content-type-id', include: 1 }
101
+ f.content_types = { partner: 'content-type-id'}
58
102
  end
59
103
  ```
104
+ The above configuration does the following:
60
105
 
61
- ## Using managed content in regular pages
106
+ * Sets the alias `partners` to the space with id _some-id_
107
+ * Sets the alias `partner` to the content type with id _content-type-id_
108
+ * Uses the default mapper to transform `partner` entries into yaml files (no mapper specified for the `partner` content type)
62
109
 
63
- The `contentful` helper provides a Contentful gem client object, that can be used to fetch managed content from Contentful:
110
+ Entries fetched using this configuration will be stored as yaml files in `data/partners/partner/ENTRY_ID.yaml`.
111
+
112
+ ```ruby
113
+ class Mapper
114
+ def map(context, entry)
115
+ context.title = "#{entry.title}-title"
116
+ #...
117
+ end
118
+ end
64
119
 
120
+ activate :contentful do |f|
121
+ f.space = {partners: 'space-id'}
122
+ f.access_token = 'some_access_token'
123
+ f.cda_query = { content_type: '1EVL9Bl48Euu28QEOa44ai', include: 1 }
124
+ f.content_types = { partner: {mapper: Mapper, id: 'content-type-id'}}
125
+ end
65
126
  ```
66
- <ol>
67
- <% contentful.entries(content_type: '6LbnqgnwA08qYaU').each do |entry| %>
68
- <li>
69
- <%= entry.title %>
70
- <%= entry.body %>
71
- <%= entry.created_at %>
72
- </li>
73
- <% end %>
74
- </ol>
127
+
128
+ The above configuration is the same as the previous one only that this time we are setting a custom mapper
129
+ for the entries belonging to the `partner` content type.
130
+
131
+
132
+ ## Using imported entries in templates
133
+
134
+ Middleman will load all the yaml files stored in the local data folder. This lets you use all the imported
135
+ data into your templates.
136
+
137
+ Consider that we have data stored under `data/partners/partner`. Then in our templates we could use that data like
138
+ this:
139
+
140
+ ```html
141
+ <h1>Partners</h1>
142
+ <ol>
143
+ <% data.partners.partner.each do |id, partner| %>
144
+ <li><%= partner["name"] %></li>
145
+ <% end %>
146
+ </ol>
75
147
  ```
76
148
 
77
- ## Synchronizing blog posts manually
149
+ ### Rendering Markdown:
78
150
 
79
- Blog posts are synchronized to your repo as YAML files with front matter, just as if you would write them manually. Either automatically when building, or manually by running:
151
+ If you want to use markdown in your content types you manually have to render this to markdown.
152
+ Depending on the markdown library you need to transform the data.
153
+ For Kramdown this would be:
80
154
 
81
155
  ```
82
- middleman contentful
156
+ <%= Kramdown::Document.new(data).to_html %>
83
157
  ```
data/Rakefile CHANGED
@@ -9,7 +9,7 @@ require 'rspec/core/rake_task'
9
9
  desc "Run RSpec"
10
10
  RSpec::Core::RakeTask.new do |spec|
11
11
  spec.pattern = 'spec/**/*_spec.rb'
12
- spec.rspec_opts = ['--color', '--format nested']
12
+ spec.rspec_opts = ['--color']
13
13
  end
14
14
 
15
15
  begin
@@ -6,8 +6,8 @@ Gem::Specification.new do |s|
6
6
  s.name = "contentful_middleman"
7
7
  s.version = ContentfulMiddleman::VERSION
8
8
  s.platform = Gem::Platform::RUBY
9
- s.authors = ["Sascha Konietzke"]
10
- s.email = ["sascha@contentful.com"]
9
+ s.authors = ["Sascha Konietzke", "Farruco Sanjurjoj"]
10
+ s.email = ["sascha@contentful.com", "madtrick@gmail.com"]
11
11
  s.homepage = "https://www.contentful.com"
12
12
  s.summary = %q{Include mangablable content from the Contentful CMS and API into your Middleman projects}
13
13
  s.description = %q{Load blog posts and other managed content into Middleman}
@@ -2,6 +2,6 @@ require "middleman-core"
2
2
 
3
3
  require 'contentful_middleman/version'
4
4
  require 'contentful_middleman/core'
5
- require "contentful_middleman/commands/sync_blog"
5
+ require "contentful_middleman/commands/contentful"
6
6
 
7
7
  ::Middleman::Extensions.register(:contentful, ContentfulMiddleman::Core)
@@ -0,0 +1,79 @@
1
+ require 'middleman-core/cli'
2
+ require 'middleman-blog/uri_templates'
3
+ require 'date'
4
+ require 'digest'
5
+ require 'contentful_middleman/commands/context'
6
+ require 'contentful_middleman/tools/backup'
7
+ require 'contentful_middleman/version_hash'
8
+ require 'Contentful_middleman/import_task'
9
+ require 'contentful_middleman/local_data/store'
10
+ require 'contentful_middleman/local_data/file'
11
+
12
+ module Middleman
13
+ module Cli
14
+ # This class provides an "contentful" command for the middleman CLI.
15
+
16
+ class Contentful < Thor
17
+ include Thor::Actions
18
+
19
+ # Path where Middleman expects the local data to be stored
20
+ MIDDLEMAN_LOCAL_DATA_FOLDER = 'data'
21
+
22
+ check_unknown_options!
23
+
24
+ namespace :contentful
25
+ desc 'contentful', 'Import data from Contentful'
26
+
27
+ method_option "rebuild",
28
+ aliases: "-r",
29
+ desc: "Rebuilds the site if there were changes in the imported data"
30
+
31
+ def self.source_root
32
+ ENV['MM_ROOT']
33
+ end
34
+
35
+ # Tell Thor to exit with a nonzero exit code on failure
36
+ def self.exit_on_failure?
37
+ true
38
+ end
39
+
40
+ def contentful
41
+ if shared_instance.respond_to? :contentful_instances
42
+ ContentfulMiddleman::VersionHash.source_root = self.class.source_root
43
+ ContentfulMiddleman::LocalData::Store.base_path = MIDDLEMAN_LOCAL_DATA_FOLDER
44
+ ContentfulMiddleman::LocalData::File.thor = self
45
+
46
+ hash_local_data_changed = contentful_instances.reduce(false) do |changes, instance|
47
+ import_task = create_import_task(instance)
48
+ import_task.run
49
+
50
+ changes || import_task.changed_local_data?
51
+ end
52
+
53
+ Middleman::Cli::Build.new.build if hash_local_data_changed && options[:rebuild]
54
+ shared_instance.logger.info 'Contentful Import: Done!'
55
+ else
56
+ raise Thor::Error.new "You need to activate the contentful extension in config.rb before you can import data from Contentful"
57
+ end
58
+ end
59
+
60
+ private
61
+ def contentful_instances
62
+ shared_instance.contentful_instances
63
+ end
64
+
65
+ def create_import_task(instance)
66
+ space_name = instance.space_name.to_s
67
+ content_type_names = instance.content_types_ids_to_names
68
+ content_type_mappers = instance.content_types_ids_to_mappers
69
+
70
+ ContentfulMiddleman::ImportTask.new(space_name, content_type_names, content_type_mappers, instance)
71
+ end
72
+
73
+ def shared_instance
74
+ @shared_instance ||= ::Middleman::Application.server.inst
75
+ end
76
+ end
77
+
78
+ end
79
+ end
@@ -0,0 +1,76 @@
1
+ module ContentfulMiddleman
2
+ class Context < BasicObject
3
+ def initialize
4
+ @variables = {}
5
+ @nexted_contexts = []
6
+ end
7
+
8
+ def method_missing(symbol, *args, &block)
9
+ if symbol =~ /\A.+=\z/
10
+ variable_name = symbol.to_s.gsub('=','')
11
+ variable_value = args.first
12
+
13
+ set variable_name, variable_value
14
+ else
15
+ get symbol
16
+ end
17
+ end
18
+
19
+ def nest(field_name)
20
+ @nexted_contexts << field_name
21
+ new_context = Context.new
22
+ yield new_context
23
+
24
+ set field_name, new_context
25
+ end
26
+
27
+ def map(field_name, elements)
28
+ @nexted_contexts << field_name
29
+ new_contexts = elements.map do |element|
30
+ new_context = Context.new
31
+ yield element, new_context
32
+ new_context
33
+ end
34
+
35
+ set field_name, new_contexts
36
+ end
37
+
38
+ def set(name, value)
39
+ @variables[name.to_sym] = value
40
+ end
41
+
42
+ def get(name)
43
+ @variables.fetch(name.to_sym)
44
+ end
45
+
46
+ def is_a?(klass)
47
+ Context == klass
48
+ end
49
+
50
+ def to_hash
51
+ @variables
52
+ end
53
+
54
+ def to_yaml
55
+ variables = @variables.dup
56
+ variables.update(variables) do |variable_name, variable_value|
57
+ if @nexted_contexts.include? variable_name
58
+ hashize_nested_context(variable_value)
59
+ else
60
+ variable_value
61
+ end
62
+ end
63
+
64
+ variables.to_yaml
65
+ end
66
+
67
+ def hashize_nested_context(nested_context)
68
+ case nested_context
69
+ when ::Array
70
+ nested_context.map {|e| e.to_hash}
71
+ else
72
+ nested_context.to_hash
73
+ end
74
+ end
75
+ end
76
+ end
@@ -1,69 +1,71 @@
1
1
  require 'middleman-core'
2
2
  require 'contentful'
3
+ require_relative 'mappers/base'
4
+ require_relative 'helpers'
5
+ require_relative 'instance'
3
6
 
4
7
  # The Contentful Middleman extensions allows to load managed content into Middleman projects through the Contentful Content Management Platform.
5
8
  module ContentfulMiddleman
9
+ def self.instances
10
+ @contentful_middleman_instances ||= []
11
+ end
12
+
6
13
  class Core < ::Middleman::Extension
7
- DEFAULT_BLOG_MAPPINGS = {
8
- slug: :id,
9
- date: :created_at,
10
- title: :id,
11
- body: :id,
12
- tags: :tags
13
- }
14
+ self.supports_multiple_instances = true
14
15
 
15
- self.supports_multiple_instances = false
16
+ option :space, nil,
17
+ 'The Contentful Space ID and name'
16
18
 
17
- option :space, nil, 'The Contentful Space ID'
18
- option :access_token, nil, 'The Contentful Content Delivery API access token'
19
+ option :access_token, nil,
20
+ 'The Contentful Content Delivery API access token'
19
21
 
20
- option :new_article_template, File.expand_path('../commands/article.tt', __FILE__), 'Path (relative to project root) to an ERb template that will be used to generate new Contentful articles from the "middleman contentful" command.'
22
+ option :cda_query, {},
23
+ 'The conditions that are used on the Content Delivery API to query for blog posts'
21
24
 
22
- option :blog_posts_query, {}, "The conditions that are used on the Content Delivery API to query for blog posts"
23
- option :blog_post_mappings, {}, "The mappings from Contentful DynamicResources to Middleman"
25
+ option :content_types, {},
26
+ 'The mapping of Content Types names to ids'
24
27
 
25
- option :sync_blog_before_build, false, "Synchronize the blog from Contentful before the build phase"
26
28
 
27
- def initialize(app, options_hash={}, &block)
28
- super
29
+ helpers ContentfulMiddleman::Helpers
29
30
 
30
- app.set :contentful_middleman, self
31
- app.set :contentful_middleman_client, client
31
+ #
32
+ # Middleman hooks
33
+ #
34
+ def after_configuration
35
+ massage_options
32
36
 
33
- app.before_build do |builder|
34
- contentful_middleman.sync_blog if contentful_middleman.middleman_blog_enabled? && contentful_middleman.options.sync_blog_before_build
35
- end
37
+ ContentfulMiddleman.instances << (ContentfulMiddleman::Instance.new self)
36
38
  end
37
39
 
38
- # Is the Middleman blog extension enabled?
39
- def middleman_blog_enabled?
40
- app.respond_to? :blog
40
+ private
41
+ def massage_options
42
+ massage_space_options
43
+ massage_content_types_options
41
44
  end
42
45
 
43
- # Synchronize blog posts from Contentful through the CLI task
44
- def sync_blog
45
- Middleman::Cli::SyncBlog.new.contentful
46
- true
47
- end
46
+ def massage_space_options
47
+ space_option = options.space
48
+ space_name = space_option.keys.first
49
+ space_id = space_option.fetch(space_name)
48
50
 
49
- def blog_post_mappings
50
- @blog_post_mappings ||= ContentfulMiddleman::Core::DEFAULT_BLOG_MAPPINGS.merge(options.blog_post_mappings)
51
+ options.space = { name: space_name, id: space_id }
51
52
  end
52
53
 
53
- # The Contentful Gem client for the Content Delivery API
54
- def client
55
- @client ||= Contentful::Client.new(
56
- access_token: options.access_token,
57
- space: options.space,
58
- dynamic_entries: :auto
59
- )
60
- end
54
+ def massage_content_types_options
55
+ content_types_options = options.content_types
56
+ new_content_types_options = content_types_options.each_with_object({}) do |(content_type_name, value), options|
57
+ if value.is_a? Hash
58
+ mapper = value.fetch(:mapper)
59
+ id = value.fetch(:id)
60
+ else
61
+ mapper = Mapper::Base
62
+ id = value
63
+ end
61
64
 
62
- helpers do
63
- # A helper method to access the Contentful Gem client
64
- def contentful
65
- contentful_middleman_client
65
+ options[id] = {name: content_type_name, mapper: mapper}
66
66
  end
67
+
68
+ options.content_types = new_content_types_options
67
69
  end
68
70
  end
69
71
  end
@@ -0,0 +1,7 @@
1
+ module ContentfulMiddleman
2
+ module Helpers
3
+ def contentful_instances
4
+ ContentfulMiddleman.instances
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,42 @@
1
+ module ContentfulMiddleman
2
+ class ImportTask
3
+ def initialize(space_name, content_type_names, content_type_mappers, contentful)
4
+ @space_name = space_name
5
+ @content_type_names = content_type_names
6
+ @content_type_mappers = content_type_mappers
7
+ @changed_local_data = false
8
+ @contentful = contentful
9
+ end
10
+
11
+ def run
12
+ old_version_hash = ContentfulMiddleman::VersionHash.read_for_space(@space_name)
13
+
14
+ LocalData::Store.new(local_data_files, @space_name).write
15
+
16
+ new_version_hash = ContentfulMiddleman::VersionHash.write_for_space_with_entries(@space_name, entries)
17
+
18
+ @changed_local_data = new_version_hash != old_version_hash
19
+ end
20
+
21
+ def changed_local_data?
22
+ @changed_local_data
23
+ end
24
+
25
+ private
26
+ def local_data_files
27
+ entries.map do |entry|
28
+ content_type_mapper = @content_type_mappers.fetch(entry.content_type.id)
29
+ content_type_name = @content_type_names.fetch(entry.content_type.id).to_s
30
+ context = ContentfulMiddleman::Context.new
31
+
32
+ content_type_mapper.new.map(context, entry)
33
+
34
+ LocalData::File.new(context.to_yaml, File.join(@space_name, content_type_name, entry.id))
35
+ end
36
+ end
37
+
38
+ def entries
39
+ @entries ||= @contentful.entries
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,45 @@
1
+ module ContentfulMiddleman
2
+ class Instance
3
+ def initialize(extension)
4
+ @extension = extension
5
+ end
6
+
7
+ def entries
8
+ client.entries(options.cda_query)
9
+ end
10
+
11
+ def space_name
12
+ @space_name ||= options.space.fetch(:name)
13
+ end
14
+
15
+ def content_types_ids_to_mappers
16
+ @content_types_mappers ||= options.content_types.reduce({}) do |acc, (content_type_id, config)|
17
+ content_type_mapper = config.fetch(:mapper)
18
+ acc[content_type_id] = content_type_mapper
19
+ acc
20
+ end
21
+ end
22
+
23
+ def content_types_ids_to_names
24
+ @content_types_names ||= options.content_types.reduce({}) do |acc, (content_type_id, config)|
25
+ content_type_name = config.fetch(:name)
26
+ acc[content_type_id] = content_type_name
27
+ acc
28
+ end
29
+ end
30
+
31
+ private
32
+ def client
33
+ @client ||= Contentful::Client.new(
34
+ access_token: options.access_token,
35
+ space: options.space.fetch(:id),
36
+ dynamic_entries: :auto,
37
+ raise_errors: true
38
+ )
39
+ end
40
+
41
+ def options
42
+ @extension.options
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,29 @@
1
+ module ContentfulMiddleman
2
+ module LocalData
3
+ class File
4
+ class << self
5
+ def thor=(thor)
6
+ @thor = thor
7
+ end
8
+
9
+ def thor
10
+ @thor
11
+ end
12
+ end
13
+
14
+ def initialize(data, path)
15
+ @data = data
16
+ @path = path
17
+ end
18
+
19
+ def write
20
+ self.class.thor.create_file(local_data_file_path, nil, {}) { @data }
21
+ end
22
+
23
+ def local_data_file_path
24
+ base_path = LocalData::Store.base_path
25
+ ::File.join(base_path, @path)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,37 @@
1
+ module ContentfulMiddleman
2
+ module LocalData
3
+ class Store
4
+ include ContentfulMiddleman::Tools::Backup::InstanceMethods
5
+
6
+ class << self
7
+ def base_path=(path)
8
+ @base_path = path
9
+ end
10
+
11
+ def base_path
12
+ @base_path
13
+ end
14
+ end
15
+
16
+ def initialize(files, folder)
17
+ @files = files
18
+ @folder = folder
19
+ end
20
+
21
+ def write
22
+ do_with_backup backup_name, path_to_backup do
23
+ @files.each(&:write)
24
+ end
25
+ end
26
+
27
+ private
28
+ def backup_name
29
+ "#{@folder}-data-backup"
30
+ end
31
+
32
+ def path_to_backup
33
+ ::File.join(self.class.base_path, @folder)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,39 @@
1
+ module ContentfulMiddleman
2
+ module Mapper
3
+ class Base
4
+ def map(context, entry)
5
+ map_entry(context, entry)
6
+ end
7
+
8
+ private
9
+ def map_entry(context, entry)
10
+ context.id = entry.id
11
+ entry.fields.each {|k, v| map_field context, k, v}
12
+ end
13
+
14
+ def map_field(context, field_name, field_value)
15
+ case field_value
16
+ when Contentful::Asset
17
+ map_asset(context, field_name, field_value)
18
+ when Array
19
+ map_array(context, field_name, field_value)
20
+ else
21
+ context.set(field_name, field_value)
22
+ end
23
+ end
24
+
25
+ def map_asset(context, field_name, field_value)
26
+ context.nest(field_name) do |nested_context|
27
+ nested_context.title = field_value.title
28
+ nested_context.url = field_value.file.url
29
+ end
30
+ end
31
+
32
+ def map_array(context, field_name, field_value)
33
+ context.map(field_name, field_value) do |element, new_context|
34
+ map_entry(new_context, element)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,87 @@
1
+ require 'fileutils'
2
+
3
+ module ContentfulMiddleman
4
+ module Tools
5
+ class NullBackup
6
+ def restore; end
7
+ def destroy; end
8
+ end
9
+
10
+ class Backup
11
+
12
+ class << self
13
+ def basepath
14
+ ::File.join ENV["MM_ROOT"], ".tmp", "backups"
15
+ end
16
+
17
+ def ensure_backup_path!
18
+ return if ::File.exists? basepath
19
+
20
+ FileUtils.mkdir_p basepath
21
+ end
22
+ end
23
+
24
+
25
+ def initialize(name, source)
26
+ @name = name
27
+ @source = source
28
+
29
+ self.class.ensure_backup_path!
30
+
31
+ FileUtils.mkdir(path)
32
+ FileUtils.mv(source, path)
33
+ end
34
+
35
+
36
+ def restore
37
+ FileUtils.rm_rf(@source)
38
+ FileUtils.mv(path, @source)
39
+ end
40
+
41
+ def destroy
42
+ FileUtils.rm_rf(path)
43
+ end
44
+
45
+ private
46
+ def path
47
+ ::File.join self.class.basepath, name_and_date
48
+ end
49
+
50
+ def all_files_in_path(path)
51
+ Dir.glob(::File.join(path, "*"))
52
+ end
53
+
54
+ def name_and_date
55
+ @name_and_date ||= "#{@name}-#{Time.now.strftime("%Y%m%d%H%M%S")}"
56
+ end
57
+
58
+ module InstanceMethods
59
+ def do_with_backup(backup_name, path_to_backup)
60
+ backup = create_backup backup_name, path_to_backup
61
+ remove_backup = false
62
+
63
+ begin
64
+ yield
65
+ remove_backup = true
66
+ rescue StandardError => e
67
+ backup.restore
68
+ remove_backup = true
69
+ raise e
70
+ ensure
71
+ backup.destroy if remove_backup
72
+ end
73
+ end
74
+
75
+ private
76
+ def create_backup(backup_name, path_to_backup)
77
+ if ::File.exist? path_to_backup
78
+ Backup.new(backup_name, path_to_backup)
79
+ else
80
+ NullBackup.new
81
+ end
82
+ end
83
+ end
84
+
85
+ end
86
+ end
87
+ end
@@ -1,3 +1,3 @@
1
1
  module ContentfulMiddleman
2
- VERSION = "0.0.4"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -0,0 +1,29 @@
1
+ module ContentfulMiddleman
2
+ class VersionHash
3
+ class << self
4
+ def source_root=(source_root)
5
+ @source_root = source_root
6
+ end
7
+
8
+ def read_for_space(space_name)
9
+ hashfilename_for_space = hashfilename(space_name)
10
+ ::File.read(hashfilename_for_space) if File.exist? hashfilename_for_space
11
+ end
12
+
13
+ def write_for_space_with_entries(space_name, entries)
14
+ sorted_entries = entries.sort {|a, b| a.id <=> b.id}
15
+ ids_and_revisions_string = sorted_entries.map {|e| "#{e.id}#{e.revision}"}.join
16
+ entries_hash = Digest::SHA1.hexdigest( ids_and_revisions_string )
17
+
18
+ File.open(hashfilename(space_name), 'w') { |file| file.write(entries_hash) }
19
+
20
+ entries_hash
21
+ end
22
+
23
+ private
24
+ def hashfilename(space_name)
25
+ ::File.join(@source_root, ".#{space_name}-space-hash")
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,7 +1,5 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
3
  describe 'ContentfulMiddleman' do
4
- it "should be pending" do
5
- pending
6
- end
4
+ it "should be pending"
7
5
  end
metadata CHANGED
@@ -1,67 +1,69 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: contentful_middleman
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sascha Konietzke
8
+ - Farruco Sanjurjoj
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2014-03-21 00:00:00.000000000 Z
12
+ date: 2015-01-26 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: middleman-core
15
16
  requirement: !ruby/object:Gem::Requirement
16
17
  requirements:
17
- - - ~>
18
+ - - "~>"
18
19
  - !ruby/object:Gem::Version
19
20
  version: '3.3'
20
21
  type: :runtime
21
22
  prerelease: false
22
23
  version_requirements: !ruby/object:Gem::Requirement
23
24
  requirements:
24
- - - ~>
25
+ - - "~>"
25
26
  - !ruby/object:Gem::Version
26
27
  version: '3.3'
27
28
  - !ruby/object:Gem::Dependency
28
29
  name: contentful
29
30
  requirement: !ruby/object:Gem::Requirement
30
31
  requirements:
31
- - - '>='
32
+ - - ">="
32
33
  - !ruby/object:Gem::Version
33
34
  version: '0'
34
35
  type: :runtime
35
36
  prerelease: false
36
37
  version_requirements: !ruby/object:Gem::Requirement
37
38
  requirements:
38
- - - '>='
39
+ - - ">="
39
40
  - !ruby/object:Gem::Version
40
41
  version: '0'
41
42
  - !ruby/object:Gem::Dependency
42
43
  name: rubygems-tasks
43
44
  requirement: !ruby/object:Gem::Requirement
44
45
  requirements:
45
- - - ~>
46
+ - - "~>"
46
47
  - !ruby/object:Gem::Version
47
48
  version: '0.2'
48
49
  type: :development
49
50
  prerelease: false
50
51
  version_requirements: !ruby/object:Gem::Requirement
51
52
  requirements:
52
- - - ~>
53
+ - - "~>"
53
54
  - !ruby/object:Gem::Version
54
55
  version: '0.2'
55
56
  description: Load blog posts and other managed content into Middleman
56
57
  email:
57
58
  - sascha@contentful.com
59
+ - madtrick@gmail.com
58
60
  executables: []
59
61
  extensions: []
60
62
  extra_rdoc_files: []
61
63
  files:
62
- - .gitignore
63
- - .travis.yml
64
- - .yardopts
64
+ - ".gitignore"
65
+ - ".travis.yml"
66
+ - ".yardopts"
65
67
  - CHANGELOG.md
66
68
  - CONTRIBUTING.md
67
69
  - Gemfile
@@ -71,10 +73,18 @@ files:
71
73
  - TODO
72
74
  - contentful_middleman.gemspec
73
75
  - lib/contentful_middleman.rb
74
- - lib/contentful_middleman/commands/article.tt
75
- - lib/contentful_middleman/commands/sync_blog.rb
76
+ - lib/contentful_middleman/commands/contentful.rb
77
+ - lib/contentful_middleman/commands/context.rb
76
78
  - lib/contentful_middleman/core.rb
79
+ - lib/contentful_middleman/helpers.rb
80
+ - lib/contentful_middleman/import_task.rb
81
+ - lib/contentful_middleman/instance.rb
82
+ - lib/contentful_middleman/local_data/file.rb
83
+ - lib/contentful_middleman/local_data/store.rb
84
+ - lib/contentful_middleman/mappers/base.rb
85
+ - lib/contentful_middleman/tools/backup.rb
77
86
  - lib/contentful_middleman/version.rb
87
+ - lib/contentful_middleman/version_hash.rb
78
88
  - lib/middleman_extension.rb
79
89
  - spec/contentful_middleman_spec.rb
80
90
  - spec/spec_helper.rb
@@ -88,17 +98,17 @@ require_paths:
88
98
  - lib
89
99
  required_ruby_version: !ruby/object:Gem::Requirement
90
100
  requirements:
91
- - - '>='
101
+ - - ">="
92
102
  - !ruby/object:Gem::Version
93
103
  version: '0'
94
104
  required_rubygems_version: !ruby/object:Gem::Requirement
95
105
  requirements:
96
- - - '>='
106
+ - - ">="
97
107
  - !ruby/object:Gem::Version
98
108
  version: '0'
99
109
  requirements: []
100
110
  rubyforge_project:
101
- rubygems_version: 2.0.0
111
+ rubygems_version: 2.2.2
102
112
  signing_key:
103
113
  specification_version: 4
104
114
  summary: Include mangablable content from the Contentful CMS and API into your Middleman
@@ -1,9 +0,0 @@
1
- ---
2
- # Warning: This file is auto-generated from the Contentful CMS. Local changes will overriden on next sync.
3
-
4
- title: <%= @title %>
5
- date: <%= @date.strftime('%F %R %Z') %>
6
- tags: <%= @tags.join(', ') %>
7
- ---
8
-
9
- <%= @body %>
@@ -1,88 +0,0 @@
1
- require 'middleman-core/cli'
2
- require 'date'
3
- require 'middleman-blog/uri_templates'
4
-
5
- module Middleman
6
- module Cli
7
- # This class provides an "contentful" command for the middleman CLI.
8
- class SyncBlog < Thor
9
- include Thor::Actions
10
- include ::Middleman::Blog::UriTemplates
11
-
12
- check_unknown_options!
13
-
14
- namespace :contentful
15
-
16
- def self.source_root
17
- ENV['MM_ROOT']
18
- end
19
-
20
- # Tell Thor to exit with a nonzero exit code on failure
21
- def self.exit_on_failure?
22
- true
23
- end
24
-
25
- desc "contentful", "Synchronize Contentful blog posts"
26
- method_option "lang",
27
- aliases: "-l",
28
- desc: "The language to create the post with (defaults to I18n.default_locale if avaliable)"
29
- method_option "blog",
30
- aliases: "-b",
31
- desc: "The name of the blog to create the post inside (for multi-blog apps, defaults to the only blog in single-blog apps)"
32
- def contentful
33
- contentful_middleman = shared_instance.contentful_middleman
34
- client = shared_instance.contentful_middleman_client
35
- contentful_middleman_options = contentful_middleman.options
36
- blog_post_mappings = contentful_middleman.blog_post_mappings
37
-
38
- if shared_instance.respond_to? :blog
39
- shared_instance.logger.info " Contentful Sync: Start..."
40
-
41
- client.entries(contentful_middleman_options.blog_posts_query).each do |entry|
42
- slug = value_from_object(entry, blog_post_mappings[:slug])
43
- title = value_from_object(entry, blog_post_mappings[:title])
44
- date = value_from_object(entry, blog_post_mappings[:date]).strftime("%Y-%m-%d")
45
- tags = value_from_object(entry, blog_post_mappings[:tags]) || []
46
- body = value_from_object(entry, blog_post_mappings[:body])
47
-
48
- @title = title
49
- @slug = slug || safe_parameterize(title)
50
- @date = date ? Time.zone.parse(date) : Time.zone.now
51
- @tags = tags
52
- @lang = options[:lang] || ( I18n.default_locale if defined? I18n )
53
- @body = body
54
-
55
- blog_inst = shared_instance.blog(options[:blog])
56
-
57
- path_template = blog_inst.source_template
58
- params = date_to_params(@date).merge(lang: @lang.to_s, title: @slug)
59
- article_path = apply_uri_template path_template, params
60
-
61
- template contentful_middleman.options.new_article_template, File.join(shared_instance.source_dir, article_path + blog_inst.options.default_extension)
62
- end
63
-
64
- shared_instance.logger.info " Contentful Sync: Done!"
65
- else
66
- raise Thor::Error.new "You need to activate the blog extension in config.rb before you can create an article"
67
- end
68
- end
69
-
70
- private
71
- def shared_instance
72
- @shared_instance ||= ::Middleman::Application.server.inst
73
- end
74
-
75
- def value_from_object(object, mapping)
76
- if ( mapping.is_a?(Symbol) || mapping.is_a?(String) ) && object.respond_to?(mapping)
77
- object.send(mapping)
78
- elsif mapping.is_a?(Proc)
79
- object.instance_exec(object, &mapping)
80
- else
81
- shared_instance.logger.warn "Warning - Unknown mapping (#{mapping}) for object (#{object.class}) with ID (#{object.id})"
82
- nil
83
- end
84
- end
85
- end
86
-
87
- end
88
- end