ektoplayer 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +49 -0
- data/bin/ektoplayer +7 -0
- data/lib/ektoplayer.rb +10 -0
- data/lib/ektoplayer/application.rb +148 -0
- data/lib/ektoplayer/bindings.rb +230 -0
- data/lib/ektoplayer/browsepage.rb +138 -0
- data/lib/ektoplayer/client.rb +18 -0
- data/lib/ektoplayer/common.rb +91 -0
- data/lib/ektoplayer/config.rb +247 -0
- data/lib/ektoplayer/controllers/browser.rb +47 -0
- data/lib/ektoplayer/controllers/controller.rb +9 -0
- data/lib/ektoplayer/controllers/help.rb +21 -0
- data/lib/ektoplayer/controllers/info.rb +22 -0
- data/lib/ektoplayer/controllers/mainwindow.rb +40 -0
- data/lib/ektoplayer/controllers/playlist.rb +60 -0
- data/lib/ektoplayer/database.rb +199 -0
- data/lib/ektoplayer/events.rb +56 -0
- data/lib/ektoplayer/models/browser.rb +127 -0
- data/lib/ektoplayer/models/database.rb +49 -0
- data/lib/ektoplayer/models/model.rb +15 -0
- data/lib/ektoplayer/models/player.rb +28 -0
- data/lib/ektoplayer/models/playlist.rb +72 -0
- data/lib/ektoplayer/models/search.rb +42 -0
- data/lib/ektoplayer/models/trackloader.rb +17 -0
- data/lib/ektoplayer/mp3player.rb +151 -0
- data/lib/ektoplayer/operations/browser.rb +19 -0
- data/lib/ektoplayer/operations/operations.rb +26 -0
- data/lib/ektoplayer/operations/player.rb +11 -0
- data/lib/ektoplayer/operations/playlist.rb +67 -0
- data/lib/ektoplayer/theme.rb +102 -0
- data/lib/ektoplayer/trackloader.rb +146 -0
- data/lib/ektoplayer/ui.rb +404 -0
- data/lib/ektoplayer/ui/colors.rb +105 -0
- data/lib/ektoplayer/ui/widgets.rb +195 -0
- data/lib/ektoplayer/ui/widgets/container.rb +125 -0
- data/lib/ektoplayer/ui/widgets/labelwidget.rb +43 -0
- data/lib/ektoplayer/ui/widgets/listwidget.rb +332 -0
- data/lib/ektoplayer/ui/widgets/tabbedcontainer.rb +110 -0
- data/lib/ektoplayer/updater.rb +77 -0
- data/lib/ektoplayer/views/browser.rb +25 -0
- data/lib/ektoplayer/views/help.rb +46 -0
- data/lib/ektoplayer/views/info.rb +208 -0
- data/lib/ektoplayer/views/mainwindow.rb +64 -0
- data/lib/ektoplayer/views/playinginfo.rb +135 -0
- data/lib/ektoplayer/views/playlist.rb +39 -0
- data/lib/ektoplayer/views/progressbar.rb +51 -0
- data/lib/ektoplayer/views/splash.rb +99 -0
- data/lib/ektoplayer/views/trackrenderer.rb +137 -0
- data/lib/ektoplayer/views/volumemeter.rb +74 -0
- metadata +164 -0
@@ -0,0 +1,138 @@
|
|
1
|
+
#!/bin/ruby
|
2
|
+
|
3
|
+
require 'nokogiri'
|
4
|
+
require 'base64'
|
5
|
+
require 'scanf'
|
6
|
+
require 'open-uri'
|
7
|
+
|
8
|
+
module Ektoplayer
|
9
|
+
class BrowsePage
|
10
|
+
ALBUM_KEYS = %w(url title artist date category styles cover_url description
|
11
|
+
download_count released_by released_by_url posted_by posted_by_url
|
12
|
+
archive_urls rating votes tracks).map(&:to_sym).freeze
|
13
|
+
|
14
|
+
TRACK_KEYS = %w(url album_url number title remix artist bpm).map(&:to_sym).freeze
|
15
|
+
|
16
|
+
attr_reader :albums, :page_urls, :current_page_index
|
17
|
+
|
18
|
+
def self.parse(src)
|
19
|
+
BrowsePage.new(src)
|
20
|
+
end
|
21
|
+
|
22
|
+
def styles; @@styles or [] end
|
23
|
+
def first_page_url; @page_urls[0] end
|
24
|
+
def last_page_url; @page_urls[-1] end
|
25
|
+
def current_page_url; @page_urls[@current_page_index] end
|
26
|
+
|
27
|
+
def prev_page_url
|
28
|
+
@page_urls[@current_page_index - 1] if @current_page_index > 0
|
29
|
+
end
|
30
|
+
|
31
|
+
def next_page_url
|
32
|
+
@page_urls[@current_page_index + 1] if @current_page_index + 1 < @page_urls.size
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize(src)
|
36
|
+
doc = Nokogiri::HTML(open(src))
|
37
|
+
@albums = []
|
38
|
+
|
39
|
+
@page_urls = []
|
40
|
+
doc.css('.wp-pagenavi option').each_with_index do |option, i|
|
41
|
+
@current_page_index = i if option[:selected]
|
42
|
+
@page_urls << option[:value]
|
43
|
+
end
|
44
|
+
|
45
|
+
@@styles ||= begin
|
46
|
+
doc.xpath('//a[contains(@href, "http") and contains(@href, "/style/")]').map do |a|
|
47
|
+
[ a.text, a[:href] ]
|
48
|
+
end.to_h
|
49
|
+
end
|
50
|
+
|
51
|
+
doc.xpath('//div[starts-with(@id, "post-")]').each do |post|
|
52
|
+
album = { tracks: [] }
|
53
|
+
album[:date] = post.at_css('.d').text rescue nil
|
54
|
+
album[:category] = post.at_css('.c a').text rescue nil
|
55
|
+
|
56
|
+
album[:styles] = []
|
57
|
+
post.css('.style a').map do |a|
|
58
|
+
@@styles[a.text] = a[:href]
|
59
|
+
album[:styles] << a.text
|
60
|
+
end
|
61
|
+
|
62
|
+
album[:cover_url] = post.at_css('.cover')[:src] rescue nil
|
63
|
+
album[:description] = post.at_css(?p).to_html rescue ''
|
64
|
+
album[:download_count] = post.at_css('.dc strong').text.delete(?,).to_i rescue 0
|
65
|
+
|
66
|
+
post.css('h1 a').each do |a|
|
67
|
+
album[:title] = a.text
|
68
|
+
album[:url] = a[:href]
|
69
|
+
end
|
70
|
+
|
71
|
+
post.xpath('.//a[@rel="tag"]').each do |a|
|
72
|
+
album[:released_by] = a.text
|
73
|
+
album[:released_by_url] = a[:href]
|
74
|
+
end
|
75
|
+
|
76
|
+
post.xpath('.//a[@rel="author external"]').each do |a|
|
77
|
+
album[:posted_by] = a.text
|
78
|
+
album[:posted_by_url] = a[:href]
|
79
|
+
end
|
80
|
+
|
81
|
+
album[:archive_urls] = post.css('.dll a').map do |a|
|
82
|
+
[ a.text.split[0] , a[:href] ]
|
83
|
+
end.to_h
|
84
|
+
|
85
|
+
begin
|
86
|
+
post.at_css('.postmetadata .d').
|
87
|
+
text.scanf('Rated %f%% with %d votes').
|
88
|
+
each_slice(2) { |r,v| album[:rating], album[:votes] = r, v }
|
89
|
+
rescue
|
90
|
+
album[:rating], album[:votes] = 0, 0
|
91
|
+
end
|
92
|
+
|
93
|
+
begin
|
94
|
+
base64_tracklist = post.at_css(:script).text.scan(/soundFile:"(.*)"/)[0][0]
|
95
|
+
tracklist_urls = Base64.decode64(base64_tracklist).split(?,)
|
96
|
+
rescue
|
97
|
+
# Sometimes there are no tracks:
|
98
|
+
# http://www.ektoplazm.com/free-music/dj-basilisk-the-colours-of-ektoplazm
|
99
|
+
tracklist_urls = []
|
100
|
+
end
|
101
|
+
|
102
|
+
post.css('.tl').each do |album_track_list|
|
103
|
+
track = nil
|
104
|
+
album_track_list.css(:span).each do |ti|
|
105
|
+
case ti[:class]
|
106
|
+
when ?n
|
107
|
+
album[:tracks] << track if track and track[:url]
|
108
|
+
track = { url: tracklist_urls.shift }
|
109
|
+
track[:number] = ti.text.to_i
|
110
|
+
when ?t then track[:title] = ti.text
|
111
|
+
when ?a then track[:artist] = ti.text
|
112
|
+
when ?r then track[:remix] = ti.text
|
113
|
+
when ?d then track[:bpm] = ti.text.scan(/\d+/)[0].to_i rescue nil
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
album[:tracks] << track if track and track[:url]
|
118
|
+
end
|
119
|
+
|
120
|
+
# extract artist name out ouf album title, set missing artist on album tracks
|
121
|
+
unless album[:tracks].all? { |t| t.key?(:artist) }
|
122
|
+
if album[:title].include?' – '
|
123
|
+
album[:artist], album[:title] = album[:title].split(' – ', 2)
|
124
|
+
album[:tracks].each { |t| t[:artist] ||= album[:artist] }
|
125
|
+
else
|
126
|
+
album[:tracks].each { |t| t[:artist] ||= 'Unknown Artist' }
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
@albums << album
|
131
|
+
end
|
132
|
+
|
133
|
+
Application.log(self, "completed; #{src} #{@albums.size} albums found")
|
134
|
+
rescue
|
135
|
+
Application.log(self, src, $!)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
require_relative 'database'
|
4
|
+
require_relative 'trackloader'
|
5
|
+
|
6
|
+
module Ektoplayer
|
7
|
+
class Client
|
8
|
+
attr_reader :database
|
9
|
+
attr_reader :trackloader
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
db_file = Config.get(:database_file)
|
13
|
+
FileUtils::touch(db_file) unless File.file? db_file
|
14
|
+
@database = Database.new(db_file)
|
15
|
+
@trackloader = Trackloader.new(@database)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'zip'
|
2
|
+
|
3
|
+
class Object
|
4
|
+
alias :frz :freeze
|
5
|
+
end
|
6
|
+
|
7
|
+
class Dir
|
8
|
+
def Dir.size(path)
|
9
|
+
Dir.glob(File.join(path, '**', ?*)).map { |f| File.size(f) }.sum
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class String
|
14
|
+
def chunks(n)
|
15
|
+
return [self] if n < 1
|
16
|
+
|
17
|
+
if (chunk_size = self.size / n) < 1
|
18
|
+
return [self]
|
19
|
+
end
|
20
|
+
|
21
|
+
(n - 1).times.map do |i|
|
22
|
+
self.slice((chunk_size * i)..(chunk_size * (i + 1) - 1))
|
23
|
+
end + [
|
24
|
+
self.slice((chunk_size * (n-1))..-1)
|
25
|
+
]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
module Common
|
30
|
+
def self.open_url_extern(url)
|
31
|
+
if url =~ /\.(jpe?g|png)$/i
|
32
|
+
Common.open_image_extern(url)
|
33
|
+
else
|
34
|
+
fork { exec('xdg-open', url) }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.open_image_extern(url)
|
39
|
+
fork do
|
40
|
+
exec('feh', url) rescue (
|
41
|
+
exec('display', url) rescue (
|
42
|
+
exec('xdg-open', url)
|
43
|
+
)
|
44
|
+
)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.extract_zip(zip_file, dest)
|
49
|
+
Zip::File.open(zip_file) do |zip_obj|
|
50
|
+
zip_obj.each do |f|
|
51
|
+
f.extract(File.join(dest, f.name))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.with_hash_zip(keys, values)
|
57
|
+
hash = {}
|
58
|
+
|
59
|
+
keys.size.times do |i|
|
60
|
+
hash[keys[i]] = values[i]
|
61
|
+
end
|
62
|
+
|
63
|
+
yield hash
|
64
|
+
|
65
|
+
keys.clear << hash.keys
|
66
|
+
values.clear << hash.values
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.to_time(secs)
|
70
|
+
return '00:00' unless secs or secs == 0
|
71
|
+
"%0.2d:%0.2d" % [(secs / 60), (secs % 60)]
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.mksingleton(cls)
|
75
|
+
unless cls.singleton_methods.include? :_get_instance
|
76
|
+
cls.define_singleton_method(:_get_instance) do
|
77
|
+
unless cls.class_variable_defined? :@@_class_instance
|
78
|
+
cls.class_variable_set :@@_class_instance, cls.new
|
79
|
+
end
|
80
|
+
|
81
|
+
cls.class_variable_get :@@_class_instance
|
82
|
+
end
|
83
|
+
|
84
|
+
(cls.instance_methods - Object.instance_methods).each do |method|
|
85
|
+
cls.define_singleton_method(method) do |*args|
|
86
|
+
cls._get_instance.send(method, *args)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,247 @@
|
|
1
|
+
require 'shellwords'
|
2
|
+
require 'nokogiri'
|
3
|
+
|
4
|
+
module Ektoplayer
|
5
|
+
class ColumnFormat
|
6
|
+
def self.parse_column_format(format)
|
7
|
+
self.parse_simple_format(format).
|
8
|
+
select { |f| f[:tag] != 'text' }.
|
9
|
+
map do |fmt|
|
10
|
+
fmt[:size] = fmt[:size].to_i if fmt[:size]
|
11
|
+
fmt[:rel] = fmt[:rel].to_i if fmt[:rel]
|
12
|
+
fmt[:justify] = fmt[:justify].to_sym if fmt[:justify]
|
13
|
+
|
14
|
+
begin
|
15
|
+
fail 'Missing size= or rel=' if (!fmt[:size] and !fmt[:rel])
|
16
|
+
fail 'size= and rel= are mutually exclusive' if (fmt[:size] and fmt[:rel])
|
17
|
+
rescue
|
18
|
+
fail "column: #{fmt[:tag]}: #{$!}"
|
19
|
+
end
|
20
|
+
|
21
|
+
fmt
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.parse_simple_format(format)
|
26
|
+
self._parse_markup(format).map do |fmt|
|
27
|
+
attrs = []
|
28
|
+
attrs << :bold if fmt[:bold]
|
29
|
+
attrs << :blink if fmt[:blink]
|
30
|
+
attrs << :standout if fmt[:standout]
|
31
|
+
attrs << :underline if fmt[:underline]
|
32
|
+
fmt[:curses_attrs] = [ (fmt[:fg] and fmt[:fg].to_sym), (fmt[:bg] and fmt[:bg].to_sym), *attrs]
|
33
|
+
fmt
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self._parse_markup(format)
|
38
|
+
Nokogiri::XML("<f>#{format}</f>").first_element_child.
|
39
|
+
children.map do |fmt|
|
40
|
+
fmt1 = fmt.attributes.map do |name,a|
|
41
|
+
[name.to_sym, a.value]
|
42
|
+
end.to_h.update(tag: fmt.name)
|
43
|
+
fmt1[:text] = fmt.text if fmt1[:tag] == 'text'
|
44
|
+
fmt1
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class Config
|
50
|
+
CONFIG_DIR = File.join(Dir.home, '.config', 'ektoplayer').freeze
|
51
|
+
CONFIG_FILE = File.join(CONFIG_DIR, 'ektoplayer.rc').freeze
|
52
|
+
|
53
|
+
DEFAULT_PLAYLIST_FORMAT = (
|
54
|
+
'<number size="3" fg="magenta" />' +
|
55
|
+
'<artist rel="33" fg="blue" />' +
|
56
|
+
'<album rel="33" fg="red" />' +
|
57
|
+
'<title rel="33" fg="yellow" />' +
|
58
|
+
'<styles rel="20" fg="cyan" />' +
|
59
|
+
'<bpm size="4" fg="green" justify="right" />').freeze
|
60
|
+
|
61
|
+
DEFAULT_PLAYINGINFO_FORMAT1 =
|
62
|
+
'<text fg="black">──┤ </text><title bold="on" fg="yellow" /><text fg="black"> ├──</text>'.freeze
|
63
|
+
|
64
|
+
DEFAULT_PLAYINGINFO_FORMAT2 =
|
65
|
+
'<artist bold="on" fg="blue" /><text> - </text><album bold="on" fg="red" /><text> (</text><date fg="cyan" /><text>)</text>'.freeze
|
66
|
+
|
67
|
+
def register(key, description, default, method=nil)
|
68
|
+
@doc[key.to_sym] = description.squeeze(' ').freeze
|
69
|
+
|
70
|
+
if method
|
71
|
+
@cast[key.to_sym] = method if method
|
72
|
+
@options[key.to_sym] = method.(default).freeze
|
73
|
+
else
|
74
|
+
@options[key.to_sym] = default.freeze
|
75
|
+
end
|
76
|
+
end
|
77
|
+
alias :reg :register
|
78
|
+
|
79
|
+
def initialize
|
80
|
+
@options = Hash.new { |h,k| fail "Unknown option #{k}" }
|
81
|
+
@doc, @cast = {}, {}
|
82
|
+
|
83
|
+
reg :database_file, 'File to store metadata',
|
84
|
+
File.join(CONFIG_DIR, 'meta.db'),
|
85
|
+
File.method(:expand_path)
|
86
|
+
|
87
|
+
reg :log_file, 'Log file',
|
88
|
+
File.join(CONFIG_DIR, 'ektoplayer.log'),
|
89
|
+
File.method(:expand_path)
|
90
|
+
|
91
|
+
reg :temp_dir, %{Where to temporary store mp3 files. Directory will be created
|
92
|
+
if it does not exist (parent directories will NOT be created},
|
93
|
+
'/tmp/.ektoplazm',
|
94
|
+
File.method(:expand_path)
|
95
|
+
|
96
|
+
reg :cache_dir,
|
97
|
+
'Directory for storing mp3 files',
|
98
|
+
File.join(Dir.home, '.cache', 'ektoplayer'),
|
99
|
+
File.method(:expand_path)
|
100
|
+
|
101
|
+
reg :archive_dir,
|
102
|
+
'Where to search for downloaded MP3 archives',
|
103
|
+
File.join(CONFIG_DIR, 'archives'),
|
104
|
+
File.method(:expand_path)
|
105
|
+
|
106
|
+
reg :download_dir,
|
107
|
+
'Where to store downloaded MP3 archives', '/tmp',
|
108
|
+
File.method(:expand_path)
|
109
|
+
|
110
|
+
reg :auto_extract_to_archive_dir,
|
111
|
+
%{Enable/disable automatic extraction of downloaded MP3
|
112
|
+
archives from download_dir to archive_dir}, true
|
113
|
+
|
114
|
+
reg :delete_after_extraction,
|
115
|
+
%{In combination with auto_extract_to_archive_dir:
|
116
|
+
Delete zip archive after successful extraction}, true
|
117
|
+
|
118
|
+
reg :playlist_load_newest,
|
119
|
+
%{How many tracks from database should be added to
|
120
|
+
the playlist on application start}, 100
|
121
|
+
|
122
|
+
reg :use_cache,
|
123
|
+
'Enable/disable local mp3 cache', true
|
124
|
+
|
125
|
+
reg :small_update_pages,
|
126
|
+
'How many pages should be fetched after start', 5
|
127
|
+
|
128
|
+
reg :use_colors,
|
129
|
+
'Choose color capabilities. auto|mono|8|256', 'auto',
|
130
|
+
lambda { |v|
|
131
|
+
{ 'auto' => :auto, 'mono' => 0,
|
132
|
+
'8' => 8, '256' => 256 }[v] or fail 'invalid value'
|
133
|
+
}
|
134
|
+
|
135
|
+
reg :threads,
|
136
|
+
'Number of donwload threads during database update',
|
137
|
+
20, lambda { |v| fail if Integer(v) < 1; Integer(v) }
|
138
|
+
|
139
|
+
reg 'browser.format', 'Format of browser columns',
|
140
|
+
DEFAULT_PLAYLIST_FORMAT, ColumnFormat.method(:parse_column_format)
|
141
|
+
|
142
|
+
reg 'playlist.format', 'Format of playlist columns',
|
143
|
+
DEFAULT_PLAYLIST_FORMAT, ColumnFormat.method(:parse_column_format)
|
144
|
+
|
145
|
+
# - Progressbar
|
146
|
+
reg 'progressbar.display',
|
147
|
+
'Enable/disable progressbar', true
|
148
|
+
|
149
|
+
reg 'progressbar.download_char',
|
150
|
+
'Character used for displaying download progress', ?-
|
151
|
+
|
152
|
+
reg 'progressbar.progress_char',
|
153
|
+
'Character used for displaying playing progress', '─'
|
154
|
+
|
155
|
+
reg 'progressbar.rest_char',
|
156
|
+
'Character used for the rest of the line', '─'
|
157
|
+
|
158
|
+
# - Volumemeter
|
159
|
+
reg 'volumemeter.display',
|
160
|
+
'Enable/disable volumemeter', true
|
161
|
+
|
162
|
+
reg 'volumemeter.level_char',
|
163
|
+
'Character used for displaying volume level', '·'
|
164
|
+
|
165
|
+
reg 'volumemeter.rest_char',
|
166
|
+
'Character used for the rest of the line', '·'
|
167
|
+
|
168
|
+
# - Playinginfo
|
169
|
+
reg 'playinginfo.display',
|
170
|
+
'Enable/display playinginfo', true
|
171
|
+
|
172
|
+
reg 'playinginfo.format1',
|
173
|
+
'Format of first line in playinginfo', DEFAULT_PLAYINGINFO_FORMAT1,
|
174
|
+
ColumnFormat.method(:parse_simple_format)
|
175
|
+
|
176
|
+
reg 'playinginfo.format2',
|
177
|
+
'Format of second line in playinginfo', DEFAULT_PLAYINGINFO_FORMAT2,
|
178
|
+
ColumnFormat.method(:parse_simple_format)
|
179
|
+
|
180
|
+
# - Tabs
|
181
|
+
reg 'tabs.show_tabbar',
|
182
|
+
'Enable/disable tabbar', true
|
183
|
+
|
184
|
+
reg 'tabs.widgets', 'Specify widget order of tabbar (left to right)',
|
185
|
+
'splash,playlist,browser,info,help',
|
186
|
+
lambda { |v| v.split(/\s*,\s*/).map(&:to_sym) }
|
187
|
+
|
188
|
+
reg 'main.widgets', 'Specify widgets to show (up to down)',
|
189
|
+
'playinginfo,progressbar,tabs,volumemeter',
|
190
|
+
lambda { |v| v.split(/\s*,\s*/).map(&:to_sym) }
|
191
|
+
end
|
192
|
+
|
193
|
+
def get(key) @options[key] end
|
194
|
+
def [](key) @options[key] end
|
195
|
+
|
196
|
+
def set(option, value)
|
197
|
+
option = option.to_sym
|
198
|
+
current_value = get(option)
|
199
|
+
|
200
|
+
if cast = @cast[option]
|
201
|
+
@options[option] = cast.call(value)
|
202
|
+
else
|
203
|
+
if current_value.is_a?Integer
|
204
|
+
@options[option] = Integer(value)
|
205
|
+
elsif current_value.is_a?Float
|
206
|
+
@options[option] = Float(value)
|
207
|
+
elsif current_value.is_a?TrueClass or current_value.is_a?FalseClass
|
208
|
+
fail 'invalid bool' unless %w(true false).include? value
|
209
|
+
@options[option] = (value == 'true')
|
210
|
+
else
|
211
|
+
@options[option] = value
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
@options[option].freeze
|
216
|
+
rescue
|
217
|
+
fail "Invalid value '#{value}' for '#{option}': #{$!}"
|
218
|
+
end
|
219
|
+
|
220
|
+
def parse(file, bindings, theme)
|
221
|
+
callbacks = {
|
222
|
+
'set': self.method(:set),
|
223
|
+
'bind': bindings.method(:bind),
|
224
|
+
'unbind': bindings.method(:unbind),
|
225
|
+
'color': theme.method(:color),
|
226
|
+
'color_256': theme.method(:color_256),
|
227
|
+
'color_mono': theme.method(:color_mono)
|
228
|
+
}
|
229
|
+
callbacks.default_proc = proc { |h,k| fail "unknown command: #{k}" }
|
230
|
+
callbacks.freeze
|
231
|
+
|
232
|
+
open(file, ?r).readlines.each do |line|
|
233
|
+
line.chomp!
|
234
|
+
next if line.empty? or line.start_with?(?#)
|
235
|
+
command, *args = line.shellsplit
|
236
|
+
|
237
|
+
begin
|
238
|
+
cb = callbacks[command.to_sym]
|
239
|
+
fail "missing arguments for #{command}" if args.size != cb.arity
|
240
|
+
cb.call(*args)
|
241
|
+
rescue
|
242
|
+
fail "#{file}:#{$.}: #{$!}"
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|