sutty-cli 0.1.0 → 0.3.0rc1
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 +3 -1
- data/lib/sutty/drop_stub.rb +10 -0
- data/lib/sutty/schema.rb +250 -0
- metadata +80 -30
- 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
data/lib/sutty/cli/cli.rb
CHANGED
@@ -4,6 +4,8 @@ require 'thor'
|
|
4
4
|
require 'date'
|
5
5
|
require_relative 'commands/layout'
|
6
6
|
require_relative 'commands/field'
|
7
|
+
require_relative 'i18n'
|
8
|
+
require_relative '../schema'
|
7
9
|
|
8
10
|
module Sutty
|
9
11
|
module Cli
|
@@ -15,43 +17,48 @@ module Sutty
|
|
15
17
|
# Error raised by this runner
|
16
18
|
Error = Class.new(StandardError)
|
17
19
|
|
18
|
-
desc 'version', '
|
20
|
+
desc I18n.t('version.desc.example'), I18n.t('version.desc.text')
|
19
21
|
def version
|
20
22
|
require_relative 'version'
|
21
23
|
puts "v#{Sutty::Cli::VERSION}"
|
22
24
|
end
|
23
25
|
map %w(--version -v) => :version
|
24
26
|
|
25
|
-
desc 'post
|
26
|
-
method_option :help, aliases: '-h', type: :boolean,
|
27
|
-
desc: 'Display usage information'
|
27
|
+
desc I18n.t('post.desc.example'), I18n.t('post.desc.text')
|
28
|
+
method_option :help, aliases: '-h', type: :boolean, desc: I18n.t('help')
|
28
29
|
|
29
30
|
method_option :layout,
|
30
31
|
aliases: '-l',
|
31
32
|
type: :string,
|
32
|
-
desc: '
|
33
|
+
desc: I18n.t('post.flags.layout'),
|
33
34
|
required: true,
|
34
35
|
enum: Sutty::Cli::Commands::Layout.layouts
|
35
36
|
|
36
37
|
method_option :title,
|
37
38
|
aliases: '-t',
|
38
39
|
type: :string,
|
39
|
-
desc: '
|
40
|
-
required: true
|
40
|
+
desc: I18n.t('post.flags.title')
|
41
41
|
|
42
42
|
method_option :date,
|
43
43
|
aliases: '-d',
|
44
44
|
type: :string,
|
45
|
-
desc: '
|
45
|
+
desc: I18n.t('post.flags.date'),
|
46
46
|
default: Date.today.to_s
|
47
47
|
|
48
48
|
# TODO: Bring locales from Jekyll configuration
|
49
49
|
method_option :locale,
|
50
50
|
aliases: '-L',
|
51
51
|
type: :string,
|
52
|
-
desc: '
|
52
|
+
desc: I18n.t('post.flags.locale'),
|
53
53
|
default: 'posts'
|
54
54
|
|
55
|
+
method_option :content,
|
56
|
+
aliases: '-c',
|
57
|
+
type: :string,
|
58
|
+
desc: I18n.t('post.flags.content'),
|
59
|
+
default: 'short',
|
60
|
+
enum: %w[short long]
|
61
|
+
|
55
62
|
def post
|
56
63
|
if options[:help]
|
57
64
|
invoke :help, ['post']
|
@@ -61,9 +68,8 @@ module Sutty
|
|
61
68
|
end
|
62
69
|
end
|
63
70
|
|
64
|
-
desc 'container
|
65
|
-
method_option :help, aliases: '-h', type: :boolean,
|
66
|
-
desc: 'Display usage information'
|
71
|
+
desc I18n.t('container.desc.example'), I18n.t('container.desc.text')
|
72
|
+
method_option :help, aliases: '-h', type: :boolean, desc: I18n.t('help')
|
67
73
|
def container(name)
|
68
74
|
if options[:help]
|
69
75
|
invoke :help, ['container']
|
@@ -73,59 +79,63 @@ module Sutty
|
|
73
79
|
end
|
74
80
|
end
|
75
81
|
|
76
|
-
desc 'field
|
77
|
-
long_desc
|
78
|
-
A field is a data type with a name, from which Sutty can build a
|
79
|
-
form on the panel and use it to validate posts.
|
80
|
-
|
81
|
-
After you add a field, edit the layout file and add labels and
|
82
|
-
help description in different languages.
|
83
|
-
EOD
|
82
|
+
desc I18n.t('field.desc.example'), I18n.t('field.desc.text')
|
83
|
+
long_desc I18n.t('field.desc.long')
|
84
84
|
|
85
85
|
method_option :help,
|
86
86
|
aliases: '-h',
|
87
87
|
type: :boolean,
|
88
|
-
desc: '
|
88
|
+
desc: I18n.t('help')
|
89
|
+
|
90
|
+
method_option :before,
|
91
|
+
type: :string,
|
92
|
+
desc: I18n.t('field.flags.before'),
|
93
|
+
default: 'order'
|
94
|
+
|
95
|
+
method_option :force,
|
96
|
+
type: :boolean,
|
97
|
+
desc: I18n.t('field.flags.force'),
|
98
|
+
default: false
|
89
99
|
|
90
100
|
method_option :layout,
|
91
101
|
aliases: '-l',
|
92
102
|
type: :string,
|
93
|
-
desc: '
|
103
|
+
desc: I18n.t('field.flags.layout'),
|
94
104
|
required: true,
|
95
105
|
enum: Sutty::Cli::Commands::Layout.layouts
|
96
106
|
|
97
107
|
method_option :type,
|
98
108
|
aliases: '-t',
|
99
109
|
type: :string,
|
100
|
-
desc: '
|
110
|
+
desc: I18n.t('field.flags.type'),
|
101
111
|
required: true,
|
102
|
-
enum: Sutty::
|
112
|
+
enum: Sutty::Schema.field_types.map(&:to_s)
|
103
113
|
|
104
114
|
method_option :required,
|
105
115
|
aliases: '-r',
|
106
116
|
type: :boolean,
|
107
|
-
desc: '
|
117
|
+
desc: I18n.t('field.flags.required')
|
108
118
|
|
109
119
|
method_option :private,
|
110
120
|
aliases: '-p',
|
111
121
|
type: :boolean,
|
112
|
-
desc: '
|
122
|
+
desc: I18n.t('field.flags.private')
|
113
123
|
|
114
124
|
method_option :inverse,
|
115
125
|
aliases: '-i',
|
116
126
|
type: :string,
|
117
|
-
desc: '
|
127
|
+
desc: I18n.t('field.flags.inverse'),
|
118
128
|
required: Sutty::Cli::Commands::Field.inverse_required?
|
119
129
|
|
120
130
|
method_option :filter,
|
121
131
|
aliases: '-F',
|
122
132
|
type: :string,
|
123
|
-
desc: '
|
133
|
+
desc: I18n.t('field.flags.filter')
|
124
134
|
|
125
135
|
method_option :value,
|
126
136
|
aliases: '-v',
|
127
137
|
type: :string,
|
128
|
-
desc: '
|
138
|
+
desc: I18n.t('field.flags.value'),
|
129
139
|
required: Sutty::Cli::Commands::Field.value_required?
|
130
140
|
|
131
141
|
def field(name)
|
@@ -136,9 +146,13 @@ module Sutty
|
|
136
146
|
end
|
137
147
|
end
|
138
148
|
|
139
|
-
desc 'layout
|
140
|
-
method_option :help, aliases: '-h', type: :boolean,
|
141
|
-
|
149
|
+
desc I18n.t('layout.desc.example'), I18n.t('layout.desc.text')
|
150
|
+
method_option :help, aliases: '-h', type: :boolean, desc: I18n.t('help')
|
151
|
+
method_option :schema_only,
|
152
|
+
aliases: '-s',
|
153
|
+
type: :boolean,
|
154
|
+
default: false,
|
155
|
+
desc: I18n.t('layout.flags.schema_only')
|
142
156
|
def layout(name)
|
143
157
|
if options[:help]
|
144
158
|
invoke :help, ['layout']
|
@@ -147,9 +161,8 @@ module Sutty
|
|
147
161
|
end
|
148
162
|
end
|
149
163
|
|
150
|
-
desc 'theme
|
151
|
-
method_option :help, aliases: '-h', type: :boolean,
|
152
|
-
desc: 'Display usage information'
|
164
|
+
desc I18n.t('theme.desc.example'), I18n.t('theme.desc.text')
|
165
|
+
method_option :help, aliases: '-h', type: :boolean, desc: I18n.t('help')
|
153
166
|
def theme(name)
|
154
167
|
if options[:help]
|
155
168
|
invoke :help, ['theme']
|
@@ -6,6 +6,7 @@ require 'tty-command'
|
|
6
6
|
module Sutty
|
7
7
|
module Cli
|
8
8
|
module Commands
|
9
|
+
# Genera un contenedor nuevo a partir del esqueleto.
|
9
10
|
class Container < Sutty::Cli::Command
|
10
11
|
attr_reader :name
|
11
12
|
|
@@ -26,10 +27,12 @@ module Sutty
|
|
26
27
|
|
27
28
|
private
|
28
29
|
|
30
|
+
# @return [String]
|
29
31
|
def origin
|
30
|
-
@origin ||=
|
32
|
+
@origin ||= "git@0xacab.org:sutty/containers/#{name}.git"
|
31
33
|
end
|
32
34
|
|
35
|
+
# @return [TTY::Command]
|
33
36
|
def cmd
|
34
37
|
@cmd ||= TTY::Command.new
|
35
38
|
end
|
@@ -1,9 +1,32 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative '../command'
|
4
|
+
require_relative '../../schema'
|
4
5
|
require 'tty-file'
|
5
6
|
require 'tty-logger'
|
6
|
-
require '
|
7
|
+
require 'active_support/core_ext/hash/keys'
|
8
|
+
require 'active_support/core_ext/object/blank'
|
9
|
+
|
10
|
+
class Hash
|
11
|
+
# Agrega una llave antes de otra
|
12
|
+
#
|
13
|
+
# @param [Any] La llave a buscar
|
14
|
+
# @param [Array] Array de dos elementos, el primero es la llave, el segundo el valor.
|
15
|
+
# @return [Hash]
|
16
|
+
# @see {https://stackoverflow.com/a/17251424}
|
17
|
+
def insert_before(key, kvpair)
|
18
|
+
arr = to_a
|
19
|
+
pos = arr.index(arr.assoc(key))
|
20
|
+
|
21
|
+
if pos
|
22
|
+
arr.insert(pos, kvpair)
|
23
|
+
else
|
24
|
+
arr << kvpair
|
25
|
+
end
|
26
|
+
|
27
|
+
replace Hash[arr]
|
28
|
+
end
|
29
|
+
end
|
7
30
|
|
8
31
|
module Sutty
|
9
32
|
module Cli
|
@@ -11,75 +34,107 @@ module Sutty
|
|
11
34
|
class Field < Sutty::Cli::Command
|
12
35
|
attr_reader :name, :options
|
13
36
|
|
37
|
+
INVERSE_OF = {
|
38
|
+
'has_many' => 'belongs_to',
|
39
|
+
'belongs_to' => 'has_many',
|
40
|
+
'has_and_belongs_to_many' => 'has_and_belongs_to_many'
|
41
|
+
}.freeze
|
42
|
+
|
43
|
+
# Campos que requieren especificar la relación inversa
|
14
44
|
INVERSE_REQUIRED = %w[has_many belongs_to has_and_belongs_to_many].freeze
|
45
|
+
# Opciones que necesitan un valor
|
15
46
|
VALUE_REQUIRED = %w[-F --filter].freeze
|
47
|
+
# Nombres de campos que no se pueden usar
|
48
|
+
RESERVED_FIELDS = %w[url date path slug ext].freeze
|
16
49
|
|
17
50
|
def initialize(name, options)
|
18
51
|
@name = name
|
19
|
-
@options = options
|
52
|
+
@options = options.to_h.transform_keys(&:to_sym)
|
53
|
+
@options[:before] ||= 'order'
|
54
|
+
|
55
|
+
unless @options[:layout]
|
56
|
+
binding.pry
|
57
|
+
raise ArgumentError, 'layout option is missing'
|
58
|
+
end
|
20
59
|
end
|
21
60
|
|
61
|
+
# TODO: Generar la relación inversa en el otro layout, a partir
|
62
|
+
# del filtro.
|
63
|
+
#
|
64
|
+
# @return [Boolean]
|
22
65
|
def execute(input: $stdin, output: $stdout)
|
23
|
-
|
24
|
-
|
25
|
-
|
66
|
+
@output = output
|
67
|
+
|
68
|
+
# Crea el layout si no existe
|
69
|
+
unless File.exist? layout_file
|
70
|
+
Sutty::Cli::Commands::Layout.new(options[:layout], {}).execute
|
26
71
|
end
|
27
72
|
|
28
|
-
|
29
|
-
|
73
|
+
if RESERVED_FIELDS.include? name
|
74
|
+
logger.info I18n.t('field.errors.reserved', name: name)
|
75
|
+
return false
|
76
|
+
end
|
77
|
+
|
78
|
+
if layout_contents.key?(name) && !options[:force]
|
79
|
+
logger.info I18n.t('field.errors.already_exists', name: name)
|
80
|
+
return false
|
81
|
+
end
|
82
|
+
|
83
|
+
if INVERSE_REQUIRED.include?(options[:type]) && options[:inverse].blank? && options[:filter].blank?
|
84
|
+
logger.info I18n.t('field.errors.inverse_required', name: name)
|
85
|
+
return false
|
30
86
|
end
|
31
|
-
end
|
32
87
|
|
33
|
-
|
34
|
-
|
35
|
-
|
88
|
+
layout_contents.insert_before options[:before],
|
89
|
+
Sutty::Schema.create(name: name, **options).to_a.flatten
|
90
|
+
|
91
|
+
# TODO: Mostrar el mismo mensaje de append_to_file
|
92
|
+
TTY::File.remove_file layout_file
|
93
|
+
TTY::File.create_file layout_file, YAML.dump(layout_contents.deep_stringify_keys)
|
94
|
+
|
95
|
+
# Agrega la relación inversa, creando el layout también.
|
96
|
+
if INVERSE_REQUIRED.include?(options[:type])
|
97
|
+
# XXX: No usar force aquí
|
98
|
+
Sutty::Cli::Commands::Field.new(options[:inverse],
|
99
|
+
layout: options[:value],
|
100
|
+
type: inverse_of(options[:type]),
|
101
|
+
filter: :layout,
|
102
|
+
value: options[:layout],
|
103
|
+
inverse: name).execute(output: output)
|
36
104
|
end
|
105
|
+
|
106
|
+
layout_contents.key? name
|
37
107
|
end
|
38
108
|
|
109
|
+
# Determina si la relación inversa es necesaria
|
39
110
|
def self.inverse_required?
|
40
111
|
ARGV.any? { |f| INVERSE_REQUIRED.include? f }
|
41
112
|
end
|
42
113
|
|
114
|
+
# Determina si el valor es necesario
|
43
115
|
def self.value_required?
|
44
116
|
ARGV.any? { |f| VALUE_REQUIRED.include? f }
|
45
117
|
end
|
46
118
|
|
47
119
|
private
|
48
120
|
|
49
|
-
def
|
50
|
-
|
51
|
-
end
|
52
|
-
|
53
|
-
def source_dir
|
54
|
-
self.class.source_dir
|
55
|
-
end
|
56
|
-
|
57
|
-
def context
|
58
|
-
return @context if @context
|
59
|
-
@context = OpenStruct.new(**options.transform_keys(&:to_sym))
|
60
|
-
@context[:name] = name
|
61
|
-
|
62
|
-
@context = @context.instance_eval('binding')
|
121
|
+
def layout_file
|
122
|
+
@data_file ||= File.join('_data', 'layouts', options[:layout] + '.yml')
|
63
123
|
end
|
64
124
|
|
65
|
-
def
|
66
|
-
@
|
125
|
+
def layout_contents
|
126
|
+
@layout_contents ||= YAML.load(File.read(layout_file))
|
67
127
|
end
|
68
128
|
|
69
|
-
def
|
70
|
-
@
|
71
|
-
end
|
72
|
-
|
73
|
-
def data_layout
|
74
|
-
@data_layout ||= File.join('_data', 'layouts', options[:layout] + '.yml')
|
75
|
-
end
|
76
|
-
|
77
|
-
def data_layout_contents
|
78
|
-
@data_layout_contents ||= File.binread data_layout
|
129
|
+
def logger
|
130
|
+
@logger ||= TTY::Logger.new(output: @output)
|
79
131
|
end
|
80
132
|
|
81
|
-
|
82
|
-
|
133
|
+
# Devuelve la relación inversa
|
134
|
+
#
|
135
|
+
# @return [String]
|
136
|
+
def inverse_of(type)
|
137
|
+
INVERSE_OF[type]
|
83
138
|
end
|
84
139
|
end
|
85
140
|
end
|
@@ -1,55 +1,68 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative '../command'
|
4
|
+
require_relative '../../schema'
|
4
5
|
require 'tty-file'
|
5
6
|
require 'yaml'
|
7
|
+
require 'active_support/core_ext/hash/keys'
|
6
8
|
|
7
9
|
module Sutty
|
8
10
|
module Cli
|
9
11
|
module Commands
|
12
|
+
# Crea un layout de Jekyll y su layout correspondiente en Sutty.
|
13
|
+
#
|
14
|
+
# _data/layouts/NAME.yml: Definición del layout para Sutty
|
15
|
+
# _layouts/NAME.yml: La plantilla Liquid para Jekyll (opcional)
|
10
16
|
class Layout < Sutty::Cli::Command
|
11
17
|
attr_reader :name
|
12
18
|
|
13
|
-
|
19
|
+
# @param [String] Nombre del layout
|
20
|
+
# @param [Hash] Opciones
|
21
|
+
def initialize(name, options = {})
|
14
22
|
@name = name
|
15
23
|
@options = options
|
16
24
|
end
|
17
25
|
|
26
|
+
# Crea los dos archivos sin pisarlos si ya existen
|
27
|
+
#
|
28
|
+
# @return [Boolean] Si el archivo fue creado o no
|
18
29
|
def execute(input: $stdin, output: $stdout)
|
19
|
-
TTY::File.create_file data_layout, YAML.dump(
|
20
|
-
|
30
|
+
TTY::File.create_file data_layout, YAML.dump(default_fields.deep_stringify_keys)
|
31
|
+
|
32
|
+
unless @options[:schema_only]
|
33
|
+
TTY::File.create_file html_layout, "---\nlayout: default\n---\n\n"
|
34
|
+
end
|
35
|
+
|
36
|
+
File.exist? data_layout
|
21
37
|
end
|
22
38
|
|
39
|
+
# Devuelve el listado de layouts disponibles
|
40
|
+
#
|
41
|
+
# @return [Array]
|
23
42
|
def self.layouts
|
24
|
-
|
43
|
+
@layouts ||= Dir.glob("#{File.join('_data', 'layouts')}/*.yml").map do |d|
|
25
44
|
File.basename d, '.yml'
|
26
|
-
end
|
45
|
+
end.sort
|
27
46
|
end
|
28
47
|
|
29
48
|
private
|
30
49
|
|
31
|
-
def
|
32
|
-
@
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
'en' => 'Title'
|
38
|
-
},
|
39
|
-
'help' => {
|
40
|
-
'es' => '',
|
41
|
-
'en' => ''
|
42
|
-
}
|
43
|
-
}
|
44
|
-
}
|
50
|
+
def default_fields
|
51
|
+
@default_fields ||= [
|
52
|
+
Sutty::Schema.string_field(name: :title, required: true),
|
53
|
+
Sutty::Schema.order_field(name: :order),
|
54
|
+
Sutty::Schema.boolean_field(name: :draft)
|
55
|
+
].inject(&:merge)
|
45
56
|
end
|
46
57
|
|
58
|
+
# @return [String]
|
47
59
|
def data_layout
|
48
|
-
@data_layout ||= File.join('_data', 'layouts', name
|
60
|
+
@data_layout ||= File.join('_data', 'layouts', "#{name}.yml")
|
49
61
|
end
|
50
62
|
|
63
|
+
# @return [String]
|
51
64
|
def html_layout
|
52
|
-
@html_layout ||= File.join('_layouts', name
|
65
|
+
@html_layout ||= File.join('_layouts', "#{name}.html")
|
53
66
|
end
|
54
67
|
end
|
55
68
|
end
|