sutty-cli 0.1.1 → 0.3.0rc2

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/config/locales/en.yml +288 -0
  3. data/config/locales/es.yml +283 -0
  4. data/lib/sutty/cli/cli.rb +48 -35
  5. data/lib/sutty/cli/commands/container.rb +4 -1
  6. data/lib/sutty/cli/commands/field.rb +94 -39
  7. data/lib/sutty/cli/commands/layout.rb +34 -21
  8. data/lib/sutty/cli/commands/post.rb +110 -9
  9. data/lib/sutty/cli/commands/theme.rb +10 -5
  10. data/lib/sutty/cli/i18n.rb +12 -0
  11. data/lib/sutty/cli/version.rb +1 -1
  12. data/lib/sutty/drop_stub.rb +10 -0
  13. data/lib/sutty/schema.rb +250 -0
  14. metadata +79 -29
  15. data/lib/sutty/cli/templates/field/array.yml.erb +0 -14
  16. data/lib/sutty/cli/templates/field/belongs_to.yml.erb +0 -16
  17. data/lib/sutty/cli/templates/field/boolean.yml.erb +0 -11
  18. data/lib/sutty/cli/templates/field/color.yml.erb +0 -11
  19. data/lib/sutty/cli/templates/field/content.yml.erb +0 -14
  20. data/lib/sutty/cli/templates/field/date.yml.erb +0 -11
  21. data/lib/sutty/cli/templates/field/email.yml.erb +0 -14
  22. data/lib/sutty/cli/templates/field/encrypted_text.yml.erb +0 -11
  23. data/lib/sutty/cli/templates/field/event.yml.erb +0 -40
  24. data/lib/sutty/cli/templates/field/file.yml.erb +0 -19
  25. data/lib/sutty/cli/templates/field/geo.yml.erb +0 -17
  26. data/lib/sutty/cli/templates/field/has_and_belongs_to_many.yml.erb +0 -16
  27. data/lib/sutty/cli/templates/field/has_many.yml.erb +0 -16
  28. data/lib/sutty/cli/templates/field/image.yml.erb +0 -19
  29. data/lib/sutty/cli/templates/field/locales.yml.erb +0 -11
  30. data/lib/sutty/cli/templates/field/markdown.yml.erb +0 -14
  31. data/lib/sutty/cli/templates/field/markdown_content.yml.erb +0 -14
  32. data/lib/sutty/cli/templates/field/number.yml.erb +0 -11
  33. data/lib/sutty/cli/templates/field/order.yml.erb +0 -11
  34. data/lib/sutty/cli/templates/field/predefined_array.yml.erb +0 -18
  35. data/lib/sutty/cli/templates/field/related_posts.yml.erb +0 -15
  36. data/lib/sutty/cli/templates/field/string.yml.erb +0 -14
  37. data/lib/sutty/cli/templates/field/tel.yml.erb +0 -14
  38. data/lib/sutty/cli/templates/field/text.yml.erb +0 -14
  39. data/lib/sutty/cli/templates/field/url.yml.erb +0 -14
@@ -6,6 +6,7 @@ require 'tty-file'
6
6
  require 'yaml'
7
7
  require 'securerandom'
8
8
  require 'jekyll/utils'
9
+ require 'faker'
9
10
 
10
11
  module Sutty
11
12
  module Cli
@@ -16,7 +17,8 @@ module Sutty
16
17
  CONTENT_FIELDS = %w[content markdown_content].freeze
17
18
 
18
19
  def initialize(options)
19
- @options = options
20
+ @options = options.to_h
21
+ @options['title'] ||= random_string(10)
20
22
  end
21
23
 
22
24
  def execute(input: $stdin, output: $stdout)
@@ -25,7 +27,10 @@ module Sutty
25
27
  return true
26
28
  end
27
29
 
28
- TTY::File.create_file path, YAML.dump(data) + "---\n\n"
30
+ TTY::File.create_file path,
31
+ YAML.dump(data) +
32
+ "---\n\n" +
33
+ (content? ? random_markdown : '')
29
34
  end
30
35
 
31
36
  private
@@ -33,11 +38,13 @@ module Sutty
33
38
  def data
34
39
  return @data if @data
35
40
 
36
- @data = Hash[(data_layout.keys - CONTENT_FIELDS).map { |k| [k, nil] }]
41
+ @data = (data_layout.keys - CONTENT_FIELDS).map do |k|
42
+ [k, generate_data(k)]
43
+ end.to_h
37
44
 
38
45
  # Required fields
39
- @data['title'] = options[:title]
40
- @data['layout'] = options[:layout]
46
+ @data['title'] = options['title']
47
+ @data['layout'] = options['layout']
41
48
  @data['uuid'] = SecureRandom.uuid
42
49
  @data['liquid'] = false
43
50
 
@@ -45,24 +52,118 @@ module Sutty
45
52
  end
46
53
 
47
54
  def data_layout
48
- @data_layout ||= YAML.safe_load(File.read(File.join('_data', 'layouts', options[:layout] + '.yml')))
55
+ @data_layout ||= YAML.safe_load(File.read(File.join('_data', 'layouts', options['layout'] + '.yml')))
49
56
  end
50
57
 
51
58
  def slug
52
- @slug ||= Jekyll::Utils.slugify(options[:title])
59
+ @slug ||= Jekyll::Utils.slugify(options['title'])
53
60
  end
54
61
 
55
62
  def dir
56
- @dir ||= '_' + options[:locale]
63
+ @dir ||= '_' + options['locale']
57
64
  end
58
65
 
59
66
  def path
60
- @path ||= File.join(dir, options[:date] + '-' + slug + '.markdown')
67
+ @path ||= File.join(dir, options['date'] + '-' + slug + '.markdown')
61
68
  end
62
69
 
63
70
  def logger
64
71
  @logger ||= TTY::Logger.new
65
72
  end
73
+
74
+ def generate_data(key)
75
+ # TODO: Generate private data
76
+ return if data_layout[key]['private']
77
+ # Sometimes data is optional
78
+ return unless data_layout[key]['required'] || random_boolean
79
+
80
+ case data_layout[key]['type']
81
+ when 'string' then random_text(1)
82
+ when 'text' then random_text(3)
83
+ when 'markdown' then random_markdown(3)
84
+ when 'number' then random_number(255)
85
+ when 'order' then random_number(255)
86
+ when 'tel' then Faker::PhoneNumber.phone_number
87
+ when 'date' then Faker::Date.in_date_period
88
+ when 'array' then Array.new(random_number(10)) { random_string(random_number(3)) }
89
+ when 'predefined_array' then data_layout[key]['values'].keys.sample
90
+ when 'boolean' then random_boolean
91
+ when 'color' then random_color
92
+ when 'email' then Faker::Internet.email
93
+ when 'url' then Faker::Internet.url
94
+ when 'file' then random_file
95
+ when 'image' then random_file
96
+ when 'belongs_to' then random_post(key)
97
+ when 'has_many' then random_posts(key)
98
+ when 'has_and_belongs_to_many' then random_posts(key)
99
+ when 'locales' then random_posts(key)
100
+ when 'related_posts' then random_posts
101
+ when 'geo'
102
+ {
103
+ 'lat' => Faker::Address.latitude,
104
+ 'lng' => Faker::Address.longitude
105
+ }
106
+ end
107
+ end
108
+
109
+ def random_number(digits)
110
+ rand(1..digits)
111
+ end
112
+
113
+ def random_text(longitude = nil)
114
+ longitude ||= long? ? random_number(20) : random_number(5)
115
+
116
+ Faker::Lorem.paragraphs(number: longitude).join("\n\n")
117
+ end
118
+
119
+ def random_string(longitude = random_number(10))
120
+ Faker::Lorem.sentence(word_count: longitude)
121
+ end
122
+
123
+ def random_markdown(longitude = nil)
124
+ longitude ||= long? ? random_number(20) : random_number(5)
125
+
126
+ Faker::Markdown.sandwich(sentences: longitude,
127
+ repeat: long? ? random_number(5) : 1)
128
+ end
129
+
130
+ def long?
131
+ options['content'] == 'long'
132
+ end
133
+
134
+ def random_color
135
+ Random.bytes(3).unpack1('H*')
136
+ end
137
+
138
+ def random_boolean
139
+ @random_boolean ||= [true,false]
140
+
141
+ @random_boolean.sample
142
+ end
143
+
144
+ def random_file
145
+ {
146
+ 'path' => 'public/placeholder.png',
147
+ 'description' => random_string(1)
148
+ }
149
+ end
150
+
151
+ # TODO: Implement relationships between posts
152
+ def random_post(key = nil)
153
+ SecureRandom.uuid
154
+ end
155
+
156
+ def random_posts(key = nil)
157
+ Array.new(random_number(10)) { random_post(key) }
158
+ end
159
+
160
+ def types
161
+ @types ||= data_layout.values.map { |v| v['type'] }.uniq
162
+ end
163
+
164
+ def content?
165
+ @content ||= CONTENT_FIELDS.map { |f| types.include? f }.any?
166
+ end
66
167
  end
67
168
  end
68
169
  end
@@ -6,6 +6,8 @@ require 'tty-command'
6
6
  module Sutty
7
7
  module Cli
8
8
  module Commands
9
+ # Genera una plantilla nueva, a partir de sutty-base-jekyll-theme
10
+ # y realiza la configuración inicial del repositorio.
9
11
  class Theme < Sutty::Cli::Command
10
12
  attr_reader :name
11
13
 
@@ -20,27 +22,30 @@ module Sutty
20
22
  Dir.chdir theme_name do
21
23
  cmd.run('git remote rename origin upstream')
22
24
  cmd.run('git remote add origin', origin)
25
+ cmd.run('git lfs fetch --all upstream')
23
26
  cmd.run('git push -u origin master')
24
- cmd.run('bundle install')
25
- cmd.run('yarn install')
26
27
  cmd.run('git mv sutty-base-jekyll-theme.gemspec', gemspec)
27
28
  end
28
29
  end
29
30
 
30
31
  private
31
32
 
33
+ # @return [String]
32
34
  def theme_name
33
- @theme_name ||= name + '-jekyll-theme'
35
+ @theme_name ||= "#{name}-jekyll-theme"
34
36
  end
35
37
 
38
+ # @return [String]
36
39
  def origin
37
- @origin ||= 'git@0xacab.org:sutty/jekyll/' + theme_name + '.git'
40
+ @origin ||= "git@0xacab.org:sutty/jekyll/#{theme_name}.git"
38
41
  end
39
42
 
43
+ # @return [String]
40
44
  def gemspec
41
- @gemspec ||= theme_name + '.gemspec'
45
+ @gemspec ||= "#{theme_name}.gemspec"
42
46
  end
43
47
 
48
+ # @return [TTY::Command]
44
49
  def cmd
45
50
  @cmd ||= TTY::Command.new
46
51
  end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'i18n'
4
+
5
+ locale = ENV['LANG'].split('_', 2).first.to_sym
6
+ locales = Dir["#{File.expand_path(File.join(__dir__, '..', '..', '..', 'config', 'locales'))}/*.yml"]
7
+
8
+ I18n::Backend::Simple.include(I18n::Backend::Memoize)
9
+ I18n.load_path = locales
10
+ I18n.available_locales = locales.map { |l| File.basename(l, '.yml') }.map(&:to_sym)
11
+ I18n.default_locale = :es
12
+ I18n.locale = I18n.available_locales.include?(locale) ? locale : :es
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Sutty
4
4
  module Cli
5
- VERSION = "0.1.1"
5
+ VERSION = "0.3.0rc2"
6
6
  end
7
7
  end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A veces necesitamos una versión falsa de esto para Jekyll::Utils
4
+ unless Object.const_defined? 'Jekyll::Drops::Drop'
5
+ module Jekyll
6
+ module Drops
7
+ class Drop ; end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,250 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jekyll/utils'
4
+
5
+ module Sutty
6
+ # Genera definiciones de campos para Sutty
7
+ class Schema
8
+ class << self
9
+ ATTRIBUTES = %i[type required private writable inverse filter]
10
+
11
+ # Definición base.
12
+ #
13
+ # Se eliminan los valores que son falsos o están vacíos.
14
+ #
15
+ # @return [Hash]
16
+ def base(**args)
17
+ validate_field_type(args[:type])
18
+
19
+ args.slice(*ATTRIBUTES).transform_keys(&:to_s).reject do |_, v|
20
+ v == false || v.nil?
21
+ end.merge(t(**args))
22
+ end
23
+
24
+ def create(type:, **args)
25
+ validate_field_type(type)
26
+
27
+ public_send :"#{type}_field", **args
28
+ end
29
+
30
+ # Aplica traducciones
31
+ #
32
+ # @param :type [String]
33
+ # @param :name [String]
34
+ # @return [Hash] traducciones
35
+ def t(**args)
36
+ require_relative 'drop_stub'
37
+
38
+ I18n.available_locales.map do |locale|
39
+ I18n.with_locale(locale) do
40
+ %i[name type].map do |translation|
41
+ translation = I18n.t("fields.common_by_#{translation}.#{args[translation]}")
42
+
43
+ next if translation.is_a? String
44
+
45
+ translation
46
+ end.compact.first
47
+ end
48
+ end.compact.reduce({}) do |t, l|
49
+ Jekyll::Utils.deep_merge_hashes(t, l)
50
+ end
51
+ end
52
+
53
+ # Texto de una sola línea
54
+ #
55
+ # @return [Hash]
56
+ def string_field(name:, **args)
57
+ { name => base(type: 'string', name: name, **args) }
58
+ end
59
+
60
+ # Texto de varias líneas
61
+ #
62
+ # @return [Hash]
63
+ def text_field(name:, **args)
64
+ { name => base(type: 'text', name: name, **args) }
65
+ end
66
+
67
+ # Números
68
+ #
69
+ # @return [Hash]
70
+ def number_field(name:, **args)
71
+ { name => base(type: 'number', name: name, **args) }
72
+ end
73
+
74
+ # URL
75
+ #
76
+ # @return [Hash]
77
+ def url_field(name:, **args)
78
+ { name => base(type: 'url', name: name, **args) }
79
+ end
80
+
81
+ # E-mail
82
+ #
83
+ # @return [Hash]
84
+ def email_field(name:, **args)
85
+ { name => base(type: 'email', name: name, **args) }
86
+ end
87
+
88
+ # Teléfono
89
+ #
90
+ # @return [Hash]
91
+ def tel_field(name:, **args)
92
+ { name => base(type: 'tel', name: name, **args) }
93
+ end
94
+
95
+ # Color
96
+ #
97
+ # @return [Hash]
98
+ def color_field(name:, **args)
99
+ { name => base(type: 'color', name: name, **args) }
100
+ end
101
+
102
+ # SKU
103
+ #
104
+ # @return [Hash]
105
+ def sku_field(name:, **args)
106
+ { name => base(type: 'sku', name: name, **args) }
107
+ end
108
+
109
+ # Precio. Los precios se escriben una sola vez.
110
+ #
111
+ # @return [Hash]
112
+ def price_field(name:, **args)
113
+ { name => base(type: 'price', name: name, writable: 'once', **args) }
114
+ end
115
+
116
+ # Permalinks
117
+ #
118
+ # @return [Hash]
119
+ def permalink_field(name:, **args)
120
+ { name => base(type: 'permalink', name: name, **args) }
121
+ end
122
+
123
+ # Orden
124
+ #
125
+ # @return [Hash]
126
+ def order_field(name:, **args)
127
+ { name => base(type: 'order', name: name, **args) }
128
+ end
129
+
130
+ # Verdadero/falso
131
+ #
132
+ # @return [Hash]
133
+ def boolean_field(name:, **args)
134
+ { name => base(type: 'boolean', name: name, **args) }
135
+ end
136
+
137
+ # Contenido
138
+ #
139
+ # @return [Hash]
140
+ def content_field(name:, **args)
141
+ { name => base(type: 'content', name: name, **args) }
142
+ end
143
+
144
+ # HTML
145
+ #
146
+ # @return [Hash]
147
+ def html_field(name:, **args)
148
+ { name => base(type: 'html', name: name, **args) }
149
+ end
150
+
151
+ # Oculto
152
+ #
153
+ # @return [Hash]
154
+ def hidden_field(name:, **args)
155
+ { name => base(type: 'hidden', name: name, **args) }
156
+ end
157
+
158
+ # Fecha
159
+ #
160
+ # @return [Hash]
161
+ def date_field(name:, **args)
162
+ { name => base(type: 'date', name: name, **args) }
163
+ end
164
+
165
+ # Coordenadas geográficas
166
+ #
167
+ # @return [Hash]
168
+ def geo_field(name:, **args)
169
+ { name => base(type: 'geo', name: name, **args) }
170
+ end
171
+
172
+ # Archivo
173
+ #
174
+ # @return [Hash]
175
+ def file_field(name:, **args)
176
+ { name => base(type: 'file', name: name, **args) }
177
+ end
178
+
179
+ # Imagen
180
+ #
181
+ # @return [Hash]
182
+ def image_field(name:, **args)
183
+ { name => base(type: 'image', name: name, **args) }
184
+ end
185
+
186
+ # Array
187
+ #
188
+ # @return [Hash]
189
+ def array_field(name:, **args)
190
+ { name => base(type: 'array', name: name, **args) }
191
+ end
192
+
193
+ # Relacionados sin relación recíproca
194
+ #
195
+ # @return [Hash]
196
+ def related_posts_field(name:, **args)
197
+ { name => base(type: 'related_posts', name: name, **args) }
198
+ end
199
+
200
+ # Relación de muchos a uno, la relación recíproca es has_many
201
+ #
202
+ # @return [Hash]
203
+ def belongs_to_field(name:, filter:, value:, **args)
204
+ { name => base(type: 'belongs_to', name: name, filter: { filter => value }, **args) }
205
+ end
206
+
207
+ # Relación de uno a muchos, la relación recíproca es belongs_to
208
+ #
209
+ # @return [Hash]
210
+ def has_many_field(name:, filter:, value:, **args)
211
+ { name => base(type: 'has_many', name: name, filter: { filter => value }, **args) }
212
+ end
213
+
214
+ # Relación de muchos a muchos, la relación recíproca es
215
+ # has_and_belongs_to_many.
216
+ #
217
+ # @return [Hash]
218
+ def has_and_belongs_to_many_field(name:, filter:, value:, **args)
219
+ { name => base(type: 'has_and_belongs_to_many', name: name, filter: { filter => value }, **args) }
220
+ end
221
+
222
+ # Los tipos de campos válidos son los que esta clase define como
223
+ # métodos terminados en _field
224
+ #
225
+ # @return [Array]
226
+ def field_types
227
+ @field_types ||= self.public_methods.select do |m|
228
+ m.to_s.end_with? '_field'
229
+ end.map do |m|
230
+ m.to_s.sub(/_field\z/, '').to_sym
231
+ end.sort
232
+ end
233
+
234
+ # Valida si el tipo de campo está definido
235
+ #
236
+ # @param [String,Symbol]
237
+ # @return [Boolean]
238
+ def field_type?(type)
239
+ self.respond_to? :"#{type}_field"
240
+ end
241
+
242
+ # Devuelve un error si el tipo del campo no es válido
243
+ #
244
+ # @return [Nil]
245
+ def validate_field_type(type)
246
+ raise ArgumentError, "#{type} must be one of #{field_types.map(&:to_s).join(', ')}" unless field_type? type
247
+ end
248
+ end
249
+ end
250
+ end