yuyi 0.0.3
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.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/.new +20 -0
- data/.rspec +2 -0
- data/.travis.yml +7 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +80 -0
- data/Guardfile +5 -0
- data/README.md +122 -0
- data/bin/install +42 -0
- data/bin/yuyi +6 -0
- data/lib/yuyi/cli.rb +355 -0
- data/lib/yuyi/core.rb +49 -0
- data/lib/yuyi/menu.rb +202 -0
- data/lib/yuyi/roll.rb +153 -0
- data/lib/yuyi/source.rb +107 -0
- data/lib/yuyi.rb +15 -0
- data/spec/fixtures/menu.yaml +6 -0
- data/spec/fixtures/menu2.yaml +6 -0
- data/spec/fixtures/roll_dir/nested/bar_roll.rb +2 -0
- data/spec/fixtures/roll_dir/nested/foo_roll.rb +2 -0
- data/spec/fixtures/roll_zip.zip +0 -0
- data/spec/lib/yuyi/cli_spec.rb +291 -0
- data/spec/lib/yuyi/core_spec.rb +43 -0
- data/spec/lib/yuyi/menu_spec.rb +207 -0
- data/spec/lib/yuyi/roll_spec.rb +215 -0
- data/spec/lib/yuyi/source_spec.rb +55 -0
- data/spec/lib/yuyi_spec.rb +4 -0
- data/spec/spec_helper.rb +54 -0
- metadata +152 -0
data/lib/yuyi/core.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'tsort'
|
2
|
+
|
3
|
+
class Array
|
4
|
+
def to_yaml_style
|
5
|
+
:inline
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class Hash
|
10
|
+
include TSort
|
11
|
+
alias tsort_each_node each_key
|
12
|
+
def tsort_each_child(node, &block)
|
13
|
+
fetch(node).each(&block)
|
14
|
+
end
|
15
|
+
|
16
|
+
# https://github.com/rails/rails/blob/c48a0cac626b4e32d7abfa9f4f1fae16568157d9/activesupport/lib/active_support/core_ext/hash/keys.rb
|
17
|
+
#
|
18
|
+
# Destructively convert all keys to symbols, as long as they respond
|
19
|
+
# to +to_sym+. This includes the keys from the root hash and from all
|
20
|
+
# nested hashes.
|
21
|
+
#
|
22
|
+
def deep_symbolize_keys!
|
23
|
+
deep_transform_keys!{ |key| key.to_sym rescue key }
|
24
|
+
end
|
25
|
+
|
26
|
+
def deep_stringify_keys!
|
27
|
+
deep_transform_keys!{ |key| key.to_s rescue key }
|
28
|
+
end
|
29
|
+
|
30
|
+
# Destructively convert all keys by using the block operation.
|
31
|
+
# This includes the keys from the root hash and from all
|
32
|
+
# nested hashes.
|
33
|
+
#
|
34
|
+
def deep_transform_keys! &block
|
35
|
+
keys.each do |key|
|
36
|
+
value = delete(key)
|
37
|
+
|
38
|
+
self[yield(key)] = case value
|
39
|
+
when Hash
|
40
|
+
value.deep_transform_keys!(&block)
|
41
|
+
when Array
|
42
|
+
value.each{ |e| e.deep_transform_keys!(&block) rescue value }
|
43
|
+
else
|
44
|
+
value
|
45
|
+
end
|
46
|
+
end
|
47
|
+
self
|
48
|
+
end
|
49
|
+
end
|
data/lib/yuyi/menu.rb
ADDED
@@ -0,0 +1,202 @@
|
|
1
|
+
class Yuyi::Menu
|
2
|
+
@rolls = {}
|
3
|
+
@pre_installs = []
|
4
|
+
@post_installs = []
|
5
|
+
|
6
|
+
# DSL Helper methods
|
7
|
+
#
|
8
|
+
def self.instance; @@instance; end
|
9
|
+
|
10
|
+
def self.object; @object; end
|
11
|
+
def object; self.class.object; end
|
12
|
+
|
13
|
+
def self.sources; @@instance.sources; end
|
14
|
+
def sources; @sources; end
|
15
|
+
|
16
|
+
def self.rolls; @rolls; end
|
17
|
+
def rolls; self.class.rolls; end
|
18
|
+
|
19
|
+
def self.path
|
20
|
+
@@instance.path
|
21
|
+
end
|
22
|
+
def path; @path; end
|
23
|
+
|
24
|
+
def self.on_the_menu? roll
|
25
|
+
@@instance.on_the_menu? roll
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.find_roll name, options = {}
|
29
|
+
@@instance.find_roll name, options
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.set_sources
|
33
|
+
@@instance.set_sources
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.upgrade? boolean = nil
|
37
|
+
@@instance.upgrade? boolean
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.options roll
|
41
|
+
@@instance.options roll
|
42
|
+
end
|
43
|
+
|
44
|
+
# Attempt to load a menu from a file path
|
45
|
+
# defaults to previously stored @path value
|
46
|
+
#
|
47
|
+
def self.load_from_file path = path
|
48
|
+
@object = begin
|
49
|
+
YAML.load(File.open(File.expand_path(path))).deep_symbolize_keys!
|
50
|
+
rescue
|
51
|
+
nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Add rolls to hash in format of {file_name: RollInstance}
|
56
|
+
# Called from yuyi/roll.rb#self.inherited
|
57
|
+
#
|
58
|
+
def self.add_roll file_name, klass
|
59
|
+
@rolls[file_name] = klass.new
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.add_pre_install block
|
63
|
+
@pre_installs << block
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.add_post_install block
|
67
|
+
@post_installs << block
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.pre_install
|
71
|
+
@pre_installs.each { |b| b.call }
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.post_install
|
75
|
+
@post_installs.each { |b| b.call }
|
76
|
+
end
|
77
|
+
|
78
|
+
# Get/Set upgrade flag
|
79
|
+
#
|
80
|
+
def upgrade? boolean = nil
|
81
|
+
@upgrade = boolean.nil? ? @upgrade : boolean
|
82
|
+
end
|
83
|
+
|
84
|
+
# Check if a roll is on the menu
|
85
|
+
#
|
86
|
+
def on_the_menu? roll
|
87
|
+
object[:rolls].keys.include? roll
|
88
|
+
end
|
89
|
+
|
90
|
+
# Download & create all sources on the menu
|
91
|
+
#
|
92
|
+
def set_sources
|
93
|
+
if object[:sources].empty?
|
94
|
+
Yuyi.say 'No rolls could be found because no sources were set in your menu.'
|
95
|
+
return
|
96
|
+
end
|
97
|
+
|
98
|
+
@sources = []
|
99
|
+
object[:sources].each do |source|
|
100
|
+
source.each do |name, path|
|
101
|
+
@sources << Yuyi::Source.new(name, path)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
@sources
|
106
|
+
end
|
107
|
+
|
108
|
+
def options roll
|
109
|
+
object[:rolls][roll.file_name] || {}
|
110
|
+
end
|
111
|
+
|
112
|
+
# Initialize all the rolls in order
|
113
|
+
#
|
114
|
+
def order_rolls
|
115
|
+
Yuyi::Menu.pre_install
|
116
|
+
|
117
|
+
sorted_rolls.each do |file_name|
|
118
|
+
rolls[file_name].order
|
119
|
+
end
|
120
|
+
|
121
|
+
Yuyi::Menu.post_install
|
122
|
+
end
|
123
|
+
|
124
|
+
# Find the best roll in the source to be added
|
125
|
+
#
|
126
|
+
def find_roll name, options = {}
|
127
|
+
options ||= {}
|
128
|
+
# return specific source roll if specified in the menu
|
129
|
+
#
|
130
|
+
if source = options[:source]
|
131
|
+
require_roll name, File.join(source, name.to_s)
|
132
|
+
return
|
133
|
+
|
134
|
+
# look through the sources for the first roll that matches.
|
135
|
+
# sources are listed in the menu in order of priority
|
136
|
+
else
|
137
|
+
sources.each do |source|
|
138
|
+
if path = source.available_rolls[name]
|
139
|
+
require_roll name, path
|
140
|
+
return
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# no roll was found
|
146
|
+
Yuyi.say "You ordered the '#{name}' roll off the menu, but we are fresh out...", :type => :fail
|
147
|
+
Yuyi.say 'Check your menu to make sure a source with your roll is listed.', :type => :warn
|
148
|
+
Yuyi.say
|
149
|
+
end
|
150
|
+
|
151
|
+
private
|
152
|
+
|
153
|
+
def initialize path
|
154
|
+
return unless Yuyi::Menu.load_from_file path
|
155
|
+
|
156
|
+
@path = path
|
157
|
+
@@instance = self
|
158
|
+
|
159
|
+
set_sources
|
160
|
+
set_rolls
|
161
|
+
end
|
162
|
+
|
163
|
+
# Create all rolls on the menu
|
164
|
+
#
|
165
|
+
def set_rolls
|
166
|
+
object[:rolls].each do |name, options|
|
167
|
+
find_roll name, options
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# Require a single roll
|
172
|
+
#
|
173
|
+
def require_roll name, path
|
174
|
+
# check if already on the roll for when requiring dependencies
|
175
|
+
return if roll_loaded? name
|
176
|
+
|
177
|
+
begin
|
178
|
+
require path
|
179
|
+
rescue LoadError
|
180
|
+
Yuyi.say "There was a problem loading the `#{name}` roll from `#{path}`", :type => :fail
|
181
|
+
Yuyi.say 'If this problem continues, please log an issue on the Yuyi github page.', :type => :warn
|
182
|
+
Yuyi.say
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# Check if roll has already been required & loaded
|
187
|
+
#
|
188
|
+
def roll_loaded? roll
|
189
|
+
rolls.keys.include? roll
|
190
|
+
end
|
191
|
+
|
192
|
+
# Return an array of the topologically sorted rolls from the menu
|
193
|
+
#
|
194
|
+
def sorted_rolls
|
195
|
+
tsort_hash = {}
|
196
|
+
rolls.each do |file_name, roll|
|
197
|
+
tsort_hash[file_name.to_s] = roll.dependencies.map(&:to_s)
|
198
|
+
end
|
199
|
+
|
200
|
+
tsort_hash.tsort.map(&:to_sym)
|
201
|
+
end
|
202
|
+
end
|
data/lib/yuyi/roll.rb
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class Yuyi::Roll
|
4
|
+
|
5
|
+
# DSL API Methods
|
6
|
+
#
|
7
|
+
def self.title title = nil
|
8
|
+
title ? @title = title : @title
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.file_name file_name = nil
|
12
|
+
file_name ? @file_name = file_name : @file_name
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.pre_install &block
|
16
|
+
Yuyi::Menu.add_pre_install block
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.install &block
|
20
|
+
@install ||= block
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.post_install &block
|
24
|
+
Yuyi::Menu.add_post_install block
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.uninstall &block
|
28
|
+
@uninstall ||= block
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.upgrade &block
|
32
|
+
@upgrade ||= block
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.installed? &block
|
36
|
+
@installed ||= block
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.dependencies *dependencies
|
40
|
+
@dependencies ||= []
|
41
|
+
|
42
|
+
unless dependencies.empty?
|
43
|
+
@dependencies |= dependencies
|
44
|
+
require_dependencies
|
45
|
+
end
|
46
|
+
|
47
|
+
@dependencies
|
48
|
+
end
|
49
|
+
|
50
|
+
# set option definitions
|
51
|
+
def self.options arg = {}
|
52
|
+
@option_defs ||= arg
|
53
|
+
end
|
54
|
+
|
55
|
+
# DSL Helper methods
|
56
|
+
#
|
57
|
+
def title; self.class.title; end
|
58
|
+
def file_name; self.class.file_name; end
|
59
|
+
|
60
|
+
# return option definitions
|
61
|
+
def option_defs
|
62
|
+
self.class.options
|
63
|
+
end
|
64
|
+
|
65
|
+
def write_to_file file, *text
|
66
|
+
Yuyi.write_to_file file, *text
|
67
|
+
end
|
68
|
+
|
69
|
+
def delete_from_file file, *text
|
70
|
+
Yuyi.delete_from_file file, *text
|
71
|
+
end
|
72
|
+
|
73
|
+
def options
|
74
|
+
option_defaults = {}
|
75
|
+
option_defs.each do |roll, option_settings|
|
76
|
+
option_defaults[roll] = option_settings[:default]
|
77
|
+
end
|
78
|
+
|
79
|
+
option_defaults.merge Yuyi::Menu.options(self)
|
80
|
+
end
|
81
|
+
|
82
|
+
def dependencies
|
83
|
+
self.class.dependencies
|
84
|
+
end
|
85
|
+
|
86
|
+
# Run the roll
|
87
|
+
#
|
88
|
+
def order
|
89
|
+
if installed?
|
90
|
+
if options[:uninstall]
|
91
|
+
Yuyi.say "🍣\s Uninstalling #{title}...", :color => 33
|
92
|
+
uninstall
|
93
|
+
elsif upgrade?
|
94
|
+
Yuyi.say "🍣\s Upgrading #{title}", :color => 36
|
95
|
+
upgrade
|
96
|
+
end
|
97
|
+
else
|
98
|
+
Yuyi.say "🍣\s Installing #{title}...", :color => 32
|
99
|
+
install
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def upgrade?
|
106
|
+
Yuyi::Menu.upgrade?
|
107
|
+
end
|
108
|
+
|
109
|
+
# Add to global collection of rolls
|
110
|
+
#
|
111
|
+
def self.inherited klass
|
112
|
+
# convert class name to a human readble title-cased string
|
113
|
+
klass.title klass.to_s.gsub(/(?=[A-Z])/, ' ').strip
|
114
|
+
|
115
|
+
# convert absolute path to a file name symbol
|
116
|
+
klass.file_name caller.first[/[a-z_]+?(?=\.rb)/].to_sym
|
117
|
+
|
118
|
+
Yuyi::Menu.add_roll klass.file_name, klass
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.require_dependencies
|
122
|
+
@dependencies.each do |roll|
|
123
|
+
unless Yuyi::Menu.on_the_menu? roll
|
124
|
+
Yuyi::Menu.find_roll roll
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def install
|
130
|
+
instance_eval(&self.class.install)
|
131
|
+
end
|
132
|
+
|
133
|
+
def uninstall
|
134
|
+
instance_eval(&self.class.uninstall)
|
135
|
+
end
|
136
|
+
|
137
|
+
def upgrade
|
138
|
+
instance_eval(&self.class.upgrade)
|
139
|
+
end
|
140
|
+
|
141
|
+
def installed?
|
142
|
+
!!instance_eval(&self.class.installed?)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Helpers for Yuyi Cli methods
|
146
|
+
def say *args; Yuyi.say *args; end
|
147
|
+
def run *args; Yuyi.run *args; end
|
148
|
+
def command? *args; Yuyi.command? *args; end
|
149
|
+
|
150
|
+
def on_the_menu? roll
|
151
|
+
Yuyi::Menu.on_the_menu? roll
|
152
|
+
end
|
153
|
+
end
|
data/lib/yuyi/source.rb
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'pathname'
|
3
|
+
require 'tmpdir'
|
4
|
+
|
5
|
+
class Yuyi::Source
|
6
|
+
ROLL_FILE_GLOB = '**/*.rb'
|
7
|
+
|
8
|
+
def available_rolls; @available_rolls end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def self.create_main_tmp_dir
|
13
|
+
@@root_tmp_dir = Dir.mktmpdir
|
14
|
+
$: << @@root_tmp_dir
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize name, path
|
18
|
+
@available_rolls = {}
|
19
|
+
@name = name
|
20
|
+
@path = path
|
21
|
+
|
22
|
+
create_tmp_dir
|
23
|
+
download_source
|
24
|
+
get_available_rolls
|
25
|
+
end
|
26
|
+
|
27
|
+
def create_tmp_dir
|
28
|
+
dir = FileUtils.mkdir_p(File.join(@@root_tmp_dir, @name.to_s))
|
29
|
+
|
30
|
+
# RUBY_VERSION
|
31
|
+
# mkdir_p <= 1.8 returns a string
|
32
|
+
# mkdir_p >= 1.9 returns an array
|
33
|
+
#
|
34
|
+
dir = case dir
|
35
|
+
when Array
|
36
|
+
dir.first
|
37
|
+
else
|
38
|
+
dir
|
39
|
+
end
|
40
|
+
|
41
|
+
@tmp_dir = dir
|
42
|
+
end
|
43
|
+
|
44
|
+
def download_source
|
45
|
+
# if remote source
|
46
|
+
if URI.parse(@path).scheme
|
47
|
+
zip_file = File.join(@tmp_dir, @name.to_s)
|
48
|
+
|
49
|
+
# Download the file and save it to the tmp directory
|
50
|
+
open zip_file, 'w' do |save_file|
|
51
|
+
begin
|
52
|
+
save_file << open(@path).read
|
53
|
+
rescue
|
54
|
+
Yuyi.say "Could not open the `#{@name}` source. Please check that the path is correct.", :type => :fail
|
55
|
+
Yuyi.say "If you continue to have issues, your source server may be down.", :type => :warn
|
56
|
+
return
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
unzip_source zip_file
|
61
|
+
|
62
|
+
# if local source
|
63
|
+
else
|
64
|
+
path = File.expand_path(@path)
|
65
|
+
|
66
|
+
# if file, expect archive file and unzip it
|
67
|
+
if File.file? path
|
68
|
+
unzip_source path if File.file? path
|
69
|
+
|
70
|
+
# if dir, copy rolls out of it
|
71
|
+
else
|
72
|
+
FileUtils.cp_r Dir.glob(File.join(path, ROLL_FILE_GLOB)), @tmp_dir
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def unzip_source file
|
78
|
+
# unzip file
|
79
|
+
unzip = if RUBY_PLATFORM =~ /darwin/
|
80
|
+
Yuyi.run "tar -xf #{file} -C #{@tmp_dir}", :boolean => true
|
81
|
+
else
|
82
|
+
Yuyi.run "unzip -o #{file} -d #{@tmp_dir}", :boolean => true
|
83
|
+
end
|
84
|
+
|
85
|
+
if unzip == false
|
86
|
+
Yuyi.say "The `#{@name}` source is an unrecognized archive format.", :type => :fail
|
87
|
+
Yuyi.say "Make sure it is a format supported by tar (tar, pax, cpio, zip, jar, ar, or ISO 9660 image)", :type => :warn
|
88
|
+
exit
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Get source rolls from tmp directory
|
93
|
+
#
|
94
|
+
def get_available_rolls
|
95
|
+
Dir.glob(File.join(@tmp_dir, ROLL_FILE_GLOB)).map do |r|
|
96
|
+
name = File.basename(r, '.rb').to_sym
|
97
|
+
tmp_path = Pathname.new @@root_tmp_dir
|
98
|
+
full_path = Pathname.new r
|
99
|
+
require_path = full_path.relative_path_from(tmp_path).to_s.chomp('.rb')
|
100
|
+
|
101
|
+
@available_rolls[name] = require_path
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Run when class is loaded
|
106
|
+
create_main_tmp_dir
|
107
|
+
end
|
data/lib/yuyi.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
class Yuyi; end
|
2
|
+
|
3
|
+
require 'yuyi/cli'
|
4
|
+
require 'yuyi/core'
|
5
|
+
require 'yuyi/menu'
|
6
|
+
require 'yuyi/roll'
|
7
|
+
require 'yuyi/source'
|
8
|
+
|
9
|
+
class Yuyi
|
10
|
+
extend Yuyi::Cli
|
11
|
+
|
12
|
+
NAME = 'Yuyi'
|
13
|
+
VERSION = '0.1.1'
|
14
|
+
DEFAULT_MENU = File.expand_path('~/yuyi_menu')
|
15
|
+
end
|
Binary file
|