Typo in expand_path in Maildir
[amazing.git] / lib / amazing / cli.rb
blob7f7e4fab59c8070b77e9a82903e96beddb789f21
1 # Copyright 2008 Dag Odenhall <dag.odenhall@gmail.com>
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 #    http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
15 require 'amazing'
16 require 'fileutils'
17 require 'logger'
18 require 'thread'
19 require 'timeout'
20 require 'yaml'
22 module Amazing
24   # Command line interface runner
25   #
26   #   CLI.run(ARGV)
27   class CLI
28     def initialize(args)
29       $KCODE = "utf-8"
30       @args = args
31       @log = Logger.new(STDOUT)
32       @options = Options.new(@args)
33       begin
34         @display = X11::DisplayName.new
35       rescue X11::EmptyDisplayName => e
36         @log.warn("#{e.message}, falling back on :0")
37         @display = X11::DisplayName.new(":0")
38       rescue X11::InvalidDisplayName => e
39         @log.fatal("#{e.message}, exiting")
40         exit 1
41       end
42     end
44     def run
45       trap("SIGINT") do
46         @log.fatal("Received SIGINT, exiting")
47         remove_pid
48         exit
49       end
50       @options.parse
51       show_help if @options[:help]
52       set_loglevel
53       stop_process(true) if @options[:stop]
54       parse_config
55       load_scripts
56       list_widgets if @options[:listwidgets]
57       test_widget if @options[:test]
58       wait_for_sockets
59       @awesome = Awesome.new(@display.display)
60       explicit_updates unless @options[:update] == []
61       stop_process
62       save_pid
63       update_non_interval
64       count = 0
65       loop do
66         @config["awesome"].each do |screen, widgets|
67           widgets.each do |widget_name, settings|
68             if settings["interval"] && count % settings["interval"] == 0
69               update_widget(screen, widget_name)
70             end
71           end
72         end
73         count += 1
74         sleep 1
75       end
76     end
78     private
80     def show_help
81       puts @options.help
82       exit
83     end
85     def set_loglevel
86       begin
87         @log.level = Logger.const_get(@options[:loglevel].upcase)
88       rescue NameError
89         @log.error("Unsupported log level #{@options[:loglevel].inspect}")
90         @log.level = Logger::INFO
91       end
92     end
94     def stop_process(quit=false)
95       begin
96         Process.kill("SIGINT", File.read("#{ENV["HOME"]}/.amazing/pids/#{@display.display}.pid").to_i) 
97         @log.warn("Killed older process") unless quit
98       rescue
99       end
100       exit if quit
101     end
103     def load_scripts
104       scripts = @options[:include]
105       @config["include"].each do |script|
106         script = File.expand_path(script, File.dirname(@options[:config]))
107         scripts << script
108       end
109       if @options[:autoinclude]
110         scripts << Dir["#{ENV["HOME"]}/.amazing/widgets/*"]
111       end
112       scripts.flatten.each do |script|
113         if File.exist?(script)
114           @log.debug("Loading script #{script.inspect}")
115           begin
116             Widgets.module_eval(File.read(script), script)
117           rescue SyntaxError => e
118             @log.error("Bad syntax in #{script} at line #{e.to_s.scan(/:(\d+)/)}")
119           end
120         else
121           @log.error("No such widget script #{script.inspect}")
122         end
123       end
124     end
126     def list_widgets
127       if @options[:listwidgets] == true
128         longest_widget_name = Widgets.constants.inject {|a,b| a.length > b.length ? a : b }.length
129         Widgets.constants.sort.each do |widget|
130           widget_class = Widgets.const_get(widget)
131           puts "%-#{longest_widget_name}s : %s" % [widget, widget_class.description]
132         end
133       else
134         widget_class = Widgets.const_get(@options[:listwidgets].camel_case)
135         puts
136         puts "#{@options[:listwidgets].camel_case} - #{widget_class.description}"
137         puts
138         dependencies = widget_class.dependencies
139         unless dependencies.empty?
140           longest_dependency_name = dependencies.keys.inject {|a,b| a.to_s.length > b.to_s.length ? a : b }.to_s.length
141           longest_dependency_name = 10 if longest_dependency_name < 10
142           longest_description = dependencies.values.inject {|a,b| a.length > b.length ? a : b }.length
143           longest_description = 11 if longest_description < 11
144           puts " %-#{longest_dependency_name}s | DESCRIPTION" % "DEPENDENCY"
145           puts "-" * (longest_dependency_name + longest_description + 5)
146           dependencies.keys.sort.each do |dependency|
147             puts " %-#{longest_dependency_name}s | #{dependencies[dependency]}" % dependency
148           end
149           puts
150         end
151         options = widget_class.options
152         unless options.empty?
153           longest_option_name = options.keys.inject {|a,b| a.to_s.length > b.to_s.length ? a : b }.to_s.length
154           longest_option_name = 6 if longest_option_name < 6
155           longest_description = options.values.inject {|a,b| a[:description].length > b[:description].length ? a : b }[:description].length
156           longest_description = 11 if longest_description < 11
157           longest_default = options.values.inject {|a,b| a[:default].inspect.length > b[:default].inspect.length ? a : b }[:default].inspect.length
158           longest_default = 7 if longest_default < 7
159           puts " %-#{longest_option_name}s | %-#{longest_description}s | DEFAULT" % ["OPTION", "DESCRIPTION"]
160           puts "-" * (longest_option_name + longest_description + longest_default + 8)
161           options.keys.sort_by {|option| option.to_s }.each do |option|
162             puts " %-#{longest_option_name}s | %-#{longest_description}s | %s" % [option, options[option][:description], options[option][:default].inspect]
163           end
164           puts
165         end
166         fields = widget_class.fields
167         unless fields.empty?
168           longest_field_name = fields.keys.inject {|a,b| a.to_s.length > b.to_s.length ? a : b }.to_s.length
169           longest_field_name = 5 if longest_field_name < 5
170           longest_description = fields.values.inject {|a,b| a[:description].length > b[:description].length ? a : b }[:description].length
171           longest_description = 11 if longest_description < 11
172           longest_default = fields.values.inject {|a,b| a[:default].inspect.length > b[:default].inspect.length ? a : b }[:default].inspect.length
173           longest_default = 7 if longest_default < 7
174           puts " %-#{longest_field_name + 1}s | %-#{longest_description}s | DEFAULT" % ["FIELD", "DESCRIPTION"]
175           puts "-" * (longest_field_name + longest_description + longest_default + 9)
176           fields.keys.sort_by {|field| field.to_s }.each do |field|
177             puts " @%-#{longest_field_name}s | %-#{longest_description}s | %s" % [field, fields[field][:description], fields[field][:default].inspect]
178           end
179           puts
180         end
181       end
182       exit
183     end
185     def test_widget
186       widget = Widgets.const_get(@options[:test].camel_case)
187       settings = YAML.load("{#{ARGV[0]}}")
188       instance = widget.new("test", settings)
189       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
190       puts "@%-#{longest_field_name}s = %s" % [:default, instance.instance_variable_get(:@default).inspect]
191       widget.fields.keys.sort_by {|field| field.to_s }.each do |field|
192         puts "@%-#{longest_field_name}s = %s" % [field, instance.instance_variable_get("@#{field}".to_sym).inspect]
193       end
194       exit
195     end
197     def parse_config
198       @log.debug("Parsing configuration file")
199       begin
200         @config = YAML.load_file(@options[:config])
201       rescue
202         @log.fatal("Unable to parse configuration file, exiting")
203         exit 1
204       end
205       @config["include"] ||= []
206     end
208     def wait_for_sockets
209       @log.debug("Waiting for awesome control socket for display #{@display.display}")
210       begin
211         Timeout.timeout(30) do
212           sleep 1 until File.exist?("#{ENV["HOME"]}/.awesome_ctl.#{@display.display}")
213           @log.debug("Got socket for display #{@display.display}")
214         end
215       rescue Timeout::Error
216         @log.fatal("Socket for display #{@display.display} not created within 30 seconds, exiting")
217         exit 1
218       end
219     end
221     def explicit_updates
222       @config["awesome"].each do |screen, widgets|
223         widgets.each_key do |widget_name|
224           next unless @options[:update] == :all || @options[:update].include?(widget_name)
225           update_widget(screen, widget_name, false)
226         end
227       end
228       exit
229     end
231     def save_pid
232       path = "#{ENV["HOME"]}/.amazing/pids"
233       FileUtils.makedirs(path)
234       File.open("#{path}/#{@display.display}.pid", "w+") do |f|
235         f.write($$)
236       end
237     end
239     def remove_pid
240       File.delete("#{ENV["HOME"]}/.amazing/pids/#{@display.display}.pid") rescue Errno::ENOENT
241     end
243     def update_non_interval
244       @config["awesome"].each do |screen, widgets|
245         widgets.each do |widget_name, settings|
246           next if settings["interval"]
247           update_widget(screen, widget_name)
248         end
249       end
250     end
252     def update_widget(screen, widget_name, threaded=true)
253       settings = @config["awesome"][screen][widget_name]
254       type = settings["type"].camel_case
255       @log.debug("Updating widget #{widget_name} of type #{type} on screen #{screen}")
256       update = Proc.new do
257         begin
258           widget = Widgets.const_get(type).new(widget_name, settings)
259           @awesome.widget_tell(screen, widget_name, widget.formatize)
260         rescue WidgetError => e
261           @log.error(type) { e.message }
262         end
263       end
264       if threaded
265         Thread.new &update
266       else
267         update.call
268       end
269     end
270   end