Commit 7195c2f2dc727bf8e35832ecc8154361c9db05d9

Refactor and restructure CLI

* lib/amazing/cli.rb: Require CLI initializers, helpers
and commands.
(Amazing) Fix bad documentation for CLI.
(CLI) Include initializers, helpers and commands.
(initialize) Move most aspects to Initializers
named with initialize_ prefix. Set at_exit here
rather than in #run.
(run) Move most stuff to Helpers and Commands
where helpers have arbitrary names and commands
are prefixed with cmd_. Move trap to before updates.

* lib/amazing/cli/initializers.rb: CLI initializers.
These mostly set up variables before run.

* lib/amazing/cli/helpers.rb: CLI helpers.
These do abit of anything but shouldn't exit.

* lib/amazing/cli/commands.rb: CLI commands.
Commands requested by the user via switches,
may exit the program.

Commit diff

lib/amazing/cli.rb

 
22# Licensed under the Academic Free License version 3.0
33
44require 'amazing'
5require 'amazing/cli/commands'
6require 'amazing/cli/helpers'
7require 'amazing/cli/initializers'
58require 'fileutils'
69require 'logger'
710require 'thread'
1515
1616 # Command line interface runner
1717 #
18 # CLI.run(ARGV)
18 # CLI.new(ARGV).run
1919 class CLI
20 def initialize(args)
21 $KCODE = "utf-8"
20 include Initializers
21 include Helpers
22 include Commands
23
24 def initialize(args=ARGV)
2225 @args = args
23 @log = Logger.new(STDOUT)
24 @options = Options.new(@args)
25 begin
26 @display = X11::DisplayName.new
27 rescue X11::EmptyDisplayName => e
28 @log.warn("#{e.message}, falling back on :0")
29 @display = X11::DisplayName.new(":0")
30 rescue X11::InvalidDisplayName => e
31 @log.fatal("#{e.message}, exiting")
32 exit 1
33 end
34 @threads = []
26 initialize_threads
27 initialize_encoding
28 initialize_logger
29 initialize_options
30 initialize_display
31 initialize_awesome
32 initialize_exit
3533 end
3634
3735 def run
38 at_exit { Thread.list.each {|t| t.exit } }
39 trap("SIGINT") do
40 @log.fatal("Received SIGINT, exiting")
41 remove_pid
42 exit
43 end
44 @options.parse
45 show_help if @options[:help]
36 parse_options
37 cmd_show_help
4638 set_loglevel
47 stop_process(true) if @options[:stop]
39 cmd_stop_process
4840 load_scripts
49 list_widgets if @options[:listwidgets]
50 test_widget if @options[:test]
41 cmd_list_widgets
42 cmd_test_widget
5143 parse_config
5244 wait_for_sockets
53 @awesome = Awesome.new(@display.display)
54 explicit_updates unless @options[:update] == []
45 cmd_explicit_updates
5546 stop_process
5647 save_pid
57 @threads << Thread.new { update_non_interval }
58 @config[:awesome].each do |awesome|
59 awesome[:widgets].each do |widget|
60 if widget[:interval]
61 @threads << Thread.new(awesome, widget) do |awesome, widget|
62 iteration = 1
63 loop do
64 Thread.new { update_widget(awesome[:screen], awesome[:statusbar], widget, iteration) }
65 iteration += 1
66 sleep widget[:interval]
67 end
68 end
69 end
70 end
71 end
72 @threads.each {|t| t.join }
73 end
74
75 private
76
77 def show_help
78 puts @options.help
79 exit
80 end
81
82 def set_loglevel
83 begin
84 @log.level = Logger.const_get(@options[:loglevel].upcase)
85 rescue NameError
86 @log.error("Unsupported log level #{@options[:loglevel].inspect}")
87 @log.level = Logger::INFO
88 end
89 end
90
91 def stop_process(quit=false)
92 begin
93 Process.kill("SIGINT", File.read("#{ENV["HOME"]}/.amazing/pids/#{@display.display}.pid").to_i)
94 @log.warn("Killed older process") unless quit
95 rescue
96 end
97 exit if quit
98 end
99
100 def load_scripts
101 scripts = @options[:include]
102 if @options[:autoinclude]
103 scripts << Dir["#{ENV["HOME"]}/.amazing/widgets/*"]
104 end
105 scripts.flatten.each do |script|
106 if File.exist?(script)
107 @log.debug("Loading script #{script.inspect}")
108 begin
109 Widgets.module_eval(File.read(script), script)
110 rescue SyntaxError => e
111 @log.error("Bad syntax in #{script} at line #{e.to_s.scan(/:(\d+)/)}")
112 end
113 else
114 @log.error("No such widget script #{script.inspect}")
115 end
116 end
117 end
118
119 def parse_config
120 @log.debug("Parsing configuration file")
121 begin
122 @config = Config.new(@options[:config])
123 rescue
124 @log.fatal("Unable to parse configuration file, exiting")
125 exit 1
126 end
127 end
128
129 def list_widgets
130 if @options[:listwidgets] == true
131 longest_widget_name = Widgets.constants.inject {|a,b| a.length > b.length ? a : b }.length
132 Widgets.constants.sort.each do |widget|
133 widget_class = Widgets.const_get(widget)
134 puts "%-#{longest_widget_name}s : %s" % [widget, widget_class.description]
135 end
136 else
137 widget_class = Widgets.const_get(@options[:listwidgets].camel_case)
138 puts
139 puts "#{@options[:listwidgets].camel_case} - #{widget_class.description}"
140 puts
141 dependencies = widget_class.dependencies
142 unless dependencies.empty?
143 longest_dependency_name = dependencies.keys.inject {|a,b| a.to_s.length > b.to_s.length ? a : b }.to_s.length
144 longest_dependency_name = 10 if longest_dependency_name < 10
145 longest_description = dependencies.values.inject {|a,b| a.length > b.length ? a : b }.length
146 longest_description = 11 if longest_description < 11
147 puts " %-#{longest_dependency_name}s | DESCRIPTION" % "DEPENDENCY"
148 puts "-" * (longest_dependency_name + longest_description + 5)
149 dependencies.keys.sort.each do |dependency|
150 puts " %-#{longest_dependency_name}s | #{dependencies[dependency]}" % dependency
151 end
152 puts
153 end
154 options = widget_class.options
155 unless options.empty?
156 longest_option_name = options.keys.inject {|a,b| a.to_s.length > b.to_s.length ? a : b }.to_s.length
157 longest_option_name = 6 if longest_option_name < 6
158 longest_description = options.values.inject {|a,b| a[:description].length > b[:description].length ? a : b }[:description].length
159 longest_description = 11 if longest_description < 11
160 longest_default = options.values.inject {|a,b| a[:default].inspect.length > b[:default].inspect.length ? a : b }[:default].inspect.length
161 longest_default = 7 if longest_default < 7
162 puts " %-#{longest_option_name}s | %-#{longest_description}s | DEFAULT" % ["OPTION", "DESCRIPTION"]
163 puts "-" * (longest_option_name + longest_description + longest_default + 8)
164 options.keys.sort_by {|option| option.to_s }.each do |option|
165 puts " %-#{longest_option_name}s | %-#{longest_description}s | %s" % [option, options[option][:description], options[option][:default].inspect]
166 end
167 puts
168 end
169 fields = widget_class.fields
170 unless fields.empty?
171 longest_field_name = fields.keys.inject {|a,b| a.to_s.length > b.to_s.length ? a : b }.to_s.length
172 longest_field_name = 5 if longest_field_name < 5
173 longest_description = fields.values.inject {|a,b| a[:description].length > b[:description].length ? a : b }[:description].length
174 longest_description = 11 if longest_description < 11
175 longest_default = fields.values.inject {|a,b| a[:default].inspect.length > b[:default].inspect.length ? a : b }[:default].inspect.length
176 longest_default = 7 if longest_default < 7
177 puts " %-#{longest_field_name + 1}s | %-#{longest_description}s | DEFAULT" % ["FIELD", "DESCRIPTION"]
178 puts "-" * (longest_field_name + longest_description + longest_default + 9)
179 fields.keys.sort_by {|field| field.to_s }.each do |field|
180 puts " @%-#{longest_field_name}s | %-#{longest_description}s | %s" % [field, fields[field][:description], fields[field][:default].inspect]
181 end
182 puts
183 end
184 end
185 exit
186 end
187
188 def test_widget
189 widget = Widgets.const_get(@options[:test].camel_case)
190 settings = YAML.load("{#{ARGV[0]}}")
191 instance = widget.new(settings)
192 longest_field_name = widget.fields.merge({:default => nil}).keys.inject {|a,b| a.to_s.length > b.to_s.length ? a : b }.to_s.length
193 puts "@%-#{longest_field_name}s = %s" % [:default, instance.instance_variable_get(:@default).inspect]
194 widget.fields.keys.sort_by {|field| field.to_s }.each do |field|
195 puts "@%-#{longest_field_name}s = %s" % [field, instance.instance_variable_get("@#{field}".to_sym).inspect]
196 end
197 exit
198 end
199
200 def wait_for_sockets
201 @log.debug("Waiting for awesome control socket for display #{@display.display}")
202 begin
203 Timeout.timeout(30) do
204 sleep 1 until File.exist?("#{ENV["HOME"]}/.awesome_ctl.#{@display.display}")
205 @log.debug("Got socket for display #{@display.display}")
206 end
207 rescue Timeout::Error
208 @log.fatal("Socket for display #{@display.display} not created within 30 seconds, exiting")
209 exit 1
210 end
211 end
212
213 def explicit_updates
214 @config[:awesome].each do |awesome|
215 awesome[:widgets].each do |widget|
216 locator = "%s/%s/%s" % [widget[:identifier], awesome[:statusbar], awesome[:screen]]
217 next unless @options[:update] == :all || @options[:update].include?(locator)
218 @threads << Thread.new(awesome, widget) do |awesome, widget|
219 update_widget(awesome[:screen], awesome[:statusbar], widget)
220 end
221 end
222 end
223 @threads.each {|t| t.join }
224 exit
225 end
226
227 def save_pid
228 path = "#{ENV["HOME"]}/.amazing/pids"
229 FileUtils.makedirs(path)
230 File.open("#{path}/#{@display.display}.pid", "w+") do |f|
231 f.write($$)
232 end
233 end
234
235 def remove_pid
236 File.delete("#{ENV["HOME"]}/.amazing/pids/#{@display.display}.pid") rescue Errno::ENOENT
237 end
238
239 def update_non_interval
240 @config[:awesome].each do |awesome|
241 awesome[:widgets].each do |widget|
242 next if widget[:interval]
243 @threads << Thread.new(awesome, widget) do |awesome, widget|
244 update_widget(awesome[:screen], awesome[:statusbar], widget)
245 end
246 end
247 end
248 end
249
250 def update_widget(screen, statusbar, widget, iteration=0)
251 threads = []
252 @log.debug("Updating widget #{widget[:identifier]} of type #{widget[:module]} on screen #{screen}")
253 begin
254 mod = Widgets.const_get(widget[:module]).new(widget.merge(:iteration => iteration))
255 if widget[:properties].empty?
256 threads << Thread.new(screen, statusbar, widget, mod) do |screen, statusbar, widget, mod|
257 @awesome.widget_tell(screen, statusbar, widget[:identifier], widget[:property], mod.formatize)
258 end
259 end
260 widget[:properties].each do |property, format|
261 threads << Thread.new(screen, statusbar, widget, property, mod, format) do |screen, statusbar, widget, property, mod, format|
262 @awesome.widget_tell(screen, statusbar, widget[:identifier], property, mod.formatize(format))
263 end
264 end
265 rescue WidgetError => e
266 @log.error(widget[:module]) { e.message }
267 end
268 threads.each {|t| t.join }
48 set_traps
49 update_non_interval
50 cmd_main
51 join_threads
26952 end
27053 end
27154end
toggle raw diff

lib/amazing/cli/commands.rb

 
1# Copyright (C) 2008 Dag Odenhall <dag.odenhall@gmail.com>
2# Licensed under the Academic Free License version 3.0
3
4module Amazing
5 class CLI
6 module Commands
7 private
8
9 def cmd_show_help
10 if @options[:help]
11 puts @options.help
12 exit
13 end
14 end
15
16 def cmd_stop_process
17 if @options[:stop]
18 stop_process(true)
19 exit
20 end
21 end
22
23 def cmd_list_widgets
24 if @options[:listwidgets]
25 if @options[:listwidgets] == true
26 longest_widget_name = Widgets.constants.inject {|a,b| a.length > b.length ? a : b }.length
27
28 Widgets.constants.sort.each do |widget|
29 widget_class = Widgets.const_get(widget)
30 puts "%-#{longest_widget_name}s : %s" % [widget, widget_class.description]
31 end
32
33 else
34 widget_class = Widgets.const_get(@options[:listwidgets].camel_case)
35
36 puts
37 puts "#{@options[:listwidgets].camel_case} - #{widget_class.description}"
38 puts
39
40 dependencies = widget_class.dependencies
41 unless dependencies.empty?
42 longest_dependency_name = dependencies.keys.inject {|a,b| a.to_s.length > b.to_s.length ? a : b }.to_s.length
43 longest_dependency_name = 10 if longest_dependency_name < 10
44 longest_description = dependencies.values.inject {|a,b| a.length > b.length ? a : b }.length
45 longest_description = 11 if longest_description < 11
46
47 puts " %-#{longest_dependency_name}s | DESCRIPTION" % "DEPENDENCY"
48 puts "-" * (longest_dependency_name + longest_description + 5)
49
50 dependencies.keys.sort.each do |dependency|
51 puts " %-#{longest_dependency_name}s | #{dependencies[dependency]}" % dependency
52 end
53
54 puts
55 end
56
57 options = widget_class.options
58
59 unless options.empty?
60 longest_option_name = options.keys.inject {|a,b| a.to_s.length > b.to_s.length ? a : b }.to_s.length
61 longest_option_name = 6 if longest_option_name < 6
62 longest_description = options.values.inject {|a,b| a[:description].length > b[:description].length ? a : b }[:description].length
63 longest_description = 11 if longest_description < 11
64 longest_default = options.values.inject {|a,b| a[:default].inspect.length > b[:default].inspect.length ? a : b }[:default].inspect.length
65 longest_default = 7 if longest_default < 7
66
67 puts " %-#{longest_option_name}s | %-#{longest_description}s | DEFAULT" % ["OPTION", "DESCRIPTION"]
68 puts "-" * (longest_option_name + longest_description + longest_default + 8)
69
70 options.keys.sort_by {|option| option.to_s }.each do |option|
71 puts " %-#{longest_option_name}s | %-#{longest_description}s | %s" % [option, options[option][:description], options[option][:default].inspect]
72 end
73
74 puts
75 end
76
77 fields = widget_class.fields
78
79 unless fields.empty?
80 longest_field_name = fields.keys.inject {|a,b| a.to_s.length > b.to_s.length ? a : b }.to_s.length
81 longest_field_name = 5 if longest_field_name < 5
82 longest_description = fields.values.inject {|a,b| a[:description].length > b[:description].length ? a : b }[:description].length
83 longest_description = 11 if longest_description < 11
84 longest_default = fields.values.inject {|a,b| a[:default].inspect.length > b[:default].inspect.length ? a : b }[:default].inspect.length
85 longest_default = 7 if longest_default < 7
86
87 puts " %-#{longest_field_name + 1}s | %-#{longest_description}s | DEFAULT" % ["FIELD", "DESCRIPTION"]
88 puts "-" * (longest_field_name + longest_description + longest_default + 9)
89
90 fields.keys.sort_by {|field| field.to_s }.each do |field|
91 puts " @%-#{longest_field_name}s | %-#{longest_description}s | %s" % [field, fields[field][:description], fields[field][:default].inspect]
92 end
93
94 puts
95 end
96 end
97
98 exit
99 end
100 end
101
102 def cmd_test_widget
103 if @options[:test]
104 widget = Widgets.const_get(@options[:test].camel_case)
105 settings = YAML.load("{#{ARGV[0]}}")
106 instance = widget.new(settings)
107 longest_field_name = widget.fields.merge({:default => nil}).keys.inject {|a,b| a.to_s.length > b.to_s.length ? a : b }.to_s.length
108
109 puts "@%-#{longest_field_name}s = %s" % [:default, instance.instance_variable_get(:@default).inspect]
110
111 widget.fields.keys.sort_by {|field| field.to_s }.each do |field|
112 puts "@%-#{longest_field_name}s = %s" % [field, instance.instance_variable_get("@#{field}".to_sym).inspect]
113 end
114
115 exit
116 end
117 end
118
119 def cmd_explicit_updates
120 if @options[:update] != []
121 @config[:awesome].each do |awesome|
122 awesome[:widgets].each do |widget|
123 locator = "%s/%s/%s" % [widget[:identifier], awesome[:statusbar], awesome[:screen]]
124 next unless @options[:update] == :all || @options[:update].include?(locator)
125
126 @threads << Thread.new(awesome, widget) do |awesome, widget|
127 update_widget(awesome[:screen], awesome[:statusbar], widget)
128 end
129 end
130 end
131
132 @threads.each {|t| t.join }
133 exit
134 end
135 end
136
137 def cmd_main
138 @config[:awesome].each do |awesome|
139 awesome[:widgets].each do |widget|
140 if widget[:interval]
141 @threads << Thread.new(awesome, widget) do |awesome, widget|
142 iteration = 1
143
144 loop do
145 Thread.new { update_widget(awesome[:screen], awesome[:statusbar], widget, iteration) }
146
147 iteration += 1
148 sleep widget[:interval]
149 end
150 end
151 end
152 end
153 end
154 end
155 end
156 end
157end
toggle raw diff

lib/amazing/cli/helpers.rb

 
1# Copyright (C) 2008 Dag Odenhall <dag.odenhall@gmail.com>
2# Licensed under the Academic Free License version 3.0
3
4module Amazing
5 class CLI
6 module Helpers
7 private
8
9 def parse_options
10 @options.parse
11 end
12
13 def set_loglevel
14 begin
15 @log.level = Logger.const_get(@options[:loglevel].upcase)
16
17 rescue NameError
18 @log.error("Unsupported log level #{@options[:loglevel].inspect}")
19 @log.level = Logger::INFO
20 end
21 end
22
23 def stop_process(log=true)
24 Process.kill("SIGINT", File.read("#{ENV["HOME"]}/.amazing/pids/#{@display.display}.pid").to_i)
25 @log.warn("Killed older process") if log
26 rescue
27 end
28
29 def load_scripts
30 scripts = @options[:include]
31
32 if @options[:autoinclude]
33 scripts << Dir["#{ENV["HOME"]}/.amazing/widgets/*"]
34 end
35
36 scripts.flatten.each do |script|
37 if File.exist?(script)
38 @log.debug("Loading script #{script.inspect}")
39
40 begin
41 Widgets.module_eval(File.read(script), script)
42
43 rescue SyntaxError => e
44 @log.error("Bad syntax in #{script} at line #{e.to_s.scan(/:(\d+)/)}")
45 end
46
47 else
48 @log.error("No such widget script #{script.inspect}")
49 end
50 end
51 end
52
53 def parse_config
54 @log.debug("Parsing configuration file")
55
56 begin
57 @config = Config.new(@options[:config])
58
59 rescue
60 @log.fatal("Unable to parse configuration file, exiting")
61
62 exit 1
63 end
64 end
65
66 def wait_for_sockets
67 @log.debug("Waiting for awesome control socket for display #{@display.display}")
68
69 begin
70 Timeout.timeout(30) do
71 sleep 1 until File.exist?("#{ENV["HOME"]}/.awesome_ctl.#{@display.display}")
72 @log.debug("Got socket for display #{@display.display}")
73 end
74
75 rescue Timeout::Error
76 @log.fatal("Socket for display #{@display.display} not created within 30 seconds, exiting")
77
78 exit 1
79 end
80 end
81
82 def save_pid
83 path = "#{ENV["HOME"]}/.amazing/pids"
84 FileUtils.makedirs(path)
85
86 File.open("#{path}/#{@display.display}.pid", "w+") do |f|
87 f.write($$)
88 end
89 end
90
91 def remove_pid
92 File.delete("#{ENV["HOME"]}/.amazing/pids/#{@display.display}.pid") rescue Errno::ENOENT
93 end
94
95 def set_traps
96 trap("SIGINT") do
97 @log.fatal("Received SIGINT, exiting")
98 remove_pid
99 exit
100 end
101 end
102
103 def update_non_interval
104 @threads << Thread.new do
105 @config[:awesome].each do |awesome|
106 awesome[:widgets].each do |widget|
107 next if widget[:interval]
108
109 @threads << Thread.new(awesome, widget) do |awesome, widget|
110 update_widget(awesome[:screen], awesome[:statusbar], widget)
111 end
112 end
113 end
114 end
115 end
116
117 def update_widget(screen, statusbar, widget, iteration=0)
118 threads = []
119 @log.debug("Updating widget #{widget[:identifier]} of type #{widget[:module]} on screen #{screen}")
120
121 begin
122 mod = Widgets.const_get(widget[:module]).new(widget.merge(:iteration => iteration))
123
124 if widget[:properties].empty?
125 threads << Thread.new(screen, statusbar, widget, mod) do |screen, statusbar, widget, mod|
126 @awesome.widget_tell(screen, statusbar, widget[:identifier], widget[:property], mod.formatize)
127 end
128 end
129
130 widget[:properties].each do |property, format|
131 threads << Thread.new(screen, statusbar, widget, property, mod, format) do |screen, statusbar, widget, property, mod, format|
132 @awesome.widget_tell(screen, statusbar, widget[:identifier], property, mod.formatize(format))
133 end
134 end
135
136 rescue WidgetError => e
137 @log.error(widget[:module]) { e.message }
138 end
139
140 threads.each {|t| t.join }
141 end
142
143 def join_threads
144 @threads.each {|t| t.join }
145 end
146 end
147 end
148end
toggle raw diff

lib/amazing/cli/initializers.rb

 
1# Copyright (C) 2008 Dag Odenhall <dag.odenhall@gmail.com>
2# Licensed under the Academic Free License version 3.0
3
4module Amazing
5 class CLI
6 module Initializers
7 private
8
9 def initialize_threads
10 @threads = []
11 end
12
13 def initialize_encoding
14 $KCODE = "utf-8"
15 end
16
17 def initialize_logger
18 @log = Logger.new(STDOUT)
19 end
20
21 def initialize_options
22 @options = Options.new(@args)
23 end
24
25 def initialize_display
26 begin
27 @display = X11::DisplayName.new
28
29 rescue X11::EmptyDisplayName => e
30 @log.warn("#{e.message}, falling back on :0")
31 @display = X11::DisplayName.new(":0")
32
33 rescue X11::InvalidDisplayName => e