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.
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 +3 -1
  12. data/lib/sutty/drop_stub.rb +10 -0
  13. data/lib/sutty/schema.rb +250 -0
  14. metadata +80 -30
  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