diff --git a/lib/puppet/util/colors.rb b/lib/puppet/util/colors.rb
index 622c3b583..ba8a3933b 100644
--- a/lib/puppet/util/colors.rb
+++ b/lib/puppet/util/colors.rb
@@ -1,175 +1,202 @@
require 'puppet/util/platform'
module Puppet::Util::Colors
BLACK = {:console => "\e[0;30m", :html => "color: #FFA0A0" }
RED = {:console => "\e[0;31m", :html => "color: #FFA0A0" }
GREEN = {:console => "\e[0;32m", :html => "color: #00CD00" }
YELLOW = {:console => "\e[0;33m", :html => "color: #FFFF60" }
BLUE = {:console => "\e[0;34m", :html => "color: #80A0FF" }
MAGENTA = {:console => "\e[0;35m", :html => "color: #FFA500" }
CYAN = {:console => "\e[0;36m", :html => "color: #40FFFF" }
WHITE = {:console => "\e[0;37m", :html => "color: #FFFFFF" }
HBLACK = {:console => "\e[1;30m", :html => "color: #FFA0A0" }
HRED = {:console => "\e[1;31m", :html => "color: #FFA0A0" }
HGREEN = {:console => "\e[1;32m", :html => "color: #00CD00" }
HYELLOW = {:console => "\e[1;33m", :html => "color: #FFFF60" }
HBLUE = {:console => "\e[1;34m", :html => "color: #80A0FF" }
HMAGENTA = {:console => "\e[1;35m", :html => "color: #FFA500" }
HCYAN = {:console => "\e[1;36m", :html => "color: #40FFFF" }
HWHITE = {:console => "\e[1;37m", :html => "color: #FFFFFF" }
BG_RED = {:console => "\e[0;41m", :html => "background: #FFA0A0"}
BG_GREEN = {:console => "\e[0;42m", :html => "background: #00CD00"}
BG_YELLOW = {:console => "\e[0;43m", :html => "background: #FFFF60"}
BG_BLUE = {:console => "\e[0;44m", :html => "background: #80A0FF"}
BG_MAGENTA = {:console => "\e[0;45m", :html => "background: #FFA500"}
BG_CYAN = {:console => "\e[0;46m", :html => "background: #40FFFF"}
BG_WHITE = {:console => "\e[0;47m", :html => "background: #FFFFFF"}
BG_HRED = {:console => "\e[1;41m", :html => "background: #FFA0A0"}
BG_HGREEN = {:console => "\e[1;42m", :html => "background: #00CD00"}
BG_HYELLOW = {:console => "\e[1;43m", :html => "background: #FFFF60"}
BG_HBLUE = {:console => "\e[1;44m", :html => "background: #80A0FF"}
BG_HMAGENTA = {:console => "\e[1;45m", :html => "background: #FFA500"}
BG_HCYAN = {:console => "\e[1;46m", :html => "background: #40FFFF"}
BG_HWHITE = {:console => "\e[1;47m", :html => "background: #FFFFFF"}
RESET = {:console => "\e[0m", :html => "" }
Colormap = {
:debug => WHITE,
:info => GREEN,
:notice => CYAN,
:warning => YELLOW,
:err => HMAGENTA,
:alert => RED,
:emerg => HRED,
:crit => HRED,
:black => BLACK,
:red => RED,
:green => GREEN,
:yellow => YELLOW,
:blue => BLUE,
:magenta => MAGENTA,
:cyan => CYAN,
:white => WHITE,
:hblack => HBLACK,
:hred => HRED,
:hgreen => HGREEN,
:hyellow => HYELLOW,
:hblue => HBLUE,
:hmagenta => HMAGENTA,
:hcyan => HCYAN,
:hwhite => HWHITE,
:bg_red => BG_RED,
:bg_green => BG_GREEN,
:bg_yellow => BG_YELLOW,
:bg_blue => BG_BLUE,
:bg_magenta => BG_MAGENTA,
:bg_cyan => BG_CYAN,
:bg_white => BG_WHITE,
:bg_hred => BG_HRED,
:bg_hgreen => BG_HGREEN,
:bg_hyellow => BG_HYELLOW,
:bg_hblue => BG_HBLUE,
:bg_hmagenta => BG_HMAGENTA,
:bg_hcyan => BG_HCYAN,
:bg_hwhite => BG_HWHITE,
:reset => { :console => "\e[m", :html => "" }
}
# We define console_has_color? at load time since it's checking the
# underlying platform which will not change, and we don't want to perform
# the check every time we use logging
if Puppet::Util::Platform.windows?
# We're on windows, need win32console for color to work
begin
- require 'Win32API'
+ require 'ffi'
require 'win32console'
- require 'puppet/util/windows/string'
# The win32console gem uses ANSI functions for writing to the console
# which doesn't work for unicode strings, e.g. module tool. Ruby 1.9
# does the same thing, but doesn't account for ANSI escape sequences
class WideConsole < Win32::Console
- WriteConsole = Win32API.new( "kernel32", "WriteConsoleW", ['l', 'p', 'l', 'p', 'p'], 'l' )
- WriteConsoleOutputCharacter = Win32API.new( "kernel32", "WriteConsoleOutputCharacterW", ['l', 'p', 'l', 'l', 'p'], 'l' )
+ extend FFI::Library
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms687401(v=vs.85).aspx
+ # BOOL WINAPI WriteConsole(
+ # _In_ HANDLE hConsoleOutput,
+ # _In_ const VOID *lpBuffer,
+ # _In_ DWORD nNumberOfCharsToWrite,
+ # _Out_ LPDWORD lpNumberOfCharsWritten,
+ # _Reserved_ LPVOID lpReserved
+ # );
+ ffi_lib :kernel32
+ attach_function_private :WriteConsoleW,
+ [:handle, :lpcwstr, :dword, :lpdword, :lpvoid], :win32_bool
+
+ # typedef struct _COORD {
+ # SHORT X;
+ # SHORT Y;
+ # } COORD, *PCOORD;
+ class COORD < FFI::Struct
+ layout :X, :short,
+ :Y, :short
+ end
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms687410(v=vs.85).aspx
+ # BOOL WINAPI WriteConsoleOutputCharacter(
+ # _In_ HANDLE hConsoleOutput,
+ # _In_ LPCTSTR lpCharacter,
+ # _In_ DWORD nLength,
+ # _In_ COORD dwWriteCoord,
+ # _Out_ LPDWORD lpNumberOfCharsWritten
+ # );
+ ffi_lib :kernel32
+ attach_function_private :WriteConsoleOutputCharacterW,
+ [:handle, :lpcwstr, :dword, COORD, :lpdword], :win32_bool
def initialize(t = nil)
super(t)
end
def WriteChar(str, col, row)
- dwWriteCoord = (row << 16) + col
- lpNumberOfCharsWritten = ' ' * 4
- utf16, nChars = string_encode(str)
- WriteConsoleOutputCharacter.call(@handle, utf16, nChars, dwWriteCoord, lpNumberOfCharsWritten)
- lpNumberOfCharsWritten.unpack('L')
- end
+ writeCoord = COORD.new()
+ writeCoord[:X] = row
+ writeCoord[:Y] = col
- def Write(str)
- written = 0.chr * 4
- reserved = 0.chr * 4
- utf16, nChars = string_encode(str)
- WriteConsole.call(@handle, utf16, nChars, written, reserved)
+ numberOfCharsWritten_ptr = FFI::MemoryPointer.new(:dword, 1)
+ WriteConsoleOutputCharacterW(@handle, FFI::MemoryPointer.from_string_to_wide_string(str),
+ str.length, writeCoord, numberOfCharsWritten_ptr)
+ numberOfCharsWritten_ptr.read_dword
end
- def string_encode(str)
- wstr = Puppet::Util::Windows::String.wide_string(str)
- [wstr, wstr.length - 1]
+ def Write(str)
+ WriteConsoleW(@handle, FFI::MemoryPointer.from_string_to_wide_string(str),
+ str.length, FFI::MemoryPointer.new(:dword, 1), FFI::MemoryPointer::NULL)
end
end
# Override the win32console's IO class so we can supply
# our own Console class
class WideIO < Win32::Console::ANSI::IO
def initialize(fd_std = :stdout)
super(fd_std)
handle = FD_STD_MAP[fd_std][1]
@Out = WideConsole.new(handle)
end
end
$stdout = WideIO.new(:stdout)
$stderr = WideIO.new(:stderr)
rescue LoadError
def console_has_color?
false
end
else
def console_has_color?
true
end
end
else
# On a posix system we can just enable it
def console_has_color?
true
end
end
def colorize(color, str)
case Puppet[:color]
when true, :ansi, "ansi", "yes"
if console_has_color?
console_color(color, str)
else
str
end
when :html, "html"
html_color(color, str)
else
str
end
end
def console_color(color, str)
Colormap[color][:console] +
str.gsub(RESET[:console], Colormap[color][:console]) +
RESET[:console]
end
def html_color(color, str)
span = '' % Colormap[color][:html]
"#{span}%s" % str.gsub(//, "\\0#{span}")
end
end
diff --git a/lib/puppet/util/windows/api_types.rb b/lib/puppet/util/windows/api_types.rb
index 45f8ef9a1..afa986e0c 100644
--- a/lib/puppet/util/windows/api_types.rb
+++ b/lib/puppet/util/windows/api_types.rb
@@ -1,102 +1,103 @@
require 'ffi'
require 'puppet/util/windows/string'
module Puppet::Util::Windows::APITypes
module ::FFI
WIN32_FALSE = 0
end
module ::FFI::Library
# Wrapper method for attach_function + private
def attach_function_private(*args)
attach_function(*args)
private args[0]
end
end
class ::FFI::Pointer
NULL_HANDLE = 0
end
class ::FFI::MemoryPointer
def self.from_string_to_wide_string(str)
str = Puppet::Util::Windows::String.wide_string(str)
ptr = FFI::MemoryPointer.new(:byte, str.bytesize)
# uchar here is synonymous with byte
ptr.put_array_of_uchar(0, str.bytes.to_a)
ptr
end
def read_win32_bool
# BOOL is always a 32-bit integer in Win32
# some Win32 APIs return 1 for true, while others are non-0
read_int32 != FFI::WIN32_FALSE
end
alias_method :read_dword, :read_uint32
def read_handle
type_size == 4 ? read_uint32 : read_uint64
end
def read_wide_string(char_length)
# char_length is number of wide chars (typically excluding NULLs), *not* bytes
str = get_bytes(0, char_length * 2).force_encoding('UTF-16LE')
str.encode(Encoding.default_external)
end
alias_method :write_dword, :write_uint32
end
# FFI Types
# https://github.com/ffi/ffi/wiki/Types
# Windows - Common Data Types
# http://msdn.microsoft.com/en-us/library/cc230309.aspx
# Windows Data Types
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa383751(v=vs.85).aspx
FFI.typedef :uint16, :word
FFI.typedef :uint32, :dword
# uintptr_t is defined in an FFI conf as platform specific, either
# ulong_long on x64 or just ulong on x86
FFI.typedef :uintptr_t, :handle
# buffer_inout is similar to pointer (platform specific), but optimized for buffers
FFI.typedef :buffer_inout, :lpwstr
# buffer_in is similar to pointer (platform specific), but optimized for CONST read only buffers
FFI.typedef :buffer_in, :lpcwstr
# string is also similar to pointer, but should be used for const char *
# NOTE that this is not wide, useful only for A suffixed functions
FFI.typedef :string, :lpcstr
# pointer in FFI is platform specific
# NOTE: for API calls with reserved lpvoid parameters, pass a FFI::Pointer::NULL
FFI.typedef :pointer, :lpvoid
FFI.typedef :pointer, :lpword
FFI.typedef :pointer, :lpdword
FFI.typedef :pointer, :pdword
FFI.typedef :pointer, :phandle
FFI.typedef :pointer, :ulong_ptr
FFI.typedef :pointer, :pbool
# any time LONG / ULONG is in a win32 API definition DO NOT USE platform specific width
# which is what FFI uses by default
# instead create new aliases for these very special cases
# NOTE: not a good idea to redefine FFI :ulong since other typedefs may rely on it
FFI.typedef :uint32, :win32_ulong
FFI.typedef :int32, :win32_long
# FFI bool can be only 1 byte at times,
# Win32 BOOL is a signed int, and is always 4 bytes, even on x64
# http://blogs.msdn.com/b/oldnewthing/archive/2011/03/28/10146459.aspx
FFI.typedef :int32, :win32_bool
- # NOTE: FFI already defines ushort as a 16-bit unsigned like this:
+ # NOTE: FFI already defines (u)short as a 16-bit (un)signed like this:
# FFI.typedef :uint16, :ushort
+ # FFI.typedef :int16, :short
# 8 bits per byte
FFI.typedef :uchar, :byte
end
diff --git a/spec/unit/util/colors_spec.rb b/spec/unit/util/colors_spec.rb
index 7407b628b..f114894da 100755
--- a/spec/unit/util/colors_spec.rb
+++ b/spec/unit/util/colors_spec.rb
@@ -1,83 +1,69 @@
#!/usr/bin/env ruby
require 'spec_helper'
describe Puppet::Util::Colors do
include Puppet::Util::Colors
let (:message) { 'a message' }
let (:color) { :black }
let (:subject) { self }
describe ".console_color" do
it { should respond_to :console_color }
it "should generate ANSI escape sequences" do
subject.console_color(color, message).should == "\e[0;30m#{message}\e[0m"
end
end
describe ".html_color" do
it { should respond_to :html_color }
it "should generate an HTML span element and style attribute" do
subject.html_color(color, message).should =~ /#{message}<\/span>/
end
end
describe ".colorize" do
it { should respond_to :colorize }
context "ansicolor supported" do
before :each do
subject.stubs(:console_has_color?).returns(true)
end
it "should colorize console output" do
Puppet[:color] = true
subject.expects(:console_color).with(color, message)
subject.colorize(:black, message)
end
it "should not colorize unknown color schemes" do
Puppet[:color] = :thisisanunknownscheme
subject.colorize(:black, message).should == message
end
end
context "ansicolor not supported" do
before :each do
subject.stubs(:console_has_color?).returns(false)
end
it "should not colorize console output" do
Puppet[:color] = true
subject.expects(:console_color).never
subject.colorize(:black, message).should == message
end
it "should colorize html output" do
Puppet[:color] = :html
subject.expects(:html_color).with(color, message)
subject.colorize(color, message)
end
end
end
-
- describe "on Windows", :if => Puppet.features.microsoft_windows? do
- it "expects a trailing embedded NULL character in the wide string" do
- message = "hello"
-
- console = Puppet::Util::Colors::WideConsole.new
- wstr, nchars = console.string_encode(message)
-
- expect(nchars).to eq(message.length)
-
- expect(wstr.length).to eq(nchars + 1)
- expect(wstr[-1].ord).to be_zero
- end
- end
end