sutty-cli 0.2.1 → 0.3.0rc

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 (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