sutty-cli 0.2.1 → 0.3.0rc

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/lib/sutty/cli/cli.rb +42 -35
  3. data/lib/sutty/cli/commands/container.rb +4 -1
  4. data/lib/sutty/cli/commands/field.rb +94 -38
  5. data/lib/sutty/cli/commands/layout.rb +34 -21
  6. data/lib/sutty/cli/commands/theme.rb +9 -5
  7. data/lib/sutty/cli/i18n.rb +12 -0
  8. data/lib/sutty/cli/version.rb +1 -1
  9. data/lib/sutty/drop_stub.rb +10 -0
  10. data/lib/sutty/schema.rb +250 -0
  11. metadata +49 -29
  12. data/lib/sutty/cli/templates/field/array.yml.erb +0 -14
  13. data/lib/sutty/cli/templates/field/belongs_to.yml.erb +0 -16
  14. data/lib/sutty/cli/templates/field/boolean.yml.erb +0 -11
  15. data/lib/sutty/cli/templates/field/color.yml.erb +0 -11
  16. data/lib/sutty/cli/templates/field/content.yml.erb +0 -14
  17. data/lib/sutty/cli/templates/field/date.yml.erb +0 -11
  18. data/lib/sutty/cli/templates/field/email.yml.erb +0 -14
  19. data/lib/sutty/cli/templates/field/encrypted_text.yml.erb +0 -11
  20. data/lib/sutty/cli/templates/field/event.yml.erb +0 -40
  21. data/lib/sutty/cli/templates/field/file.yml.erb +0 -19
  22. data/lib/sutty/cli/templates/field/geo.yml.erb +0 -17
  23. data/lib/sutty/cli/templates/field/has_and_belongs_to_many.yml.erb +0 -16
  24. data/lib/sutty/cli/templates/field/has_many.yml.erb +0 -16
  25. data/lib/sutty/cli/templates/field/image.yml.erb +0 -19
  26. data/lib/sutty/cli/templates/field/locales.yml.erb +0 -11
  27. data/lib/sutty/cli/templates/field/markdown.yml.erb +0 -14
  28. data/lib/sutty/cli/templates/field/markdown_content.yml.erb +0 -14
  29. data/lib/sutty/cli/templates/field/number.yml.erb +0 -11
  30. data/lib/sutty/cli/templates/field/order.yml.erb +0 -11
  31. data/lib/sutty/cli/templates/field/predefined_array.yml.erb +0 -18
  32. data/lib/sutty/cli/templates/field/related_posts.yml.erb +0 -15
  33. data/lib/sutty/cli/templates/field/string.yml.erb +0 -14
  34. data/lib/sutty/cli/templates/field/tel.yml.erb +0 -14
  35. data/lib/sutty/cli/templates/field/text.yml.erb +0 -14
  36. data/lib/sutty/cli/templates/field/url.yml.erb +0 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bb3fe159753323120692f2a9362297ad2d426c86c9157c201701c4ea1550f751
4
- data.tar.gz: 15cc8ce51e749cecf5b5d3660c94849315a9701eae3e6aab52e2a1a066f37ae0
3
+ metadata.gz: a3f2c98ed854fd05ff7e75d5353b421ec3ec3195074be762e722eea2efdca1a7
4
+ data.tar.gz: e7b901948cddc1a7ced86caacb7d08ea9002f0cedaef9f3a0c10e76fab3fe981
5
5
  SHA512:
6
- metadata.gz: dc9d1c193c52b4acca35a8ced028728d87f6146fbecc31be425922e057d0f4b1e40665409cab9a724cfe3c9fcb239095458ab2f274ee8ca3ece7f621d0ebf772
7
- data.tar.gz: 6de2d1ab46cd4850bdd227a6c8820b603e5154438e6b117929211e37180d3e05fda67c2204be03331991533623e6fd3be38d2f334aab8bdfc4df97c2abd5a725
6
+ metadata.gz: 42ae5a382fefcdb2f3f19dbcd4b6865444913bdcc32d2511417739eb2ca0bce88a9da3d56471d00e33d032a4023b2244a7845d33a779a8e0ab3359aa09597021
7
+ data.tar.gz: 84c5bbf870b299878980e86fede20c43ed5e3351c4e6c63e997485771ed872e50c77375a8f95461d7c78970cb8663c4e0a980217de0329dfe8ccd6daed617825
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,46 +17,45 @@ module Sutty
15
17
  # Error raised by this runner
16
18
  Error = Class.new(StandardError)
17
19
 
18
- desc 'version', 'sutty-cli 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', 'Adds a 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: 'Layout',
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: 'Title'
40
+ desc: I18n.t('post.flags.title')
40
41
 
41
42
  method_option :date,
42
43
  aliases: '-d',
43
44
  type: :string,
44
- desc: 'Date',
45
+ desc: I18n.t('post.flags.date'),
45
46
  default: Date.today.to_s
46
47
 
47
48
  # TODO: Bring locales from Jekyll configuration
48
49
  method_option :locale,
49
50
  aliases: '-L',
50
51
  type: :string,
51
- desc: 'Locale collection',
52
+ desc: I18n.t('post.flags.locale'),
52
53
  default: 'posts'
53
54
 
54
55
  method_option :content,
55
56
  aliases: '-c',
56
57
  type: :string,
57
- desc: 'Text length',
58
+ desc: I18n.t('post.flags.content'),
58
59
  default: 'short',
59
60
  enum: %w[short long]
60
61
 
@@ -67,9 +68,8 @@ module Sutty
67
68
  end
68
69
  end
69
70
 
70
- desc 'container NAME', 'Adds a container'
71
- method_option :help, aliases: '-h', type: :boolean,
72
- 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')
73
73
  def container(name)
74
74
  if options[:help]
75
75
  invoke :help, ['container']
@@ -79,59 +79,63 @@ module Sutty
79
79
  end
80
80
  end
81
81
 
82
- desc 'field NAME', 'Adds a field with a type to a layout'
83
- long_desc <<~EOD
84
- A field is a data type with a name, from which Sutty can build a
85
- form on the panel and use it to validate posts.
86
-
87
- After you add a field, edit the layout file and add labels and
88
- help description in different languages.
89
- EOD
82
+ desc I18n.t('field.desc.example'), I18n.t('field.desc.text')
83
+ long_desc I18n.t('field.desc.long')
90
84
 
91
85
  method_option :help,
92
86
  aliases: '-h',
93
87
  type: :boolean,
94
- desc: 'Display usage information'
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
95
99
 
96
100
  method_option :layout,
97
101
  aliases: '-l',
98
102
  type: :string,
99
- desc: 'Layout to add',
103
+ desc: I18n.t('field.flags.layout'),
100
104
  required: true,
101
105
  enum: Sutty::Cli::Commands::Layout.layouts
102
106
 
103
107
  method_option :type,
104
108
  aliases: '-t',
105
109
  type: :string,
106
- desc: 'Field type',
110
+ desc: I18n.t('field.flags.type'),
107
111
  required: true,
108
- enum: Sutty::Cli::Commands::Field.fields
112
+ enum: Sutty::Schema.field_types.map(&:to_s)
109
113
 
110
114
  method_option :required,
111
115
  aliases: '-r',
112
116
  type: :boolean,
113
- desc: 'Field is required'
117
+ desc: I18n.t('field.flags.required')
114
118
 
115
119
  method_option :private,
116
120
  aliases: '-p',
117
121
  type: :boolean,
118
- desc: 'Field is private (encrypted)'
122
+ desc: I18n.t('field.flags.private')
119
123
 
120
124
  method_option :inverse,
121
125
  aliases: '-i',
122
126
  type: :string,
123
- desc: 'Inverse relation',
127
+ desc: I18n.t('field.flags.inverse'),
124
128
  required: Sutty::Cli::Commands::Field.inverse_required?
125
129
 
126
130
  method_option :filter,
127
131
  aliases: '-F',
128
132
  type: :string,
129
- desc: 'Filter relations by this field'
133
+ desc: I18n.t('field.flags.filter')
130
134
 
131
135
  method_option :value,
132
136
  aliases: '-v',
133
137
  type: :string,
134
- desc: 'Filter relations by this field value',
138
+ desc: I18n.t('field.flags.value'),
135
139
  required: Sutty::Cli::Commands::Field.value_required?
136
140
 
137
141
  def field(name)
@@ -142,9 +146,13 @@ module Sutty
142
146
  end
143
147
  end
144
148
 
145
- desc 'layout NAME', 'Start a new layout'
146
- method_option :help, aliases: '-h', type: :boolean,
147
- desc: 'Display usage information'
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')
148
156
  def layout(name)
149
157
  if options[:help]
150
158
  invoke :help, ['layout']
@@ -153,9 +161,8 @@ module Sutty
153
161
  end
154
162
  end
155
163
 
156
- desc 'theme NAME', 'Start a new theme'
157
- method_option :help, aliases: '-h', type: :boolean,
158
- 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')
159
166
  def theme(name)
160
167
  if options[:help]
161
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 ||= 'git@0xacab.org:sutty/containers/' + name + '.git'
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,8 +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'
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
6
30
 
7
31
  module Sutty
8
32
  module Cli
@@ -10,75 +34,107 @@ module Sutty
10
34
  class Field < Sutty::Cli::Command
11
35
  attr_reader :name, :options
12
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
13
44
  INVERSE_REQUIRED = %w[has_many belongs_to has_and_belongs_to_many].freeze
45
+ # Opciones que necesitan un valor
14
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
15
49
 
16
50
  def initialize(name, options)
17
51
  @name = name
18
- @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
19
59
  end
20
60
 
61
+ # TODO: Generar la relación inversa en el otro layout, a partir
62
+ # del filtro.
63
+ #
64
+ # @return [Boolean]
21
65
  def execute(input: $stdin, output: $stdout)
22
- unless data_layout_contents.scan(/\n#{name}:\n/).empty?
23
- logger.info "The #{name} field is already present, please edit #{data_layout}"
24
- return true
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
25
71
  end
26
72
 
27
- TTY::File.safe_append_to_file(data_layout) do
28
- ERB.new(template_contents, trim_mode: '<>', eoutvar: '@output_buffer').result(context)
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
29
86
  end
30
- end
31
87
 
32
- def self.fields
33
- @@fields ||= Dir.glob(source_dir.to_s + '/*.yml.erb').map do |f|
34
- File.basename f, '.yml.erb'
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)
35
104
  end
105
+
106
+ layout_contents.key? name
36
107
  end
37
108
 
109
+ # Determina si la relación inversa es necesaria
38
110
  def self.inverse_required?
39
111
  ARGV.any? { |f| INVERSE_REQUIRED.include? f }
40
112
  end
41
113
 
114
+ # Determina si el valor es necesario
42
115
  def self.value_required?
43
116
  ARGV.any? { |f| VALUE_REQUIRED.include? f }
44
117
  end
45
118
 
46
119
  private
47
120
 
48
- def self.source_dir
49
- @@source_dir ||= Pathname(__dir__).join('..', 'templates', 'field')
50
- end
51
-
52
- def source_dir
53
- self.class.source_dir
54
- end
55
-
56
- def context
57
- return @context if @context
58
- @context = OpenStruct.new(**options.transform_keys(&:to_sym))
59
- @context[:name] = name
60
-
61
- @context = @context.instance_eval('binding')
121
+ def layout_file
122
+ @data_file ||= File.join('_data', 'layouts', options[:layout] + '.yml')
62
123
  end
63
124
 
64
- def template
65
- @template ||= source_dir.join(options[:type] + '.yml.erb')
125
+ def layout_contents
126
+ @layout_contents ||= YAML.load(File.read(layout_file))
66
127
  end
67
128
 
68
- def template_contents
69
- @template_contents ||= File.binread template
70
- end
71
-
72
- def data_layout
73
- @data_layout ||= File.join('_data', 'layouts', options[:layout] + '.yml')
74
- end
75
-
76
- def data_layout_contents
77
- @data_layout_contents ||= File.binread data_layout
129
+ def logger
130
+ @logger ||= TTY::Logger.new(output: @output)
78
131
  end
79
132
 
80
- def logger
81
- @logger ||= TTY::Logger.new
133
+ # Devuelve la relación inversa
134
+ #
135
+ # @return [String]
136
+ def inverse_of(type)
137
+ INVERSE_OF[type]
82
138
  end
83
139
  end
84
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
- def initialize(name, options)
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(data_default_fields)
20
- TTY::File.create_file html_layout, "---\nlayout: default\n---\n\n"
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
- @@layouts ||= Dir.glob(File.join('_data', 'layouts') + '/*.yml').map do |d|
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 data_default_fields
32
- @data_default_fields ||= {
33
- 'title' => {
34
- 'type' => 'string',
35
- 'label' => {
36
- 'es' => 'Título',
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 + '.yml')
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 + '.html')
65
+ @html_layout ||= File.join('_layouts', "#{name}.html")
53
66
  end
54
67
  end
55
68
  end