#-- # rbmh/pager.rb : rbmhshow 0.4.2 # # Copyright (C) 2004--2005 Merlin Hughes # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # #++ # Managed less pager require 'tempfile' require 'merlin/let' require 'rbmh/command' module RBMH class Pager def initialize(context) @context = context @commands = {0 => [RBMH::Command.lookup("exit", "0", context)]} # @less_file @less_prompt @temp_file @exit_code profile_init end # TODO: handle name=nil, handle prompt code ?f___:___. def page(io, message) @message = message dispname = message.display_name.gsub(/\./, '\\.') # other chars too prompt = @less_prompt.gsub(/(^|[^\\])%f/, dispname) less_env = "-RmPm#{prompt}$PM#{prompt}$k#{@less_file}$#{ENV['LESS']}" ENV.let([ 'LESS', less_env ]) { STDIN.let(io) { system "less -" } } @exit_code = $?.exitstatus raise RBMH::Exception, "unknown less exit code - #{less_exit_code}" unless @commands.has_key?(@exit_code) end def execute_exit_commands(message) catch :show do @commands[@exit_code].each { |command| command.execute(message) } return false end return true end private def profile_init profile = @context.profile # I do this in two loops so I can sort the keys, to protect # against the hash ordering changing between invocations actions = {} profile.each(ProfileLessKeyRE) { |key, value| raise RBMH::Exception, "invalid commands - #{value}" unless value =~ CommandsRE commands = value.scan(CommandRE) commands.collect! { |command, parameter| RBMH::Command.lookup(command, parameter, @context) } actions[key] = commands } exit = "@" exits = {} actions.sort.each { |key, commands| exit[0] += 1 exits[key] = exit.dup @commands[exit[0]] = commands } file = profile[ProfileLessFile] if file.nil? @temp_file = Tempfile.new("rbmh-less") @temp_file.close @less_file = @temp_file.path lesskeys(exits) else @less_file = File.expand_path(file) if (not FileTest.exists?(@less_file)) or (profile.mtime > File.stat(@less_file).mtime) STDERR.puts "rbmh: updating #{@less_file}" lesskeys(exits) end end prompt = profile[ProfileLessPrompt] @less_prompt = prompt || LessPrompt end # things will go badly wrong if you change .mh_profile and # its mtime precedes .rbmh_less' because the exit codes will # no longer line up def lesskeys(exits) IO.popen("lesskey -o #{@less_file} -", "w") { | lesskey | exits.each { | key, exit | lesskey.puts "#{key} quit #{exit}" } } exit 1 if $?.exitstatus != 0 end ProfileLessFile = 'rbmh-less-file' ProfileLessPrompt = 'rbmh-less-prompt' ProfileLessKeyRE = /^rbmh-less-key-(\S+)$/ LessPrompt = '%f ?ltlines %lt-%lb?L/%L. : byte % bB?s/%s. .?e(END):?pB%pB\%..%t' # TODO: optional parens, as ruby.. can't express it elegantly ParameterRE = /(?:\((?>\s*)((?:[^\\)]|\\.)*(?:[^ \\)]|\\.))?\s*\))?/ # $1: parameter CommandRE = /\s*(\w+)\s*#{ParameterRE.source}\s*/ # $1: command, $2: parameter CommandsRE = /^#{CommandRE.source}(;#{CommandRE.source})*$/ end # class Pager end # module RBMH