ektoplayer 0.1.0
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/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
|