Inline YAML options for --test-widget
[amazing.git] / lib / amazing / cli.rb
blob4919b4a60925dfa72e77b1ce0481bb0df4c9fa81
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].empty?
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       longest_widget_name = Widgets.constants.inject {|a,b| a.length > b.length ? a : b }.length
110       Widgets.constants.each do |widget|
111         widget_class = Widgets.const_get(widget)
112         puts "%-#{longest_widget_name}s : %s" % [widget, widget_class.description]
113         widget_class.dependencies.each do |name, description|
114           puts "#{" " * longest_widget_name}     require '#{name}' # #{description}"
115         end
116         widget_class.options.each do |name, info|
117           puts "#{" " * longest_widget_name}     #{name}: <#{info[:description]}> || #{info[:default].inspect}"
118         end
119         widget_class.fields.each do |name, info|
120           puts "#{" " * longest_widget_name}     @#{name} = <#{info[:description]}> || #{info[:default].inspect}"
121         end
122         puts
123       end
124       exit
125     end
127     def test_widget
128       widget = Widgets.const_get(@options[:test])
129       settings = YAML.load("{#{ARGV[0]}}")
130       instance = widget.new("test", settings)
131       longest_field_name = widget.fields.keys.inject {|a,b| a.to_s.length > b.to_s.length ? a : b }.to_s.length
132       widget.fields.keys.each do |field|
133         puts "@%-#{longest_field_name}s = %s" % [field, instance.instance_variable_get("@#{field}".to_sym).inspect]
134       end
135       exit
136     end
138     def parse_config
139       @log.debug("Parsing configuration file")
140       begin
141         @config = YAML.load_file(@options[:config])
142       rescue
143         @log.fatal("Unable to parse configuration file, exiting")
144         exit 1
145       end
146       @config["include"] ||= []
147     end
149     def wait_for_sockets
150       @log.debug("Waiting for awesome control socket for display #{@display.display}")
151       begin
152         Timeout.timeout(30) do
153           sleep 1 until File.exist?("#{ENV["HOME"]}/.awesome_ctl.#{@display.display}")
154           @log.debug("Got socket for display #{@display.display}")
155         end
156       rescue Timeout::Error
157         @log.fatal("Socket for display #{@display.display} not created within 30 seconds, exiting")
158         exit 1
159       end
160     end
162     def explicit_updates
163       @config["widgets"].each do |screen, widgets|
164         widgets.each_key do |widget_name|
165           next unless @options[:update].include? widget_name
166           update_widget(screen, widget_name, false)
167         end
168       end
169       exit
170     end
172     def save_pid
173       path = "#{ENV["HOME"]}/.amazing/pids"
174       FileUtils.makedirs(path)
175       File.open("#{path}/#{@display.display}.pid", "w+") do |f|
176         f.write($$)
177       end
178     end
180     def remove_pid
181       File.delete("#{ENV["HOME"]}/.amazing/pids/#{@display.display}.pid") rescue Errno::ENOENT
182     end
184     def update_non_interval
185       @config["widgets"].each do |screen, widgets|
186         widgets.each do |widget_name, settings|
187           next if settings["every"]
188           update_widget(screen, widget_name)
189         end
190       end
191     end
193     def update_widget(screen, widget_name, threaded=true)
194       settings = @config["widgets"][screen][widget_name]
195       begin
196         @log.debug("Updating widget #{widget_name} of type #{settings["type"]} on screen #{screen}")
197         update = Proc.new do
198           widget = Widgets.const_get(settings["type"]).new(widget_name, settings)
199           @awesome.widget_tell(screen, widget_name, widget.formatize)
200         end
201         if threaded
202           Thread.new &update
203         else
204           update.call
205         end
206       rescue WidgetError => e
207         @log.error(settings["type"]) { e.message }
208       end
209     end
210   end