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
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', '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 NAME', '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
- 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: 'Date',
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: 'Locale collection',
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 NAME', 'Adds a 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 NAME', 'Adds a field with a type to a layout'
77
- long_desc <<~EOD
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: '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
89
99
 
90
100
  method_option :layout,
91
101
  aliases: '-l',
92
102
  type: :string,
93
- desc: 'Layout to add',
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: 'Field type',
110
+ desc: I18n.t('field.flags.type'),
101
111
  required: true,
102
- enum: Sutty::Cli::Commands::Field.fields
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: 'Field is required'
117
+ desc: I18n.t('field.flags.required')
108
118
 
109
119
  method_option :private,
110
120
  aliases: '-p',
111
121
  type: :boolean,
112
- desc: 'Field is private (encrypted)'
122
+ desc: I18n.t('field.flags.private')
113
123
 
114
124
  method_option :inverse,
115
125
  aliases: '-i',
116
126
  type: :string,
117
- desc: 'Inverse relation',
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: 'Filter relations by this field'
133
+ desc: I18n.t('field.flags.filter')
124
134
 
125
135
  method_option :value,
126
136
  aliases: '-v',
127
137
  type: :string,
128
- desc: 'Filter relations by this field value',
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 NAME', 'Start a new layout'
140
- method_option :help, aliases: '-h', type: :boolean,
141
- 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')
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 NAME', 'Start a new 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 ||= '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,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 'pry'
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
- unless data_layout_contents.scan(/\n#{name}:\n/).empty?
24
- logger.info "The #{name} field is already present, please edit #{data_layout}"
25
- 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
26
71
  end
27
72
 
28
- TTY::File.safe_append_to_file(data_layout) do
29
- 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
30
86
  end
31
- end
32
87
 
33
- def self.fields
34
- @@fields ||= Dir.glob(source_dir.to_s + '/*.yml.erb').map do |f|
35
- 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)
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 self.source_dir
50
- @@source_dir ||= Pathname(__dir__).join('..', 'templates', 'field')
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 template
66
- @template ||= source_dir.join(options[:type] + '.yml.erb')
125
+ def layout_contents
126
+ @layout_contents ||= YAML.load(File.read(layout_file))
67
127
  end
68
128
 
69
- def template_contents
70
- @template_contents ||= File.binread template
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
- def logger
82
- @logger ||= TTY::Logger.new
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
- 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