Let --update without args update all widgets and exit
[amazing.git] / lib / amazing / cli.rb
bloba74b5ff604887592697f40b00076006ae7b1baaa
1 # Copyright (C) 2008 Dag Odenhall <dag.odenhall@gmail.com>
2 # Licensed under the Academic Free License version 3.0
4 require 'logger'
5 require 'amazing/options'
6 require 'amazing/x11/display_name'
7 require 'yaml'
8 require 'amazing/widget'
9 require 'amazing/proc_file'
10 require 'amazing/widgets'
11 require 'amazing/awesome'
12 require 'timeout'
13 require 'fileutils'
14 require 'thread'
16 module Amazing
18   # Command line interface runner
19   #
20   #   CLI.run(ARGV)
21   class CLI
22     def initialize(args)
23       @args = args
24       @log = Logger.new(STDOUT)
25       @options = Options.new(@args)
26       begin
27         @display = X11::DisplayName.new
28       rescue X11::EmptyDisplayName => e
29         @log.warn("#{e.message}, falling back on :0")
30         @display = X11::DisplayName.new(":0")
31       rescue X11::InvalidDisplayName => e
32         @log.fatal("#{e.message}, exiting")
33         exit 1
34       end
35     end
37     def run
38       trap("SIGINT") do
39         @log.fatal("Received SIGINT, exiting")
40         remove_pid
41         exit
42       end
43       @options.parse
44       show_help if @options[:help]
45       set_loglevel
46       parse_config
47       load_scripts
48       list_widgets if @options[:listwidgets]
49       test_widget if @options[:test]
50       wait_for_sockets
51       @awesome = Awesome.new(@display.display)
52       explicit_updates unless @options[:update] == []
53       save_pid
54       update_non_interval
55       count = 0
56       loop do
57         @config["widgets"].each do |screen, widgets|
58           widgets.each do |widget_name, settings|
59             if settings["every"] && count % settings["every"] == 0
60               update_widget(screen, widget_name)
61             end
62           end
63         end
64         count += 1
65         sleep 1
66       end
67     end
69     private
71     def show_help
72       puts @options.help
73       exit
74     end
76     def set_loglevel
77       begin
78         @log.level = Logger.const_get(@options[:loglevel].upcase)
79       rescue NameError
80         @log.error("Unsupported log level #{@options[:loglevel].inspect}")
81         @log.level = Logger::INFO
82       end
83     end
85     def load_scripts
86       scripts = @options[:include]
87       @config["include"].each do |script|
88         script = "#{File.dirname(@options[:config])}/#{script}" if script[0] != ?/
89         scripts << script
90       end
91       if @options[:autoinclude]
92         scripts << Dir["#{ENV["HOME"]}/.amazing/widgets/*"]
93       end
94       scripts.flatten.each do |script|
95         if File.exist?(script)
96           @log.debug("Loading script #{script.inspect}")
97           begin
98             Widgets.module_eval(File.read(script), script)
99           rescue SyntaxError => e
100             @log.error("Bad syntax in #{script} at line #{e.to_s.scan(/:(\d+)/)}")
101           end
102         else
103           @log.error("No such widget script #{script.inspect}")
104         end
105       end
106     end
108     def list_widgets
109       if @options[:listwidgets] == true
110         longest_widget_name = Widgets.constants.inject {|a,b| a.length > b.length ? a : b }.length
111         Widgets.constants.sort.each do |widget|
112           widget_class = Widgets.const_get(widget)
113           puts "%-#{longest_widget_name}s : %s" % [widget, widget_class.description]
114         end
115       else
116         widget_class = Widgets.const_get(@options[:listwidgets])
117         puts
118         puts "#{@options[:listwidgets]} - #{widget_class.description}"
119         puts
120         dependencies = widget_class.dependencies
121         unless dependencies.empty?
122           longest_dependency_name = dependencies.keys.inject {|a,b| a.to_s.length > b.to_s.length ? a : b }.to_s.length
123           longest_dependency_name = 10 if longest_dependency_name < 10
124           longest_description = dependencies.values.inject {|a,b| a.length > b.length ? a : b }.length
125           longest_description = 11 if longest_description < 11
126           puts " %-#{longest_dependency_name}s | DESCRIPTION" % "DEPENDENCY"
127           puts "-" * (longest_dependency_name + longest_description + 5)
128           dependencies.keys.sort.each do |dependency|
129             puts " %-#{longest_dependency_name}s | #{dependencies[dependency]}" % dependency
130           end
131           puts
132         end
133         options = widget_class.options
134         unless options.empty?
135           longest_option_name = options.keys.inject {|a,b| a.to_s.length > b.to_s.length ? a : b }.to_s.length
136           longest_option_name = 6 if longest_option_name < 6
137           longest_description = options.values.inject {|a,b| a[:description].length > b[:description].length ? a : b }[:description].length
138           longest_description = 11 if longest_description < 11
139           longest_default = options.values.inject {|a,b| a[:default].inspect.length > b[:default].inspect.length ? a : b }[:default].inspect.length
140           longest_default = 7 if longest_default < 7
141           puts " %-#{longest_option_name}s | %-#{longest_description}s | DEFAULT" % ["OPTION", "DESCRIPTION"]
142           puts "-" * (longest_option_name + longest_description + longest_default + 8)
143           options.keys.sort_by {|option| option.to_s }.each do |option|
144             puts " %-#{longest_option_name}s | %-#{longest_description}s | %s" % [option, options[option][:description], options[option][:default].inspect]
145           end
146           puts
147         end
148         fields = widget_class.fields
149         unless fields.empty?
150           longest_field_name = fields.keys.inject {|a,b| a.to_s.length > b.to_s.length ? a : b }.to_s.length
151           longest_field_name = 5 if longest_field_name < 5
152           longest_description = fields.values.inject {|a,b| a[:description].length > b[:description].length ? a : b }[:description].length
153           longest_description = 11 if longest_description < 11
154           longest_default = fields.values.inject {|a,b| a[:default].inspect.length > b[:default].inspect.length ? a : b }[:default].inspect.length
155           longest_default = 7 if longest_default < 7
156           puts " %-#{longest_field_name + 1}s | %-#{longest_description}s | DEFAULT" % ["FIELD", "DESCRIPTION"]
157           puts "-" * (longest_field_name + longest_description + longest_default + 9)
158           fields.keys.sort_by {|field| field.to_s }.each do |field|
159             puts " @%-#{longest_field_name}s | %-#{longest_description}s | %s" % [field, fields[field][:description], fields[field][:default].inspect]
160           end
161           puts
162         end
163       end
164       exit
165     end
167     def test_widget
168       widget = Widgets.const_get(@options[:test])
169       settings = YAML.load("{#{ARGV[0]}}")
170       instance = widget.new("test", settings)
171       longest_field_name = widget.fields.keys.inject {|a,b| a.to_s.length > b.to_s.length ? a : b }.to_s.length
172       widget.fields.keys.each do |field|
173         puts "@%-#{longest_field_name}s = %s" % [field, instance.instance_variable_get("@#{field}".to_sym).inspect]
174       end
175       exit
176     end
178     def parse_config
179       @log.debug("Parsing configuration file")
180       begin
181         @config = YAML.load_file(@options[:config])
182       rescue
183         @log.fatal("Unable to parse configuration file, exiting")
184         exit 1
185       end
186       @config["include"] ||= []
187     end
189     def wait_for_sockets
190       @log.debug("Waiting for awesome control socket for display #{@display.display}")
191       begin
192         Timeout.timeout(30) do
193           sleep 1 until File.exist?("#{ENV["HOME"]}/.awesome_ctl.#{@display.display}")
194           @log.debug("Got socket for display #{@display.display}")
195         end
196       rescue Timeout::Error
197         @log.fatal("Socket for display #{@display.display} not created within 30 seconds, exiting")
198         exit 1
199       end
200     end
202     def explicit_updates
203       @config["widgets"].each do |screen, widgets|
204         widgets.each_key do |widget_name|
205           next unless @options[:update] == :all || @options[:update].include?(widget_name)
206           update_widget(screen, widget_name, false)
207         end
208       end
209       exit
210     end
212     def save_pid
213       path = "#{ENV["HOME"]}/.amazing/pids"
214       FileUtils.makedirs(path)
215       File.open("#{path}/#{@display.display}.pid", "w+") do |f|
216         f.write($$)
217       end
218     end
220     def remove_pid
221       File.delete("#{ENV["HOME"]}/.amazing/pids/#{@display.display}.pid") rescue Errno::ENOENT
222     end
224     def update_non_interval
225       @config["widgets"].each do |screen, widgets|
226         widgets.each do |widget_name, settings|
227           next if settings["every"]
228           update_widget(screen, widget_name)
229         end
230       end
231     end
233     def update_widget(screen, widget_name, threaded=true)
234       settings = @config["widgets"][screen][widget_name]
235       @log.debug("Updating widget #{widget_name} of type #{settings["type"]} on screen #{screen}")
236       update = Proc.new do
237         begin
238           widget = Widgets.const_get(settings["type"]).new(widget_name, settings)
239           @awesome.widget_tell(screen, widget_name, widget.formatize)
240         rescue WidgetError => e
241           @log.error(settings["type"]) { e.message }
242         end
243       end
244       if threaded
245         Thread.new &update
246       else
247         update.call
248       end
249     end
250   end