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,93 @@
1
+ # frozen_string_literal: true
2
+ require 'thor'
3
+ require 'agave/dump/runner'
4
+ require 'agave/dump/ssg_detector'
5
+ require 'agave/watch/site_change_watcher'
6
+ require 'listen'
7
+ require 'thread'
8
+
9
+ module Agave
10
+ class Cli < Thor
11
+ package_name 'AgaveCMS'
12
+
13
+ desc 'dump', 'dumps AgaveCMS content into local files'
14
+ option :config, default: 'agave.config.rb'
15
+ option :token, default: ENV['AGAVE_API_TOKEN'], required: true
16
+ option :preview, default: false, type: :boolean
17
+ option :watch, default: false, type: :boolean
18
+ def dump
19
+ if !ENV['AGAVE_API_TOKEN']
20
+ say 'Site token is not specified! Please configure AGAVE_API_TOKEN.'
21
+ exit 0
22
+ end
23
+
24
+ config_file = File.expand_path(options[:config])
25
+ watch_mode = options[:watch]
26
+ preview_mode = options[:preview]
27
+
28
+ client = Agave::Site::Client.new(
29
+ extra_headers: {
30
+ 'X-Reason' => 'dump',
31
+ 'X-SSG' => Dump::SsgDetector.new(Dir.pwd).detect
32
+ }
33
+ )
34
+
35
+ if watch_mode
36
+ site_id = client.request(:get, '/api/site')['data']['id']
37
+
38
+ semaphore = Mutex.new
39
+
40
+ thread_safe_dump(semaphore, config_file, client, preview_mode)
41
+
42
+ Agave::Watch::SiteChangeWatcher.new(site_id).connect do
43
+ thread_safe_dump(semaphore, config_file, client, preview_mode)
44
+ end
45
+
46
+ watch_config_file(config_file) do
47
+ thread_safe_dump(semaphore, config_file, client, preview_mode)
48
+ end
49
+
50
+ sleep
51
+ else
52
+ Dump::Runner.new(config_file, client, preview_mode).run
53
+ end
54
+ end
55
+
56
+ desc 'check', 'checks the presence of a AgaveCMS token'
57
+ def check
58
+ exit 0 if ENV['AGAVE_API_TOKEN']
59
+
60
+ say 'Site token is not specified!'
61
+ token = ask "Please paste your AgaveCMS site read-only API token:\n>"
62
+
63
+ if !token || token.empty?
64
+ puts 'Missing token'
65
+ exit 1
66
+ end
67
+
68
+ File.open('.env', 'a') do |file|
69
+ file.puts "AGAVE_API_TOKEN=#{token}"
70
+ end
71
+
72
+ say 'Token added to .env file.'
73
+
74
+ exit 0
75
+ end
76
+
77
+ no_tasks do
78
+ def watch_config_file(config_file, &block)
79
+ Listen.to(
80
+ File.dirname(config_file),
81
+ only: /#{Regexp.quote(File.basename(config_file))}/,
82
+ &block
83
+ ).start
84
+ end
85
+
86
+ def thread_safe_dump(semaphore, config_file, client, preview_mode)
87
+ semaphore.synchronize do
88
+ Dump::Runner.new(config_file, client, preview_mode).run
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+ require 'agave/dump/operation/add_to_data_file'
3
+
4
+ module Agave
5
+ module Dump
6
+ module Dsl
7
+ module AddToDataFile
8
+ def add_to_data_file(*args)
9
+ operations.add Operation::AddToDataFile.new(operations, *args)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+ require 'agave/dump/operation/create_data_file'
3
+
4
+ module Agave
5
+ module Dump
6
+ module Dsl
7
+ module CreateDataFile
8
+ def create_data_file(*args)
9
+ operations.add Operation::CreateDataFile.new(operations, *args)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+ require 'agave/dump/operation/create_post'
3
+
4
+ module Agave
5
+ module Dump
6
+ module Dsl
7
+ class DataFile
8
+ def initialize(operation, &block)
9
+ @operation = operation
10
+ instance_eval(&block)
11
+ end
12
+
13
+ def frontmatter(format, value)
14
+ @operation.frontmatter_format = format
15
+ @operation.frontmatter_value = value
16
+ end
17
+
18
+ def content(value)
19
+ @operation.content = value
20
+ end
21
+ end
22
+
23
+ module CreatePost
24
+ def create_post(path, &block)
25
+ operation = Operation::CreatePost.new(operations, path)
26
+ DataFile.new(operation, &block)
27
+
28
+ operations.add operation
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+ require 'agave/dump/dsl/create_post'
3
+ require 'agave/dump/dsl/create_data_file'
4
+ require 'agave/dump/dsl/add_to_data_file'
5
+
6
+ module Agave
7
+ module Dump
8
+ module Dsl
9
+ class Directory
10
+ include Dsl::CreateDataFile
11
+ include Dsl::CreatePost
12
+ include Dsl::AddToDataFile
13
+
14
+ attr_reader :agave, :operations
15
+
16
+ def initialize(agave, operations, &block)
17
+ @agave = agave
18
+ @operations = operations
19
+ @self_before_instance_eval = eval 'self', block.binding
20
+
21
+ instance_eval(&block)
22
+ end
23
+
24
+ def method_missing(method, *args, &block)
25
+ @self_before_instance_eval.send method, *args, &block
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+ require 'agave/dump/dsl/directory'
3
+ require 'agave/dump/dsl/create_post'
4
+ require 'agave/dump/dsl/create_data_file'
5
+ require 'agave/dump/dsl/add_to_data_file'
6
+
7
+ require 'agave/dump/operation/directory'
8
+
9
+ module Agave
10
+ module Dump
11
+ module Dsl
12
+ class Root
13
+ include Dsl::CreateDataFile
14
+ include Dsl::CreatePost
15
+ include Dsl::AddToDataFile
16
+
17
+ attr_reader :agave, :operations
18
+
19
+ def initialize(config_code, agave, operations)
20
+ @agave = agave
21
+ @operations = operations
22
+
23
+ # rubocop:disable Lint/Eval
24
+ eval(config_code)
25
+ # rubocop:enable Lint/Eval
26
+ end
27
+
28
+ def directory(path, &block)
29
+ operation = Operation::Directory.new(operations, path)
30
+ operations.add operation
31
+
32
+ Directory.new(agave, operation, &block)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+ require 'agave/dump/format/toml'
3
+ require 'agave/dump/format/yaml'
4
+
5
+ module Agave
6
+ module Dump
7
+ module Format
8
+ def self.dump(format, value)
9
+ converter_for(format).dump(value)
10
+ end
11
+
12
+ def self.frontmatter_dump(format, value)
13
+ converter_for(format).frontmatter_dump(value)
14
+ end
15
+
16
+ def self.converter_for(format)
17
+ case format.to_sym
18
+ when :toml
19
+ Format::Toml
20
+ when :yaml, :yml
21
+ Format::Yaml
22
+ when :json
23
+ Format::Json
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ require 'json'
3
+
4
+ module Agave
5
+ module Dump
6
+ module Format
7
+ module Json
8
+ def self.dump(value)
9
+ JSON.dump(value)
10
+ end
11
+
12
+ def self.frontmatter_dump(value)
13
+ "#{dump(value)}\n"
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+ require 'active_support/core_ext/hash/keys'
3
+ require 'toml'
4
+
5
+ class Time
6
+ def to_toml(_path = '')
7
+ utc.strftime('%Y-%m-%dT%H:%M:%SZ')
8
+ end
9
+ end
10
+
11
+ class Date
12
+ def to_toml(_path = '')
13
+ strftime('%Y-%m-%d')
14
+ end
15
+ end
16
+
17
+ module Agave
18
+ module Dump
19
+ module Format
20
+ module Toml
21
+ def self.dump(value)
22
+ TOML::Generator.new(value).body
23
+ end
24
+
25
+ def self.frontmatter_dump(value)
26
+ "+++\n#{dump(value)}+++"
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+ require 'active_support/core_ext/hash/keys'
3
+ require 'yaml'
4
+
5
+ require 'agave/local/item'
6
+
7
+ class Array
8
+ def deep_stringify_keys
9
+ each_with_object([]) do |value, accum|
10
+ if value.is_a?(Hash) || value.is_a?(Array)
11
+ new_val = value.deep_stringify_keys
12
+ accum.push new_val
13
+ else
14
+ accum.push value
15
+ end
16
+ accum
17
+ end
18
+ end
19
+ end
20
+
21
+ module Agave
22
+ module Dump
23
+ module Format
24
+ module Yaml
25
+ def self.deep_hashify_items(value)
26
+ case value
27
+ when Array
28
+ value.map { |v| deep_hashify_items(v) }
29
+ when Hash
30
+ value.each_with_object({}) do |(k, v), acc|
31
+ acc[k] = deep_hashify_items(v)
32
+ end
33
+ when ::Agave::Local::Item
34
+ value.to_hash
35
+ else
36
+ if value.respond_to?(:to_hash)
37
+ value.to_hash
38
+ else
39
+ value
40
+ end
41
+ end
42
+ end
43
+
44
+ def self.dump(value)
45
+ plain = deep_hashify_items(value)
46
+ YAML.dump(plain.deep_stringify_keys).chomp.gsub(/^\-+\n/, '')
47
+ end
48
+
49
+ def self.frontmatter_dump(value)
50
+ "---\n#{dump(value)}\n---"
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+ require 'agave/dump/format'
3
+
4
+ module Agave
5
+ module Dump
6
+ module Operation
7
+ class AddToDataFile
8
+ attr_reader :context, :path, :format, :value
9
+
10
+ def initialize(context, path, format, value)
11
+ @context = context
12
+ @path = path
13
+ @format = format
14
+ @value = value
15
+ end
16
+
17
+ def perform
18
+ complete_path = File.join(context.path, path)
19
+ FileUtils.mkdir_p(File.dirname(complete_path))
20
+
21
+ content_to_add = Format.dump(format, value)
22
+
23
+ old_content = if File.exist? complete_path
24
+ ::File.read(complete_path)
25
+ else
26
+ ''
27
+ end
28
+
29
+ new_content = old_content.sub(
30
+ /\n*(#\s*agavecms:start.*#\s*agavecms:end|\Z)/m,
31
+ "\n\n# agavecms:start\n#{content_to_add}\n# agavecms:end"
32
+ )
33
+
34
+ File.open(complete_path, 'w') do |f|
35
+ f.write new_content
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+ require 'fileutils'
3
+ require 'agave/dump/format'
4
+
5
+ module Agave
6
+ module Dump
7
+ module Operation
8
+ class CreateDataFile
9
+ attr_reader :context, :path, :format, :value
10
+
11
+ def initialize(context, path, format, value)
12
+ @context = context
13
+ @path = path
14
+ @format = format
15
+ @value = value
16
+ end
17
+
18
+ def perform
19
+ FileUtils.mkdir_p(File.dirname(path))
20
+
21
+ File.open(File.join(context.path, path), 'w') do |file|
22
+ file.write Format.dump(format, value)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+ require 'fileutils'
3
+ require 'agave/dump/format'
4
+
5
+ module Agave
6
+ module Dump
7
+ module Operation
8
+ class CreatePost
9
+ attr_reader :context, :path
10
+
11
+ attr_accessor :frontmatter_format, :frontmatter_value
12
+ attr_accessor :content
13
+
14
+ def initialize(context, path)
15
+ @context = context
16
+ @path = path
17
+ end
18
+
19
+ def perform
20
+ FileUtils.mkdir_p(File.dirname(path))
21
+
22
+ File.open(File.join(context.path, path), 'w') do |file|
23
+ file.write Format.frontmatter_dump(
24
+ frontmatter_format,
25
+ frontmatter_value
26
+ )
27
+ file.write "\n\n"
28
+ file.write content
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end