hobo-inviqa 0.0.4 → 0.0.6
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.
- data/Gemfile.lock +27 -6
- data/README.md +1 -10
- data/bin/hobo +20 -0
- data/features/hobo/help.feature +5 -1
- data/hobo.gemspec +4 -0
- data/lib/hobo.rb +17 -14
- data/lib/hobo/asset_applicator.rb +16 -0
- data/lib/hobo/cli.rb +40 -20
- data/lib/hobo/config.rb +5 -0
- data/lib/hobo/config/file.rb +3 -1
- data/lib/hobo/errors.rb +8 -1
- data/lib/hobo/help_formatter.rb +8 -1
- data/lib/hobo/helper/file_locator.rb +8 -5
- data/lib/hobo/helper/shell.rb +12 -4
- data/lib/hobo/helper/vm_command.rb +67 -0
- data/lib/hobo/lib/host_check.rb +23 -0
- data/lib/hobo/lib/host_check/deps.rb +21 -0
- data/lib/hobo/lib/host_check/git.rb +52 -0
- data/lib/hobo/lib/host_check/ruby.rb +42 -0
- data/lib/hobo/lib/host_check/vagrant.rb +15 -0
- data/lib/hobo/lib/s3sync.rb +233 -0
- data/lib/hobo/lib/seed/project.rb +12 -0
- data/lib/hobo/lib/seed/seed.rb +19 -0
- data/lib/hobo/logging.rb +22 -0
- data/lib/hobo/metadata.rb +7 -1
- data/lib/hobo/null.rb +31 -0
- data/lib/hobo/patches/deepstruct.rb +23 -0
- data/lib/hobo/patches/rake.rb +14 -1
- data/lib/hobo/patches/slop.rb +11 -1
- data/lib/hobo/paths.rb +8 -3
- data/lib/hobo/tasks/assets.rb +96 -0
- data/lib/hobo/tasks/console.rb +18 -0
- data/lib/hobo/tasks/debug.rb +2 -2
- data/lib/hobo/tasks/deps.rb +1 -1
- data/lib/hobo/tasks/host.rb +17 -0
- data/lib/hobo/tasks/vm.rb +2 -2
- data/lib/hobo/ui.rb +21 -16
- data/lib/hobo/version.rb +1 -1
- data/spec/hobo/asset_applicator_spec.rb +31 -0
- data/spec/hobo/cli_spec.rb +115 -97
- data/spec/hobo/config/file_spec.rb +13 -3
- data/spec/hobo/help_formatter_spec.rb +50 -18
- data/spec/hobo/helpers/file_locator_spec.rb +5 -1
- data/spec/hobo/helpers/shell_spec.rb +15 -1
- data/spec/hobo/helpers/vm_command_spec.rb +85 -0
- data/spec/hobo/lib/s3sync_spec.rb +13 -0
- data/spec/hobo/lib/seed/project_spec.rb +7 -8
- data/spec/hobo/lib/seed/seed_spec.rb +3 -4
- data/spec/hobo/logging_spec.rb +28 -0
- data/spec/hobo/metadata_spec.rb +10 -0
- data/spec/hobo/null_spec.rb +31 -0
- data/spec/hobo/paths_spec.rb +6 -6
- data/spec/hobo/ui_spec.rb +4 -0
- metadata +93 -6
- data/features/vm.feature +0 -0
@@ -14,8 +14,11 @@ module Hobo
|
|
14
14
|
seed.update
|
15
15
|
seed.export config[:project_path]
|
16
16
|
config[:seed][:version] = seed.version
|
17
|
+
config[:hostname] = "#{config[:name]}.development.local"
|
18
|
+
config[:asset_bucket] = "inviqa-assets-#{config[:name]}"
|
17
19
|
|
18
20
|
@opts[:replacer].replace(config[:project_path], config)
|
21
|
+
load_seed_init(config)
|
19
22
|
|
20
23
|
project_path = config[:project_path]
|
21
24
|
config.delete :project_path
|
@@ -26,6 +29,15 @@ module Hobo
|
|
26
29
|
|
27
30
|
private
|
28
31
|
|
32
|
+
def load_seed_init config
|
33
|
+
Hobo.project_config = DeepStruct.wrap(config)
|
34
|
+
seed_init_file = File.join(config[:project_path], 'seedinit.rb')
|
35
|
+
if File.exists?(seed_init_file)
|
36
|
+
require seed_init_file
|
37
|
+
File.unlink(seed_init_file)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
29
41
|
def initialize_git path, git_url
|
30
42
|
Dir.chdir path do
|
31
43
|
Hobo::Helper.shell 'git', 'init'
|
data/lib/hobo/lib/seed/seed.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
1
3
|
module Hobo
|
2
4
|
module Lib
|
3
5
|
module Seed
|
4
6
|
class Seed
|
7
|
+
include Hobo::Logging
|
8
|
+
|
5
9
|
def initialize(seed_path, url)
|
6
10
|
@seed_path = seed_path
|
7
11
|
@url = url
|
@@ -10,18 +14,33 @@ module Hobo
|
|
10
14
|
def export path
|
11
15
|
path = File.expand_path(path)
|
12
16
|
FileUtils.mkdir_p path
|
17
|
+
|
18
|
+
logger.info "Exporting seed to #{path}"
|
19
|
+
|
20
|
+
tmp_path = Dir.mktmpdir("hobo-seed-export")
|
21
|
+
|
13
22
|
Dir.chdir @seed_path do
|
23
|
+
Hobo::Helper.shell "git clone . #{tmp_path.shellescape}"
|
24
|
+
end
|
25
|
+
|
26
|
+
Dir.chdir tmp_path do
|
27
|
+
Hobo::Helper.shell "git submodule update --init"
|
14
28
|
Hobo::Helper.shell "git archive master | tar -x -C #{path.shellescape}"
|
29
|
+
Hobo::Helper.shell "git submodule foreach 'cd #{tmp_path.shellescape}/$path && git archive HEAD | tar -x -C #{path.shellescape}/$path'"
|
15
30
|
end
|
31
|
+
|
32
|
+
FileUtils.rm_f tmp_path
|
16
33
|
end
|
17
34
|
|
18
35
|
def update
|
19
36
|
FileUtils.mkdir_p @seed_path
|
20
37
|
if File.exists? File.join(@seed_path, 'HEAD')
|
21
38
|
Dir.chdir @seed_path do
|
39
|
+
logger.info "Updating seed in #{@seed_path}"
|
22
40
|
Hobo::Helper.shell 'git', 'fetch', '--all'
|
23
41
|
end
|
24
42
|
else
|
43
|
+
logger.info "Cloning seed from #{@url} to #{@seed_path}"
|
25
44
|
Hobo::Helper.shell 'git', 'clone', @url, @seed_path, '--mirror'
|
26
45
|
end
|
27
46
|
end
|
data/lib/hobo/logging.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Hobo
|
4
|
+
class << self
|
5
|
+
attr_accessor :logger
|
6
|
+
end
|
7
|
+
|
8
|
+
module Logging
|
9
|
+
def logger
|
10
|
+
Hobo::Logging.logger
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.logger
|
14
|
+
unless Hobo.logger
|
15
|
+
Hobo.logger = Logger.new(STDOUT)
|
16
|
+
Hobo.logger.level = Logger::WARN
|
17
|
+
end
|
18
|
+
|
19
|
+
return Hobo.logger
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/hobo/metadata.rb
CHANGED
@@ -21,7 +21,13 @@ module Hobo
|
|
21
21
|
data = store[type] if data.nil?
|
22
22
|
metadata[task] ||= {}
|
23
23
|
metadata[task][type] = data
|
24
|
-
|
24
|
+
end
|
25
|
+
|
26
|
+
def reset_store
|
27
|
+
@store = {}
|
28
|
+
@defaults.each do |k, v|
|
29
|
+
@store[k] = v.nil? ? nil : v.dup
|
30
|
+
end
|
25
31
|
end
|
26
32
|
end
|
27
33
|
end
|
data/lib/hobo/null.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
module Hobo
|
2
|
+
class Null
|
3
|
+
def method_missing(method, *args, &block)
|
4
|
+
self
|
5
|
+
end
|
6
|
+
|
7
|
+
def nil?
|
8
|
+
true
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_a
|
12
|
+
[]
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
""
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_f
|
20
|
+
0.0
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_i
|
24
|
+
0
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def maybe val
|
30
|
+
val.nil? ? nil : val
|
31
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'deepstruct'
|
2
|
+
|
3
|
+
# Copied from the DeepStruct gem
|
4
|
+
# Modified to return Null on unknown key
|
5
|
+
module DeepStruct
|
6
|
+
class HashWrapper < DeepWrapper
|
7
|
+
def method_missing(method, *args, &block)
|
8
|
+
return @value.send(method, *args, &block) if @value.respond_to?(method)
|
9
|
+
method = method.to_s
|
10
|
+
if method.chomp!('?')
|
11
|
+
key = method.to_sym
|
12
|
+
self.has_key?(key) && !!self[key]
|
13
|
+
elsif method.chomp!('=')
|
14
|
+
raise ArgumentError, "wrong number of arguments (#{arg_count} for 1)", caller(1) if args.length != 1
|
15
|
+
self[method] = args[0]
|
16
|
+
elsif args.length == 0 && self.has_key?(method)
|
17
|
+
self[method]
|
18
|
+
else
|
19
|
+
Hobo::Null.new
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/hobo/patches/rake.rb
CHANGED
@@ -1,11 +1,18 @@
|
|
1
|
+
require 'rake'
|
2
|
+
|
1
3
|
module Rake
|
2
4
|
class Task
|
3
5
|
attr_accessor :opts
|
6
|
+
def opts
|
7
|
+
@opts = @opts || {}
|
8
|
+
end
|
4
9
|
end
|
5
10
|
|
6
11
|
module DSL
|
7
12
|
def replace *args, &block
|
8
|
-
|
13
|
+
old = (args[0].is_a? Hash) ? args[0].keys[0] : args[0]
|
14
|
+
Hobo::Logging.logger.debug("rake.dsl: Replacing #{old} with block")
|
15
|
+
Rake::Task[old].clear
|
9
16
|
task(*args, &block)
|
10
17
|
end
|
11
18
|
|
@@ -25,6 +32,10 @@ module Rake
|
|
25
32
|
Hobo::Metadata.add scoped_name, meta
|
26
33
|
end
|
27
34
|
|
35
|
+
Hobo::Metadata.reset_store
|
36
|
+
|
37
|
+
Hobo::Logging.logger.debug("Added metadata to #{scoped_name} -- #{Hobo::Metadata.metadata[scoped_name]}")
|
38
|
+
|
28
39
|
task = Rake::Task.define_task(*args, &block)
|
29
40
|
end
|
30
41
|
|
@@ -47,6 +58,8 @@ module Rake
|
|
47
58
|
Hobo::Metadata.add scoped_name, meta
|
48
59
|
end
|
49
60
|
|
61
|
+
Hobo::Metadata.reset_store
|
62
|
+
|
50
63
|
_old_namespace(name, &block)
|
51
64
|
end
|
52
65
|
end
|
data/lib/hobo/patches/slop.rb
CHANGED
@@ -1,5 +1,15 @@
|
|
1
|
+
require 'slop'
|
2
|
+
|
1
3
|
class Slop
|
2
|
-
attr_accessor :long_desc, :arg_list, :hidden
|
4
|
+
attr_accessor :long_desc, :arg_list, :hidden, :desc
|
5
|
+
|
6
|
+
# Slop has a description method but it uses @config which is inherited
|
7
|
+
# This is not desired behaviour
|
8
|
+
def description desc = nil
|
9
|
+
@desc = desc if desc
|
10
|
+
@desc
|
11
|
+
end
|
12
|
+
|
3
13
|
def long_description desc = nil
|
4
14
|
@long_desc = desc if desc
|
5
15
|
@long_desc
|
data/lib/hobo/paths.rb
CHANGED
@@ -12,11 +12,16 @@ module Hobo
|
|
12
12
|
|
13
13
|
def project_path
|
14
14
|
return @project_path unless @project_path.nil?
|
15
|
+
return @project_path = Dir.pwd if File.exists? "Hobofile"
|
16
|
+
|
15
17
|
dir = Dir.pwd.split('/').reverse
|
16
18
|
min_length = Gem.win_platform? ? 1 : 0
|
19
|
+
Hobo::Logging.logger.debug("paths.project: Searching backwards from #{Dir.pwd}")
|
17
20
|
|
18
21
|
while dir.length > min_length
|
19
22
|
test_dir = dir.reverse.join('/')
|
23
|
+
Hobo::Logging.logger.debug("paths.project: Testing #{test_dir}")
|
24
|
+
|
20
25
|
match = [
|
21
26
|
File.exists?(File.join(test_dir, 'Hobofile')),
|
22
27
|
File.exists?(File.join(test_dir, 'tools', 'hobo')),
|
@@ -25,7 +30,7 @@ module Hobo
|
|
25
30
|
|
26
31
|
return @project_path = test_dir if match.length > 0
|
27
32
|
|
28
|
-
dir.
|
33
|
+
dir.shift
|
29
34
|
end
|
30
35
|
return @project_path = nil
|
31
36
|
end
|
@@ -42,11 +47,11 @@ module Hobo
|
|
42
47
|
|
43
48
|
def project_config_file
|
44
49
|
return nil if !project_path
|
45
|
-
File.join(project_path, 'tools', 'hobo', '
|
50
|
+
File.join(project_path, 'tools', 'hobo', 'config.yaml')
|
46
51
|
end
|
47
52
|
|
48
53
|
def user_config_file
|
49
|
-
File.join(config_path, 'config.
|
54
|
+
File.join(config_path, 'config.yaml')
|
50
55
|
end
|
51
56
|
|
52
57
|
def user_hobofile_path
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'hobo/lib/s3sync'
|
2
|
+
|
3
|
+
desc "Project asset commands"
|
4
|
+
project_only
|
5
|
+
namespace :assets do
|
6
|
+
|
7
|
+
desc "Download project assets"
|
8
|
+
option "-e=", "--env=", "Environment"
|
9
|
+
task :download do |task, args|
|
10
|
+
Hobo.ui.success "Synchonizing assets (download)"
|
11
|
+
|
12
|
+
unless Hobo.project_config.asset_bucket.nil?
|
13
|
+
env = task.opts[:env] || args[:env] || 'development'
|
14
|
+
s3_uri = "s3://#{Hobo.project_config.asset_bucket}/#{env}/"
|
15
|
+
|
16
|
+
sync = Hobo::Lib::S3Sync.new(
|
17
|
+
maybe(Hobo.user_config.aws.access_key_id),
|
18
|
+
maybe(Hobo.user_config.aws.secret_access_key)
|
19
|
+
)
|
20
|
+
|
21
|
+
changes = sync.sync(s3_uri, "tools/assets/#{env}")
|
22
|
+
Hobo.ui.warning " No changes required" if (changes[:add] + changes[:remove]).length == 0
|
23
|
+
else
|
24
|
+
Hobo.ui.warning " No asset bucket configured. Skipping..."
|
25
|
+
end
|
26
|
+
Hobo.ui.separator
|
27
|
+
end
|
28
|
+
|
29
|
+
desc "Upload project assets"
|
30
|
+
option "-e=", "--env=", "Environment"
|
31
|
+
task :upload do |task, args|
|
32
|
+
|
33
|
+
Hobo.ui.success "Synchronzing assets (upload)"
|
34
|
+
|
35
|
+
unless Hobo.project_config.asset_bucket.nil?
|
36
|
+
env = task.opts[:env] || args[:env] || 'development'
|
37
|
+
s3_uri = "s3://#{Hobo.project_config.asset_bucket}/#{env}/"
|
38
|
+
|
39
|
+
sync = Hobo::Lib::S3Sync.new(
|
40
|
+
maybe(Hobo.user_config.aws.access_key_id),
|
41
|
+
maybe(Hobo.user_config.aws.secret_access_key)
|
42
|
+
)
|
43
|
+
|
44
|
+
changes = sync.sync("tools/assets/#{env}", s3_uri)
|
45
|
+
Hobo.ui.warning " No changes required" if (changes[:add] + changes[:remove]).length == 0
|
46
|
+
else
|
47
|
+
Hobo.ui.warning " No asset bucket configured. Skipping..."
|
48
|
+
end
|
49
|
+
|
50
|
+
Hobo.ui.separator
|
51
|
+
end
|
52
|
+
|
53
|
+
desc "Apply project assets"
|
54
|
+
option "-e=", "--env=", "Environment"
|
55
|
+
task :apply do |task, args|
|
56
|
+
env = task.opts[:env] || args[:env] || 'development'
|
57
|
+
path = "tools/assets/#{env}"
|
58
|
+
|
59
|
+
next unless File.exists? path
|
60
|
+
|
61
|
+
Dir.new(path).each do |file|
|
62
|
+
file = File.join(path, file)
|
63
|
+
next unless File.file? file
|
64
|
+
Hobo.asset_applicators.each do |matcher, proc|
|
65
|
+
proc.call(file) if matcher.match(file)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Built in applicators
|
73
|
+
Hobo.asset_applicators.register /.*\.files\.(tgz|tar\.gz|tar\.bz2)/ do |file|
|
74
|
+
Hobo.ui.title "Applying file dump (#{file})"
|
75
|
+
Dir.chdir Hobo.project_path do
|
76
|
+
shell "tar -xvf #{file.shellescape}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
Hobo.asset_applicators.register /.*\.sql\.gz/ do |file|
|
81
|
+
matches = file.match(/^([^\.]+).*\.sql\.gz/)
|
82
|
+
db = File.basename(matches[1])
|
83
|
+
|
84
|
+
begin
|
85
|
+
shell(vm_mysql << "USE #{db}")
|
86
|
+
Hobo.ui.warning "Already applied (#{file})"
|
87
|
+
next
|
88
|
+
rescue Hobo::ExternalCommandError => e
|
89
|
+
raise e if e.exit_code != 1
|
90
|
+
end
|
91
|
+
|
92
|
+
Hobo.ui.title "Applying mysqldump (#{file})"
|
93
|
+
|
94
|
+
shell(vm_mysql << "CREATE DATABASE #{db}")
|
95
|
+
shell(vm_mysql(:auto_echo => false, :db => db) << "zcat #{file.shellescape}")
|
96
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
desc "VM console shortcut commands"
|
2
|
+
project_only
|
3
|
+
namespace :console do
|
4
|
+
desc "Open an SSH connection"
|
5
|
+
task :ssh do
|
6
|
+
exec vm_command
|
7
|
+
end
|
8
|
+
|
9
|
+
desc "Open a MySQL cli connection"
|
10
|
+
task :mysql do
|
11
|
+
exec vm_mysql
|
12
|
+
end
|
13
|
+
|
14
|
+
desc "Open a Redis cli connection"
|
15
|
+
task :redis do
|
16
|
+
exec vm_command "redis-cli"
|
17
|
+
end
|
18
|
+
end
|
data/lib/hobo/tasks/debug.rb
CHANGED
@@ -13,8 +13,8 @@ namespace 'hobo-debug' do
|
|
13
13
|
:'composer.json' => "composer.json"
|
14
14
|
}.each do |k,v|
|
15
15
|
path = nil
|
16
|
-
locate v do |
|
17
|
-
path =
|
16
|
+
locate v do |file, full_file|
|
17
|
+
path = full_file
|
18
18
|
end
|
19
19
|
Hobo.ui.info "<%=color('#{k.to_s}:', :green) %> #{path.nil? ? "none" : path}"
|
20
20
|
end
|
data/lib/hobo/tasks/deps.rb
CHANGED
@@ -0,0 +1,17 @@
|
|
1
|
+
namespace :host do
|
2
|
+
task :config do
|
3
|
+
config = Hobo.user_config
|
4
|
+
|
5
|
+
config.full_name = Hobo.ui.ask "Full name", :default => config.full_name
|
6
|
+
config.email = Hobo.ui.ask "Email", :default => config.email
|
7
|
+
config.aws.access_key_id = Hobo.ui.ask "AWS access key ID", :default => config.aws.access_key_id
|
8
|
+
config.aws.secret_access_key = Hobo.ui.ask "AWS secret access key", :default => config.aws.secret_access_key
|
9
|
+
|
10
|
+
Hobo::Config::File.save(Hobo.user_config_file, config)
|
11
|
+
File.chmod(0600, Hobo.user_config_file)
|
12
|
+
end
|
13
|
+
|
14
|
+
task :check do
|
15
|
+
Hobo::HostCheck.check false
|
16
|
+
end
|
17
|
+
end
|
data/lib/hobo/tasks/vm.rb
CHANGED
@@ -8,7 +8,7 @@ namespace :vm do
|
|
8
8
|
end
|
9
9
|
|
10
10
|
desc "Start & provision VM"
|
11
|
-
task :up => [ 'deps:
|
11
|
+
task :up => [ 'deps:chef', 'deps:composer', 'assets:download', 'vm:start', 'vm:provision', 'assets:apply' ]
|
12
12
|
|
13
13
|
desc "Stop VM"
|
14
14
|
task :stop => [ "deps:gems" ] do
|
@@ -32,7 +32,7 @@ namespace :vm do
|
|
32
32
|
end
|
33
33
|
|
34
34
|
desc "Start VM without provision"
|
35
|
-
task :start => [ "deps:gems" ] do
|
35
|
+
task :start => [ "deps:gems", "deps:vagrant_plugins" ] do
|
36
36
|
vagrantfile do
|
37
37
|
Hobo.ui.title "Starting vagrant VM"
|
38
38
|
bundle_shell "vagrant", "up", "--no-provision", "--color", realtime: true, indent: 2
|
data/lib/hobo/ui.rb
CHANGED
@@ -8,23 +8,22 @@ module Hobo
|
|
8
8
|
class Ui
|
9
9
|
attr_accessor :interactive
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
end
|
25
|
-
|
26
|
-
HighLine.color_scheme = colors
|
11
|
+
COLORS = {
|
12
|
+
:debug => [ ],
|
13
|
+
:info => [ ],
|
14
|
+
:warning => [:yellow],
|
15
|
+
:error => [:red],
|
16
|
+
:success => [:green],
|
17
|
+
:opt => [:green],
|
18
|
+
:command => [:green],
|
19
|
+
:special => [:blue],
|
20
|
+
:title => [:green],
|
21
|
+
:help_title => [:yellow],
|
22
|
+
:description => [:bold]
|
23
|
+
}
|
27
24
|
|
25
|
+
def initialize out = $stdout, error = $stderr
|
26
|
+
HighLine.color_scheme = HighLine::ColorScheme.new COLORS
|
28
27
|
@out = ::HighLine.new $stdin, out
|
29
28
|
@error = ::HighLine.new $stdin, error
|
30
29
|
end
|
@@ -59,6 +58,12 @@ module Hobo
|
|
59
58
|
end
|
60
59
|
end
|
61
60
|
|
61
|
+
def section title
|
62
|
+
Hobo.ui.title title
|
63
|
+
yield
|
64
|
+
Hobo.ui.separator
|
65
|
+
end
|
66
|
+
|
62
67
|
def separator
|
63
68
|
info ""
|
64
69
|
end
|