lib/maxcube/network/tcp/client/commands.rb in maxcube-client-0.4.1 vs lib/maxcube/network/tcp/client/commands.rb in maxcube-client-0.5.0

- old
+ new

@@ -1,285 +1,352 @@ module MaxCube module Network module TCP class Client - private + # Provides handling of concrete commands from shell command line. + module Commands + private - COMMANDS = { - 'usage' => %w[? h help], - 'data' => %w[B buffer d], - 'history' => %w[H hist], - 'clear' => %w[C], - 'dump' => %w[D], - 'list' => %w[l], - 'config' => %w[c], - 'send' => %w[cmd s set], - 'pair' => %w[n], - 'ntp' => %w[N f], - 'url' => %w[U u], - 'wake' => %w[w z], - 'metadata' => %w[m meta], - 'delete' => %w[del], - 'reset' => %w[], - 'verbose' => %w[V], - 'save' => %w[S], - 'load' => %w[L], - 'persist' => %w[P], - 'quit' => %w[q], - }.freeze + # List of commands and their aliases. + COMMANDS = { + 'usage' => %w[? h help], + 'data' => %w[B buffer d], + 'history' => %w[H hist], + 'clear' => %w[C], + 'dump' => %w[D], + 'list' => %w[l], + 'config' => %w[c], + 'send' => %w[cmd s set], + 'pair' => %w[n], + 'ntp' => %w[N f], + 'url' => %w[U u], + 'wake' => %w[w z], + 'metadata' => %w[m meta], + 'delete' => %w[del], + 'reset' => %w[], + 'verbose' => %w[V], + 'save' => %w[S], + 'load' => %w[L], + 'persist' => %w[P], + 'quit' => %w[q], + }.freeze - def usage_line(command, args, description, - message = nil, response = nil) - cmds_str = (COMMANDS[command].dup << command).join('|') - cmds_str << ' ' << args unless args.empty? + # Returns usage message for a single command. + # @param command [String] command key from {COMMANDS}. + # @param args [String] arguments accepted by command. + # @param description [String] description of command. + # @param message [String, nil] optional message type + # that is about to be sent by the command. + # @param response [String, nil] optional message type + # of expected response to this message. + # @return [String] usage message for +command+. + def usage_cmd(command, args, description, + message = nil, response = nil) + cmds_str = (COMMANDS[command].dup << command).join('|') + cmds_str << ' ' << args unless args.empty? - description, *rest = description.split("\n") - rest << "[#{message} message]" if message - rest << "[#{response} response]" if response - rest = if rest.empty? - '' - else - rest.map { |s| ' ' * 52 + s }.join("\n") << "\n" - end + description, *rest = description.split("\n") + rest << "[#{message} message]" if message + rest << "[#{response} response]" if response + rest = if rest.empty? + '' + else + rest.map { |s| ' ' * 52 + s }.join("\n") << "\n" + end - ' ' << cmds_str << ' ' * (48 - cmds_str.size) << - description << "\n" << rest - end + ' ' << cmds_str << ' ' * (48 - cmds_str.size) << + description << "\n" << rest + end - def cmd_usage - puts "\nUSAGE: <command> [<arguments...>]\nCOMMADS:\n" << - usage_line('usage', '', - 'Prints this message') << - usage_line('data', '', - 'Lists buffered received data (hashes)') << - usage_line('history', '', - 'Lists all received data incl. the cleared') << - usage_line('clear', '', - "Clears collected data\n" \ - '(resp. moves it to history)') << - usage_line('dump', '', - "Shortcut for 'data' + 'clear'") << - usage_line('list', '', - 'Requests for new list of devices', 'l', 'L') << - usage_line('config', '', - 'Requests for configuration message', 'c', 'C') << - usage_line('send', '{}', - 'Sends settings to connected devices', - 's', 'S') << - usage_line('pair', '{<timeout>}', - 'Sets device into pairing mode' \ - " with optional timeout\n" \ - '(request for a new device)', 'n', 'N') << - usage_line('ntp', '{<NTP servers...>}', - 'Requests for NTP servers' \ - ' and optionally updates them', - 'f', 'F') << - usage_line('url', '{<URL> <port>}', - 'Configures Cube\'s portal URL', 'u') << - usage_line('wake', '{<time> <scope> [<ID>]}', - 'Wake-ups the Cube', - 'z', 'A') << - usage_line('metadata', '{}', - 'Serializes metadata for the Cube', - 'm', 'M') << - usage_line('delete', '{<count> <force> <RF addresses...>}', - 'Deletes one or more devices from the Cube (!)', - 't', 'A') << - usage_line('reset', '', - 'Requests for factory reset (!)', 'a', 'A') << - usage_line('verbose', '', - "Toggles verbose mode (whether is incoming data\n" \ - 'printed immediately or is not printed)') << - usage_line('save', '[a|A|all]', - "Saves buffered [all] received and sent data\n" \ - "into files at '#{@save_data_dir}'") << - usage_line('load', '[<path>]', - 'Loads first hash from YAML file' \ - " to internal variable\n" \ - "-> to pass data with outgoing message\n" \ - 'If path is relative,' \ - " it looks in '#{@load_data_dir}'\n" \ - "(loads previous valid hash if no file given)\n" \ - '(command can be combined' \ - " using '#{ARGS_FROM_HASH}'\n" \ - " with other commands which have '{}' arguments)") << - usage_line('persist', '', - 'Toggles persistent mode' \ - "(whether is internal hash\n" \ - 'not invalidated after use)') << - usage_line('quit', '', - "Shuts the client down gracefully\n" \ - '(SIGINT and EOF also work)', 'q') << - "\n[<arg>] means optional argument <arg>" \ - "\n[<args...>] means multiple arguments <args...> or none" \ - "\n (<args...> requires at least one)" \ - "\n{<arg>} means that either <arg>" \ - " or '#{ARGS_FROM_HASH}' is expected" \ - "\n (when '#{ARGS_FROM_HASH}' specified as first argument," \ - ' internal hash is used' \ - "\n -> 'load' command is called with rest arguments)" \ - "\n ({} means that only internal hash can be used," \ - "\n '#{ARGS_FROM_HASH}' is not necessary in this case)" - end + # Prints usage message composed mainly + # from particular {#usage_cmd} calls. + def cmd_usage + puts "\nUSAGE: <command> [<arguments...>]\nCOMMADS:\n" << + usage_cmd('usage', '', + 'Prints this message') << + usage_cmd('data', '', + 'Lists buffered received data (hashes)') << + usage_cmd('history', '', + 'Lists all received data incl. the cleared') << + usage_cmd('clear', '', + "Clears collected data\n" \ + '(resp. moves it to history)') << + usage_cmd('dump', '', + "Shortcut for 'data' + 'clear'") << + usage_cmd('list', '', + 'Requests for new list of devices', 'l', 'L') << + usage_cmd('config', '', + 'Requests for configuration message', 'c', 'C') << + usage_cmd('send', '{}', + 'Sends settings to connected devices', + 's', 'S') << + usage_cmd('pair', '{<timeout>}', + 'Sets device into pairing mode' \ + " with optional timeout\n" \ + '(request for a new device)', 'n', 'N') << + usage_cmd('ntp', '{<NTP servers...>}', + 'Requests for NTP servers' \ + ' and optionally updates them', + 'f', 'F') << + usage_cmd('url', '{<URL> <port>}', + 'Configures Cube\'s portal URL', 'u') << + usage_cmd('wake', '{<time> <scope> [<ID>]}', + 'Wake-ups the Cube', + 'z', 'A') << + usage_cmd('metadata', '{}', + 'Serializes metadata for the Cube', + 'm', 'M') << + usage_cmd('delete', '{<count> <force> <RF addresses...>}', + 'Deletes one or more devices from the Cube (!)', + 't', 'A') << + usage_cmd('reset', '', + 'Requests for factory reset (!)', 'a', 'A') << + usage_cmd('verbose', '', + "Toggles verbose mode (whether is incoming data\n" \ + 'printed immediately or is not printed)') << + usage_cmd('save', '[a|A|all]', + "Saves buffered [all] received and sent data\n" \ + "into files at '#{@save_data_dir}'") << + usage_cmd('load', '[<path>]', + 'Loads first hash from YAML file' \ + " to internal variable\n" \ + "-> to pass data with outgoing message\n" \ + 'If path is relative,' \ + " it looks in '#{@load_data_dir}'\n" \ + "(loads previous valid hash if no file given)\n" \ + '(command can be combined' \ + " using '#{ARGS_FROM_HASH}'\n" \ + ' with other commands' \ + " which have '{}' arguments)") << + usage_cmd('persist', '', + 'Toggles persistent mode' \ + "(whether is internal hash\n" \ + 'not invalidated after use)') << + usage_cmd('quit', '', + "Shuts the client down gracefully\n" \ + '(SIGINT and EOF also work)', 'q') << + "\n[<arg>] means optional argument <arg>" \ + "\n[<args...>] means multiple arguments <args...> or none" \ + "\n (<args...> requires at least one)" \ + "\n{<arg>} means that either <arg>" \ + " or '#{ARGS_FROM_HASH}' is expected" \ + "\n (when '#{ARGS_FROM_HASH}' specified as first argument," \ + ' internal hash is used' \ + "\n -> 'load' command is called with rest arguments)" \ + "\n ({} means that only internal hash can be used," \ + "\n '#{ARGS_FROM_HASH}' is not necessary in this case)" + end - def list_hashes(history) - buffer(:recv, :hashes, history).each_with_index do |h, i| - puts "<#{i + 1}>" - print_hash(h) - puts + # Displays received hashes optionally with history. + # Calls {#buffer}. + # @param history [Boolean] whether to include history. + def list_hashes(history) + buffer(:recv, :hashes, history).each_with_index do |h, i| + puts "<#{i + 1}>" + print_hash(h) + puts + end end - end - def cmd_data - list_hashes(false) - end + # Calls {#list_hashes} without history. + def cmd_data + list_hashes(false) + end - def cmd_history - list_hashes(true) - end + # Calls {#list_hashes} with history. + def cmd_history + list_hashes(true) + end - def cmd_clear - %i[data hashes].each do |sym| - @history[:recv][sym] += @buffer[:recv][sym] - @buffer[:recv][sym].clear + # Clears all buffered received data + # (i.e. moves them to history). + def cmd_clear + %i[data hashes].each do |sym| + @history[:recv][sym] += @buffer[:recv][sym] + @buffer[:recv][sym].clear + end end - end - def cmd_dump - cmd_data - cmd_clear - end + # Calls {#cmd_data} and {#cmd_clear}. + def cmd_dump + cmd_data + cmd_clear + end - def cmd_list - send_msg('l') - end + # Calls {#send_msg} with message type 'l' + # (device list request). + def cmd_list + send_msg('l') + end - def cmd_config - send_msg('c') - end + # Calls {#send_msg} with message type 'c' + # (configuration message request). + def cmd_config + send_msg('c') + end - def cmd_send(*args) - send_msg('s', *args, load_only: true) - end + # Calls {#send_msg} with message type 's' + # (send command message) + # and +args+ and +load_only+ option. + # @param args [Array<String>] arguments from command line. + def cmd_send(*args) + send_msg('s', *args, load_only: true) + end - def cmd_pair(*args) - send_msg('n', *args) - end + # Calls {#send_msg} with message type 'n' + # (new device request) + # and +args+. + # @param args [Array<String>] arguments from command line. + def cmd_pair(*args) + send_msg('n', *args) + end - def cmd_url(*args) - send_msg('u', *args) - end + # Calls {#send_msg} with message type 'u' + # (set portal URL message) + # and +args+. + # @param args [Array<String>] arguments from command line. + def cmd_url(*args) + send_msg('u', *args) + end - def cmd_ntp(*args) - send_msg('f', *args, last_array: true) - end + # Calls {#send_msg} with message type 'f' + # (NTP servers message) + # and +args+ and +last_array+ option. + # @param args [Array<String>] arguments from command line. + def cmd_ntp(*args) + send_msg('f', *args, last_array: true) + end - def cmd_wake(*args) - send_msg('z', *args) - end + # Calls {#send_msg} with message type 'z' + # (wake-up message) + # and +args+. + # @param args [Array<String>] arguments from command line. + def cmd_wake(*args) + send_msg('z', *args) + end - def cmd_metadata(*args) - send_msg('m', *args, load_only: true) - end + # Calls {#send_msg} with message type 'm' + # (metadata message) + # and +args+ and +load_only+ option. + # @param args [Array<String>] arguments from command line. + def cmd_metadata(*args) + send_msg('m', *args, load_only: true) + end - def cmd_delete(*args) - send_msg('t', *args, last_array: true, array_nonempty: true) - end + # Calls {#send_msg} with message type 't' + # (delete a device request) + # and +args+, and +last_array+ and +array_nonempty+ options. + # @param args [Array<String>] arguments from command line. + def cmd_delete(*args) + send_msg('t', *args, last_array: true, array_nonempty: true) + end - def cmd_reset - send_msg('a') - end + # Calls {#send_msg} with message type 'a' + # (factory reset request). + def cmd_reset + send_msg('a') + end - def toggle(name, flag) - puts "#{name}: #{flag} -> #{!flag}" - !flag - end + def cmd_save(what = nil) + buffer = !what + all = %w[a A all].include?(what) + unless all || buffer + puts "Unrecognized argument: '#{what}'" + return + end - def cmd_verbose - @verbose = toggle('verbose', @verbose) - end + dir = @save_data_dir + Time.now.strftime('%Y%m%d-%H%M') + dir.mkpath - def cmd_save(what = nil) - buffer = !what - all = %w[a A all].include?(what) - unless all || buffer - puts "Unrecognized argument: '#{what}'" - return - end + %i[recv sent].each do |sym| + data_fn = dir + (sym.to_s << '.data') + File.open(data_fn, 'w') do |f| + f.puts(buffer(sym, :data, all).join) + end - dir = @save_data_dir + Time.now.strftime('%Y%m%d-%H%M') - dir.mkpath - - %i[recv sent].each do |sym| - data_fn = dir + (sym.to_s << '.data') - File.open(data_fn, 'w') do |f| - f.puts(buffer(sym, :data, all).join) + hashes_fn = dir + (sym.to_s << '.yaml') + File.open(hashes_fn, 'w') do |f| + buffer(sym, :hashes, all).to_yaml(f) + end end - hashes_fn = dir + (sym.to_s << '.yaml') - File.open(hashes_fn, 'w') do |f| - buffer(sym, :hashes, all).to_yaml(f) - end + which = buffer ? 'Buffered' : 'All' + puts "#{which} received and sent raw data and hashes" \ + " saved into '#{dir}'" + rescue SystemCallError => e + puts "Files could not been saved:\n#{e}" end - which = buffer ? 'Buffered' : 'All' - puts "#{which} received and sent raw data and hashes" \ - " saved into '#{dir}'" - rescue SystemCallError => e - puts "Files could not been saved:\n#{e}" - end + def parse_hash(path) + unless path.file? && path.readable? + return puts "File is not readable: '#{path}'" + end - def parse_hash(path) - unless path.file? && path.readable? - return puts "File is not readable: '#{path}'" + hash = YAML.load_file(path) + hash = hash.first while hash.is_a?(Array) + raise YAML::SyntaxError unless hash.is_a?(Hash) + hash + rescue YAML::SyntaxError => e + puts "File '#{path}' does not contain proper YAML hash", e end - hash = YAML.load_file(path) - hash = hash.first while hash.is_a?(Array) - raise YAML::SyntaxError unless hash.is_a?(Hash) - hash - rescue YAML::SyntaxError => e - puts "File '#{path}' does not contain proper YAML hash", e - end + def load_hash(path = nil) + if path + path = Pathname.new(path) + path = @load_data_dir + path if path.relative? + return parse_hash(path) + end + return @hash if @hash && @hash_set - def load_hash(path = nil) - if path - path = Pathname.new(path) - path = @load_data_dir + path if path.relative? - return parse_hash(path) + if @hash + puts 'Internal hash is not set' + else + puts 'No internal hash loaded yet' + cmd_usage + end end - return @hash if @hash && @hash_set - if @hash - puts 'Internal hash is not set' - else - puts 'No internal hash loaded yet' - cmd_usage + def assign_hash(hash) + valid_hash = !hash.nil? + @hash = hash if valid_hash + @hash_set |= valid_hash + valid_hash end - end - def assign_hash(hash) - valid_hash = !hash.nil? - @hash = hash if valid_hash - @hash_set |= valid_hash - valid_hash - end + def cmd_load(path = nil) + hash = load_hash(path) + return false unless assign_hash(hash) + print_hash(hash) + true + end - def cmd_load(path = nil) - hash = load_hash(path) - return false unless assign_hash(hash) - print_hash(hash) - true - end + # Helper method that 'toggles' boolean value and prints context info. + # (It actually does not modify +flag+.) + # @param name [String] variable name. + # @param flag [Boolean] boolean value to be 'toggled'. + # @return [Boolean] negated value of +flag+. + def toggle(name, flag) + puts "#{name}: #{flag} -> #{!flag}" + !flag + end - def cmd_persist - @persist = toggle('persist', @persist) - @hash_set = @persist if @hash - end + # Calls {#toggle} with verbose mode variable. + def cmd_verbose + @verbose = toggle('verbose', @verbose) + end - def cmd_quit - raise Interrupt + # Calls {#toggle} with persist mode variable. + def cmd_persist + @persist = toggle('persist', @persist) + @hash_set = @persist if @hash + end + + # Manages to close the client gracefully + # (it should lead to {#close}). + def cmd_quit + raise Interrupt + end end end end end end