agave-client 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.travis.yml +14 -0
  4. data/CODE_OF_CONDUCT.md +49 -0
  5. data/CONTRIBUTORS.md +34 -0
  6. data/Gemfile +7 -0
  7. data/LICENSE.txt +29 -0
  8. data/README.md +41 -0
  9. data/Rakefile +7 -0
  10. data/agave-client.gemspec +63 -0
  11. data/bin/console +15 -0
  12. data/bin/setup +8 -0
  13. data/exe/agave +10 -0
  14. data/lib/agave.rb +11 -0
  15. data/lib/agave/api_client.rb +97 -0
  16. data/lib/agave/api_error.rb +23 -0
  17. data/lib/agave/cli.rb +93 -0
  18. data/lib/agave/dump/dsl/add_to_data_file.rb +14 -0
  19. data/lib/agave/dump/dsl/create_data_file.rb +14 -0
  20. data/lib/agave/dump/dsl/create_post.rb +33 -0
  21. data/lib/agave/dump/dsl/directory.rb +30 -0
  22. data/lib/agave/dump/dsl/root.rb +37 -0
  23. data/lib/agave/dump/format.rb +28 -0
  24. data/lib/agave/dump/format/json.rb +18 -0
  25. data/lib/agave/dump/format/toml.rb +31 -0
  26. data/lib/agave/dump/format/yaml.rb +55 -0
  27. data/lib/agave/dump/operation/add_to_data_file.rb +41 -0
  28. data/lib/agave/dump/operation/create_data_file.rb +28 -0
  29. data/lib/agave/dump/operation/create_post.rb +34 -0
  30. data/lib/agave/dump/operation/directory.rb +34 -0
  31. data/lib/agave/dump/operation/root.rb +27 -0
  32. data/lib/agave/dump/runner.rb +47 -0
  33. data/lib/agave/dump/ssg_detector.rb +36 -0
  34. data/lib/agave/json_api_deserializer.rb +38 -0
  35. data/lib/agave/json_api_serializer.rb +145 -0
  36. data/lib/agave/local/entities_repo.rb +46 -0
  37. data/lib/agave/local/field_type/boolean.rb +12 -0
  38. data/lib/agave/local/field_type/color.rb +63 -0
  39. data/lib/agave/local/field_type/date.rb +12 -0
  40. data/lib/agave/local/field_type/date_time.rb +12 -0
  41. data/lib/agave/local/field_type/file.rb +65 -0
  42. data/lib/agave/local/field_type/float.rb +12 -0
  43. data/lib/agave/local/field_type/gallery.rb +23 -0
  44. data/lib/agave/local/field_type/global_seo.rb +56 -0
  45. data/lib/agave/local/field_type/image.rb +82 -0
  46. data/lib/agave/local/field_type/integer.rb +12 -0
  47. data/lib/agave/local/field_type/json.rb +13 -0
  48. data/lib/agave/local/field_type/lat_lon.rb +30 -0
  49. data/lib/agave/local/field_type/link.rb +12 -0
  50. data/lib/agave/local/field_type/links.rb +21 -0
  51. data/lib/agave/local/field_type/rich_text.rb +21 -0
  52. data/lib/agave/local/field_type/seo.rb +33 -0
  53. data/lib/agave/local/field_type/slug.rb +12 -0
  54. data/lib/agave/local/field_type/string.rb +12 -0
  55. data/lib/agave/local/field_type/text.rb +12 -0
  56. data/lib/agave/local/field_type/theme.rb +43 -0
  57. data/lib/agave/local/field_type/video.rb +77 -0
  58. data/lib/agave/local/item.rb +179 -0
  59. data/lib/agave/local/items_repo.rb +210 -0
  60. data/lib/agave/local/json_api_entity.rb +78 -0
  61. data/lib/agave/local/loader.rb +60 -0
  62. data/lib/agave/local/site.rb +73 -0
  63. data/lib/agave/paginator.rb +33 -0
  64. data/lib/agave/repo.rb +93 -0
  65. data/lib/agave/site/client.rb +24 -0
  66. data/lib/agave/upload/file.rb +92 -0
  67. data/lib/agave/upload/image.rb +8 -0
  68. data/lib/agave/utils/favicon_tags_builder.rb +85 -0
  69. data/lib/agave/utils/locale_value.rb +15 -0
  70. data/lib/agave/utils/meta_tags/article_modified_time.rb +15 -0
  71. data/lib/agave/utils/meta_tags/article_publisher.rb +18 -0
  72. data/lib/agave/utils/meta_tags/base.rb +55 -0
  73. data/lib/agave/utils/meta_tags/description.rb +24 -0
  74. data/lib/agave/utils/meta_tags/image.rb +35 -0
  75. data/lib/agave/utils/meta_tags/og_locale.rb +15 -0
  76. data/lib/agave/utils/meta_tags/og_site_name.rb +18 -0
  77. data/lib/agave/utils/meta_tags/og_type.rb +18 -0
  78. data/lib/agave/utils/meta_tags/robots.rb +14 -0
  79. data/lib/agave/utils/meta_tags/title.rb +46 -0
  80. data/lib/agave/utils/meta_tags/twitter_card.rb +14 -0
  81. data/lib/agave/utils/meta_tags/twitter_site.rb +18 -0
  82. data/lib/agave/utils/seo_tags_builder.rb +45 -0
  83. data/lib/agave/version.rb +4 -0
  84. data/lib/agave/watch/site_change_watcher.rb +37 -0
  85. metadata +504 -0
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+ require 'fileutils'
3
+
4
+ module Agave
5
+ module Dump
6
+ module Operation
7
+ class Directory
8
+ attr_reader :context, :path
9
+
10
+ def initialize(context, path)
11
+ @context = context
12
+ @path = File.join(context.path, path)
13
+ @operations = []
14
+ end
15
+
16
+ def add(operation)
17
+ @operations << operation
18
+ end
19
+
20
+ def perform
21
+ FileUtils.remove_dir(path) if Dir.exist?(path)
22
+
23
+ FileUtils.mkdir_p(path)
24
+
25
+ operations.each(&:perform)
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :operations
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+ module Agave
3
+ module Dump
4
+ module Operation
5
+ class Root
6
+ attr_reader :path
7
+
8
+ def initialize(path)
9
+ @operations = []
10
+ @path = path
11
+ end
12
+
13
+ def add(operation)
14
+ @operations << operation
15
+ end
16
+
17
+ def perform
18
+ operations.each(&:perform)
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :operations
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+ require 'agave/dump/dsl/root'
3
+ require 'agave/dump/operation/root'
4
+ require 'agave/dump/ssg_detector'
5
+ require 'agave/local/loader'
6
+
7
+ module Agave
8
+ module Dump
9
+ class Runner
10
+ attr_reader :config_path, :client, :destination_path, :preview_mode
11
+
12
+ def initialize(config_path, client, preview_mode, destination_path = Dir.pwd)
13
+ @config_path = config_path
14
+ @preview_mode = preview_mode
15
+ @client = client
16
+ @destination_path = destination_path
17
+ end
18
+
19
+ def run
20
+ print 'Fetching content from AgaveCMS... '
21
+
22
+ loader.load
23
+
24
+ I18n.available_locales = loader.items_repo.available_locales
25
+ I18n.locale = I18n.available_locales.first
26
+
27
+ Dsl::Root.new(
28
+ File.read(config_path),
29
+ loader.items_repo,
30
+ operation
31
+ )
32
+
33
+ operation.perform
34
+
35
+ puts "\e[32m✓\e[0m Done!"
36
+ end
37
+
38
+ def operation
39
+ @operation ||= Operation::Root.new(destination_path)
40
+ end
41
+
42
+ def loader
43
+ @loader ||= Agave::Local::Loader.new(client, preview_mode)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+ require 'toml'
3
+ require 'json'
4
+ require 'yaml'
5
+
6
+ module Agave
7
+ module Dump
8
+ class SsgDetector
9
+ attr_reader :path
10
+
11
+ RUBY = %w(jekyll).freeze
12
+
13
+ def initialize(path)
14
+ @path = path
15
+ end
16
+
17
+ def detect
18
+ ruby_generator ||
19
+ 'unknown'
20
+ end
21
+
22
+ private
23
+
24
+ def ruby_generator
25
+ gemfile_path = File.join(path, 'Gemfile')
26
+ return unless File.exist?(gemfile_path)
27
+
28
+ gemfile = File.read(gemfile_path)
29
+
30
+ RUBY.find do |generator|
31
+ gemfile =~ /('#{generator}'|"#{generator}")/
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+ module Agave
3
+ class JsonApiDeserializer
4
+ def deserialize(data)
5
+ data = data[:data]
6
+
7
+ if data.is_a? Array
8
+ data.map { |resource| deserialize_resource(resource) }
9
+ else
10
+ deserialize_resource(data)
11
+ end
12
+ end
13
+
14
+ def deserialize_resource(data)
15
+ result = { id: data[:id] }
16
+
17
+ if data[:attributes]
18
+ result.merge!(data[:attributes])
19
+ end
20
+
21
+ relationships = data.delete(:relationships)
22
+
23
+ if relationships
24
+ relationships.each do |key, handle|
25
+ handle_data = handle['data']
26
+ value = if handle_data.is_a? Array
27
+ handle_data.map { |ref| ref['id'] }
28
+ elsif handle_data.is_a? Hash
29
+ handle_data[:id]
30
+ end
31
+ result[key] = value
32
+ end
33
+ end
34
+
35
+ result.with_indifferent_access
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+ module Agave
3
+ class JsonApiSerializer
4
+ attr_reader :link, :type
5
+
6
+ def initialize(type, link)
7
+ @link = link
8
+ @type = type
9
+ end
10
+
11
+ def serialize(resource, id = nil)
12
+ resource = resource.with_indifferent_access
13
+ data = {}
14
+
15
+ data[:id] = id || resource[:id] if id || resource[:id]
16
+
17
+ data[:type] = type
18
+ data[:attributes] = serialized_attributes(resource)
19
+
20
+ if relationships.any?
21
+ data[:relationships] = serialized_relationships(resource)
22
+ end
23
+
24
+ { data: data }
25
+ end
26
+
27
+ def serialized_attributes(resource)
28
+ result = {}
29
+
30
+ attributes(resource).each do |attribute|
31
+ if resource.key? attribute
32
+ result[attribute] = resource[attribute]
33
+ elsif required_attributes.include? attribute
34
+ throw "Required attribute: #{attribute}"
35
+ end
36
+ end
37
+
38
+ result
39
+ end
40
+
41
+ def serialized_relationships(resource)
42
+ result = {}
43
+
44
+ relationships.each do |relationship, meta|
45
+ if resource.key? relationship
46
+ value = resource[relationship]
47
+
48
+ data = if value
49
+ if meta[:collection]
50
+ value.map do |id|
51
+ { type: meta[:type], id: id.to_s }
52
+ end
53
+ else
54
+ { type: meta[:type], id: value.to_s }
55
+ end
56
+ end
57
+ result[relationship] = { data: data }
58
+
59
+ elsif required_relationships.include?(relationship)
60
+ throw "Required attribute: #{relationship}"
61
+ end
62
+ end
63
+
64
+ result
65
+ end
66
+
67
+ def attributes(resource)
68
+ if type == "item"
69
+ return resource.keys.map(&:to_sym) - [
70
+ :item_type,
71
+ :id,
72
+ :created_at,
73
+ :updated_at,
74
+ :is_valid,
75
+ :published_version,
76
+ :current_version
77
+ ]
78
+ end
79
+
80
+ link_attributes["properties"].keys.map(&:to_sym)
81
+ end
82
+
83
+ def required_attributes
84
+ if type == "item"
85
+ return []
86
+ end
87
+
88
+ (link_attributes.required || []).map(&:to_sym)
89
+ end
90
+
91
+ def relationships
92
+ if type == "item"
93
+ if link.rel == :create
94
+ return { item_type: { collection: false, type: 'item_type' } }
95
+ else
96
+ {}
97
+ end
98
+ end
99
+
100
+ if !link_relationships
101
+ return {}
102
+ end
103
+
104
+ link_relationships.properties.reduce({}) do |acc, (relationship, schema)|
105
+ is_collection = schema.properties["data"].type.first == 'array'
106
+
107
+ definition = if is_collection
108
+ schema.properties['data'].items
109
+ elsif schema.properties['data'].type.first == 'object'
110
+ schema.properties['data']
111
+ else
112
+ schema.properties['data'].any_of.find do |option|
113
+ option.type.first == 'object'
114
+ end
115
+ end
116
+
117
+ type = definition.properties['type']
118
+ .pattern.source.gsub(/(^\^|\$$)/, '')
119
+
120
+ acc[relationship.to_sym] = {
121
+ collection: is_collection,
122
+ type: type,
123
+ }
124
+
125
+ acc
126
+ end
127
+ end
128
+
129
+ def required_relationships
130
+ if type == "item"
131
+ return %i(item_type)
132
+ end
133
+
134
+ (link_relationships.required || []).map(&:to_sym)
135
+ end
136
+
137
+ def link_attributes
138
+ link.schema.properties["data"].properties["attributes"]
139
+ end
140
+
141
+ def link_relationships
142
+ link.schema.properties["data"].properties["relationships"]
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+ require 'agave/local/json_api_entity'
3
+
4
+ module Agave
5
+ module Local
6
+ class EntitiesRepo
7
+ attr_reader :entities
8
+
9
+ def initialize(*payloads)
10
+ @entities = {}
11
+
12
+ payloads.each do |payload|
13
+ EntitiesRepo.payload_entities(payload).each do |entity_payload|
14
+ object = JsonApiEntity.new(entity_payload, self)
15
+ @entities[object.type] ||= {}
16
+ @entities[object.type][object.id] = object
17
+ end
18
+ end
19
+ end
20
+
21
+ def find_entities_of_type(type)
22
+ entities.fetch(type, {}).values
23
+ end
24
+
25
+ def find_entity(type, id)
26
+ entities.fetch(type, {}).fetch(id, nil)
27
+ end
28
+
29
+ def self.payload_entities(payload)
30
+ acc = []
31
+
32
+ if payload[:data]
33
+ acc = if payload[:data].is_a? Array
34
+ acc + payload[:data]
35
+ else
36
+ acc + [payload[:data]]
37
+ end
38
+ end
39
+
40
+ acc += payload[:included] if payload[:included]
41
+
42
+ acc
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+ module Agave
3
+ module Local
4
+ module FieldType
5
+ class Boolean
6
+ def self.parse(value, _repo)
7
+ value
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ module Agave
4
+ module Local
5
+ module FieldType
6
+ class Color
7
+ attr_reader :red, :green, :blue, :alpha
8
+
9
+ def self.parse(value, _repo)
10
+ value && new(
11
+ value[:red],
12
+ value[:green],
13
+ value[:blue],
14
+ value[:alpha]
15
+ )
16
+ end
17
+
18
+ def initialize(red, green, blue, alpha)
19
+ @red = red
20
+ @green = green
21
+ @blue = blue
22
+ @alpha = alpha / 255.0
23
+ end
24
+
25
+ def rgb
26
+ if alpha == 1.0
27
+ "rgb(#{red}, #{green}, #{blue})"
28
+ else #
29
+ "rgba(#{red}, #{green}, #{blue}, #{alpha})"
30
+ end
31
+ end
32
+
33
+ def hex
34
+ r = red.to_s(16)
35
+ g = green.to_s(16)
36
+ b = blue.to_s(16)
37
+ a = (alpha * 255).to_i.to_s(16)
38
+
39
+ r = "0#{r}" if r.length == 1
40
+ g = "0#{g}" if g.length == 1
41
+ b = "0#{b}" if b.length == 1
42
+ a = "0#{a}" if a.length == 1
43
+
44
+ hex = '#' + r + g + b
45
+
46
+ hex += a if a != 'ff'
47
+
48
+ hex
49
+ end
50
+
51
+ def to_hash(*_args)
52
+ {
53
+ red: red,
54
+ green: green,
55
+ blue: blue,
56
+ rgb: rgb,
57
+ hex: hex
58
+ }
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end