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.
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
@@ -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
@@ -0,0 +1,6 @@
1
+ sources:
2
+ - local: spec/fixtures/roll_dir
3
+ - yuyi: spec/fixtures/roll_zip.zip
4
+ rolls:
5
+ foo_roll:
6
+ foo: bar
@@ -0,0 +1,6 @@
1
+ sources:
2
+ - local: spec/fixtures/roll_dir
3
+ - yuyi: spec/fixtures/roll_zip.zip
4
+ rolls:
5
+ foo_roll:
6
+ bar: foo
@@ -0,0 +1,2 @@
1
+ class FooRoll < Yuyi::Roll
2
+ end
@@ -0,0 +1,2 @@
1
+ class FooRoll < Yuyi::Roll
2
+ end
Binary file