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.
- checksums.yaml +4 -4
- data/config/locales/en.yml +288 -0
- data/config/locales/es.yml +283 -0
- data/lib/sutty/cli/cli.rb +48 -35
- data/lib/sutty/cli/commands/container.rb +4 -1
- data/lib/sutty/cli/commands/field.rb +94 -39
- data/lib/sutty/cli/commands/layout.rb +34 -21
- data/lib/sutty/cli/commands/post.rb +110 -9
- data/lib/sutty/cli/commands/theme.rb +10 -5
- data/lib/sutty/cli/i18n.rb +12 -0
- data/lib/sutty/cli/version.rb +1 -1
- data/lib/sutty/drop_stub.rb +10 -0
- data/lib/sutty/schema.rb +250 -0
- metadata +79 -29
- data/lib/sutty/cli/templates/field/array.yml.erb +0 -14
- data/lib/sutty/cli/templates/field/belongs_to.yml.erb +0 -16
- data/lib/sutty/cli/templates/field/boolean.yml.erb +0 -11
- data/lib/sutty/cli/templates/field/color.yml.erb +0 -11
- data/lib/sutty/cli/templates/field/content.yml.erb +0 -14
- data/lib/sutty/cli/templates/field/date.yml.erb +0 -11
- data/lib/sutty/cli/templates/field/email.yml.erb +0 -14
- data/lib/sutty/cli/templates/field/encrypted_text.yml.erb +0 -11
- data/lib/sutty/cli/templates/field/event.yml.erb +0 -40
- data/lib/sutty/cli/templates/field/file.yml.erb +0 -19
- data/lib/sutty/cli/templates/field/geo.yml.erb +0 -17
- data/lib/sutty/cli/templates/field/has_and_belongs_to_many.yml.erb +0 -16
- data/lib/sutty/cli/templates/field/has_many.yml.erb +0 -16
- data/lib/sutty/cli/templates/field/image.yml.erb +0 -19
- data/lib/sutty/cli/templates/field/locales.yml.erb +0 -11
- data/lib/sutty/cli/templates/field/markdown.yml.erb +0 -14
- data/lib/sutty/cli/templates/field/markdown_content.yml.erb +0 -14
- data/lib/sutty/cli/templates/field/number.yml.erb +0 -11
- data/lib/sutty/cli/templates/field/order.yml.erb +0 -11
- data/lib/sutty/cli/templates/field/predefined_array.yml.erb +0 -18
- data/lib/sutty/cli/templates/field/related_posts.yml.erb +0 -15
- data/lib/sutty/cli/templates/field/string.yml.erb +0 -14
- data/lib/sutty/cli/templates/field/tel.yml.erb +0 -14
- data/lib/sutty/cli/templates/field/text.yml.erb +0 -14
- 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,
|
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 =
|
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[
|
40
|
-
@data['layout'] = options[
|
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[
|
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[
|
59
|
+
@slug ||= Jekyll::Utils.slugify(options['title'])
|
53
60
|
end
|
54
61
|
|
55
62
|
def dir
|
56
|
-
@dir ||= '_' + options[
|
63
|
+
@dir ||= '_' + options['locale']
|
57
64
|
end
|
58
65
|
|
59
66
|
def path
|
60
|
-
@path ||= File.join(dir, options[
|
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
|
35
|
+
@theme_name ||= "#{name}-jekyll-theme"
|
34
36
|
end
|
35
37
|
|
38
|
+
# @return [String]
|
36
39
|
def origin
|
37
|
-
@origin ||=
|
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
|
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
|
data/lib/sutty/cli/version.rb
CHANGED
data/lib/sutty/schema.rb
ADDED
@@ -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
|