contentful_middleman 0.0.4 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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