agave-client 0.1.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.
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