sutty-cli 0.1.1 → 0.3.0rc2

Sign up to get free protection for your applications and to get access to all the features.
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