win 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 arvicco
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,149 @@
1
+ = win
2
+
3
+ by: Arvicco
4
+ url: http://github.com/arvicco/win_gui
5
+
6
+ == DESCRIPTION
7
+
8
+ A collection of Windows functions predefined for you using FFI. In addition to
9
+ straightforward (CamelCase) API wrappers, it also strives to provide more Ruby-like
10
+ snake_case methods that take a minimum of arguments with sensible defaults and have
11
+ sensible return values (false/true instead of 0/nonzero for test functions, etc).
12
+
13
+ Windows functions are grouped by topics and defined in separate namespaces (modules),
14
+ that also contain related constants and convenience methods. For example, win/dde.rb
15
+ file contains functions related to DDE protocol, such as DdeInitialize() as well as
16
+ constants such as DMLERR_NO_ERROR, APPCLASS_STANDARD, etc.
17
+
18
+ It is still work in progress, only a small number of Windows API functions wrapped so far...
19
+
20
+ == SUMMARY
21
+
22
+ So you want to write a simple program that makes some Windows API function calls.
23
+ You searched MSDN high and low and you now know exactly what functions you need.
24
+ All you want is just putting those function calls into your Ruby code without too
25
+ much pain. You'd love this to be more or less natural extension of your Ruby code,
26
+ preferably not turning your code base into an ugly spaghetty of CamelCase calls,
27
+ String/Array pack/unpack gymnastics, buffer/pointer allocations, extracting return
28
+ values from [in/out] parameters and checking return codes for 0.
29
+
30
+ You have several options at this point. You can use either 'win32-api' or 'ffi' libraries
31
+ to connect your ruby code to Windows API and manually define wrapper methods for
32
+ needed function calls. This is definitely a valid approach, even if it is a bit
33
+ low-level one: you'll still have to handle (somewhat) gory details of callback
34
+ announcement, argument preparation, mimicking pointers with Strings (or declaring
35
+ pointers explicitly with FFI) and other stuff (like manually defining about a gazillion
36
+ obscure Windows constants). As an example, consider the amount of code needed to complete
37
+ a task as simple as getting unicode title text for the window that you already have handle
38
+ for (using win32-api):
39
+
40
+ api = Win32::API.new( 'GetWindowTextW', ['L', 'P', 'I'], 'L', 'user32' )
41
+ buffer = "\x00" * 1024 # I just hope it will be enough...
42
+ num_chars = api.call(window_handle, buffer, buffer.size)
43
+ title = if num_chars == 0
44
+ nil
45
+ else
46
+ buffer.force_encoding('utf-16LE').encode('utf-8').rstrip
47
+ end
48
+
49
+ This is how you achieve the same result with ffi:
50
+
51
+ extend FFI::Library
52
+ ffi_lib 'user32'
53
+ ffi_convention :stdcall
54
+ attach_function :GetWindowTextW, [ :long, :buffer_out, :int ], :long
55
+ buffer = FFI::Buffer.new 1024
56
+ buffer.put_string(0, "\x00" * 1024)
57
+ num_chars = GetWindowTextW(window_handle, buffer, 1024)
58
+ title = if num_chars == 0
59
+ nil
60
+ else
61
+ buffer.get_bytes(0, num_chars).force_encoding('utf-16LE').encode('utf-8').rstrip
62
+ end
63
+
64
+ As an alternative, you can use 'windows-pr' (pure ruby) library that gives you lots of
65
+ Windows functions pre-defined and sectioned into modules, declares Windows constants and
66
+ adds some other niceties. Unfortunately this library works only with MRI (not JRuby or
67
+ other Ruby implementations), and still lacks Ruby look-and-feel for declared functions.
68
+ It helps you to cut some of the declaration slack:
69
+
70
+ num_chars = GetWindowTextW(window_handle, buffer ="\x00" * 1024 , buffer.size)
71
+ title = if num_chars == 0
72
+ nil
73
+ else
74
+ buffer.force_encoding('utf-16LE').encode('utf-8').rstrip
75
+ end
76
+
77
+ But still, does not it look like TOO MUCH code for something that should (ideally) look like this:
78
+
79
+ title = window_text_w(window_handle)
80
+
81
+ This is an idea behind this library - make Windows API functions easier to use and feel more
82
+ natural inside Ruby code. Following the principle of least surprise, we define methods that:
83
+ * Have Rubyesque names (minimized? instead of IsMinimized, etc)
84
+ * Require minimum arguments with sensible defaults
85
+ * Return appropriate values explicitly (several return values if necessary)
86
+ * Have sensible returns (false/true instead of 0/nonzero for test functions, nil if find function fails, etc)
87
+ * Accept blocks where callback is expected
88
+ * Are partitioned into related namespaces, so that you can load only the modules you need
89
+
90
+ Well, we even keep a backup solution for those diehard Win32 API longtimers who would rather
91
+ allocate their buffer strings by hand and mess with obscure return codes. If you use original
92
+ CamelCase method name instead of Rubyesque snake_case one, it will expect those standard
93
+ parameters you know and love from MSDN, return your zeroes instead of nils and support no
94
+ other enhancements.
95
+
96
+ And if you do not see your favorite Windows API functions amoung those already defined, it is
97
+ quite easy to define new ones with 'function' macro method from Win::Library class - it does a
98
+ lot of heavy lifting for you and can be customized with options and code blocks to give you
99
+ reusable API wrapper methods with the exact behavior you need.
100
+
101
+ == REQUIREMENTS:
102
+
103
+ Only works with Ruby 1.9 compatible implementations since it uses some of the most recent features
104
+ (block arguments given to block, etc...)
105
+
106
+ == FEATURES/PROBLEMS:
107
+
108
+ This project is quite new, so it may be not suitable for production-quality systems yet.
109
+ Contributors always welcome!
110
+
111
+ == INSTALLATION
112
+
113
+ $ gem install win
114
+
115
+ == SYNOPSIS
116
+
117
+ require 'win/window'
118
+
119
+ class Foo
120
+ include Win::Window
121
+
122
+ fg_window = foreground_window
123
+ puts window_text_w(fg_window)
124
+ show_window(fg_window) unless minimized?(fg_window)
125
+ hide_window(fg_window) if maximized?(fg_window)
126
+ ...
127
+ end
128
+
129
+ == CREDITS:
130
+
131
+ This library started as an extension of ideas and code described in excellent book
132
+ "Scripted GUI Testing with Ruby" by Ian Dees. 'win32-api' and 'windows-pr' gems by
133
+ Daniel J. Berger and Park Heesob provided both inspiration and an excellent source
134
+ for code borrowing. 'ffi' gem serves as a solid basis for this library, allowing to
135
+ use it for multiple Ruby implementations.
136
+
137
+ == Note on Patches/Pull Requests
138
+
139
+ * Fork the project.
140
+ * Make your feature addition or bug fix.
141
+ * Add tests for it. This is important so I don't break it in a
142
+ future version unintentionally.
143
+ * Commit, do not mess with rakefile, version, or history.
144
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
145
+ * Send me a pull request. Bonus points for topic branches.
146
+
147
+ == Copyright
148
+
149
+ Copyright (c) 2010 arvicco. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,57 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "win"
8
+ gem.summary = %Q{A collection of Windows functions predefined for you using FFI}
9
+ gem.description = %Q{A collection of Windows functions predefined for you using FFI}
10
+ gem.email = "arvitallian@gmail.com"
11
+ gem.homepage = "http://github.com/arvicco/win"
12
+ gem.authors = ["arvicco"]
13
+ gem.add_development_dependency "rspec", ">= 1.2.9"
14
+ gem.add_development_dependency "cucumber", ">= 0"
15
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
20
+ end
21
+
22
+ require 'spec/rake/spectask'
23
+ Spec::Rake::SpecTask.new(:spec) do |spec|
24
+ spec.libs << 'lib' << 'spec'
25
+ spec.spec_files = FileList['spec/**/*_spec.rb']
26
+ end
27
+
28
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
29
+ spec.libs << 'lib' << 'spec'
30
+ spec.pattern = 'spec/**/*_spec.rb'
31
+ spec.rcov = true
32
+ end
33
+
34
+ task :spec => :check_dependencies
35
+
36
+ begin
37
+ require 'cucumber/rake/task'
38
+ Cucumber::Rake::Task.new(:features)
39
+
40
+ task :features => :check_dependencies
41
+ rescue LoadError
42
+ task :features do
43
+ abort "Cucumber is not available. In order to run features, you must: sudo gem install cucumber"
44
+ end
45
+ end
46
+
47
+ task :default => :spec
48
+
49
+ require 'rake/rdoctask'
50
+ Rake::RDocTask.new do |rdoc|
51
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
52
+
53
+ rdoc.rdoc_dir = 'rdoc'
54
+ rdoc.title = "win #{version}"
55
+ rdoc.rdoc_files.include('README*')
56
+ rdoc.rdoc_files.include('lib/**/*.rb')
57
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.2
File without changes
@@ -0,0 +1,4 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
2
+ require 'win'
3
+
4
+ require 'spec/expectations'
@@ -0,0 +1,9 @@
1
+ Feature: something something
2
+ In order to something something
3
+ A user something something
4
+ something something something
5
+
6
+ Scenario: something something
7
+ Given inspiration
8
+ When I create a sweet new gem
9
+ Then everyone should see how awesome I am
@@ -0,0 +1,24 @@
1
+ class String
2
+ def snake_case
3
+ gsub(/([a-z])([A-Z0-9])/, '\1_\2' ).downcase
4
+ end
5
+
6
+ def to_w
7
+ (self+"\x00").encode('utf-16LE')
8
+ end
9
+
10
+ def to_vkeys
11
+ unless size == 1
12
+ raise "Can't convert but a single character: #{self}"
13
+ end
14
+ ascii = upcase.unpack('C')[0]
15
+ case self
16
+ when 'a'..'z', '0'..'9', ' '
17
+ [ascii]
18
+ when 'A'..'Z'
19
+ [0x10, ascii] # Win.const_get(:VK_SHIFT) = 0x10 Bad coupling
20
+ else
21
+ raise "Can't convert unknown character: #{self}"
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,198 @@
1
+ require 'ffi'
2
+ require 'win/extensions'
3
+
4
+ module Win
5
+
6
+ class NotFoundError < NameError
7
+ def initialize(name=nil, libs=nil)
8
+ super %Q[Function #{name ? "'#{name}' ": ""}not found#{libs ? " in #{libs}" : ""}"]
9
+ end
10
+ end
11
+
12
+ module Library
13
+
14
+ # Class Win::Library::API mimics Win32::API
15
+ class API
16
+
17
+ # The name of the DLL that exports the API function
18
+ attr_reader :dll_name
19
+
20
+ # Ruby (library) module to which this function is attached
21
+ attr_reader :lib_module
22
+
23
+ # The name of the function passed to the constructor
24
+ attr_reader :function_name
25
+
26
+ # The name of the actual function that is returned by the constructor.
27
+ # For example, if you passed 'GetUserName' to the constructor, then the
28
+ # effective function name would be either 'GetUserNameA' or 'GetUserNameW'.
29
+ attr_accessor :effective_function_name
30
+
31
+ # The prototype, returned as an array of FFI types
32
+ attr_reader :prototype
33
+
34
+ # The return type (:void for no return value)
35
+ attr_reader :return_type
36
+
37
+ def initialize( lib_module, function, effective_function_name, prototype, return_type )
38
+ @lib_module = lib_module
39
+ @function_name = function
40
+ @effective_function_name = effective_function_name
41
+ @prototype = prototype
42
+ @return_type = return_type
43
+ end
44
+
45
+ def call( *args )
46
+ @lib_module.send(@effective_function_name.to_sym, *args)
47
+ end
48
+ end
49
+
50
+ # Contains class methods (macros) to be used in Win::Libraries
51
+ module ClassMethods
52
+ TYPES = {
53
+ V: :void, # For functions that return nothing (return type void).
54
+ v: :void, # For functions that return nothing (return type void).
55
+ C: :uchar, #– 8-bit unsigned character (byte)
56
+ c: :char, # 8-bit character (byte)
57
+ # :int8 – 8-bit signed integer
58
+ # :uint8 – 8-bit unsigned integer
59
+ S: :ushort, # – 16-bit unsigned integer (Win32API: used for string)
60
+ s: :short, # – 16-bit signed integer
61
+ # :uint16 – 16-bit unsigned integer
62
+ # :int16 – 16-bit signed integer
63
+ I: :uint, # 32-bit unsigned integer
64
+ i: :int, # 32-bit signed integer
65
+ # :uint32 – 32-bit unsigned integer
66
+ # :int32 – 32-bit signed integer
67
+ L: :ulong, # unsigned long int – platform-specific size
68
+ l: :long, # long int – platform-specific size (http://groups.google.com/group/ruby-ffi/browse_thread/thread/4762fc77130339b1)
69
+ # :int64 – 64-bit signed integer
70
+ # :uint64 – 64-bit unsigned integer
71
+ # :long_long – 64-bit signed integer
72
+ # :ulong_long – 64-bit unsigned integer
73
+ F: :float, # 32-bit floating point
74
+ D: :double, # 64-bit floating point (double-precision)
75
+ P: :pointer, # pointer – platform-specific size
76
+ p: :string, # C-style (NULL-terminated) character string (Win32API: S)
77
+ B: :bool # (?? 1 byte in C++)
78
+ #For function argument type only:
79
+ # :buffer_in – Similar to :pointer, but optimized for Buffers that the function can only read (not write).
80
+ # :buffer_out – Similar to :pointer, but optimized for Buffers that the function can only write (not read).
81
+ # :buffer_inout – Similar to :pointer, but may be optimized for Buffers.
82
+ # :varargs – Variable arguments
83
+ }
84
+
85
+ ##
86
+ # Defines new method wrappers for Windows API function call:
87
+ # - Defines method with original (CamelCase) API function name and original signature (matches MSDN description)
88
+ # - Defines method with snake_case name (converted from CamelCase function name) with enhanced API signature
89
+ # When the defined wrapper method is called, it checks the argument count, executes underlying API
90
+ # function call and (optionally) transforms the result before returning it. If block is attached to
91
+ # method invocation, raw result is yielded to this block before final transformations
92
+ # - Defines aliases for enhanced method with more Rubyesque names for getters, setters and tests:
93
+ # GetWindowText -> window_test, SetWindowText -> window_text=, IsZoomed -> zoomed?
94
+ #
95
+ # You may modify default behavior of defined method by providing optional &define_block to def_api.
96
+ # If you do so, instead of directly calling API function, defined method just yields callable api
97
+ # object, arguments and (optional) runtime block to your &define_block and returns result coming out of it.
98
+ # So, &define_block should define all the behavior of defined method. You can use define_block to:
99
+ # - Change original signature of API function, provide argument defaults, check argument types
100
+ # - Pack arguments into strings for [in] or [in/out] parameters that expect a pointer
101
+ # - Allocate string buffers for pointers required by API functions [out] parameters
102
+ # - Unpack [out] and [in/out] parameters returned as pointers
103
+ # - Explicitly return results of API call that are returned in [out] and [in/out] parameters
104
+ # - Convert attached runtime blocks into callback functions and stuff them into [in] callback parameters
105
+ #
106
+ # Accepts following options:
107
+ # :dll:: Use this dll instead of default 'user32'
108
+ # :rename:: Use this name instead of standard (conventional) function name
109
+ # :alias(es):: Provides additional alias(es) for defined method
110
+ # :boolean:: Forces method to return true/false instead of nonzero/zero
111
+ # :zeronil:: Forces method to return nil if function result is zero
112
+ #
113
+ def function(function, params, returns, options={}, &define_block)
114
+ method_name, effective_names, aliases = generate_names(function, options)
115
+ boolean = options[:boolean]
116
+ zeronil = options[:zeronil]
117
+ params = params.split(//) if params.respond_to?(:split) # Convert params string into array
118
+ #puts "#{method_name}, #{effective_names}, #{aliases}"
119
+ params.map! {|param| TYPES[param.to_sym] || param} # Convert chars into FFI type symbols
120
+ returns = TYPES[returns.to_sym] || returns # Convert chars into FFI type symbols
121
+
122
+ effective = effective_names.inject(nil) do |func, ename|
123
+ func || begin
124
+ #p ename, params, returns
125
+ attach_function(ename, params.dup, returns)
126
+ ename
127
+ rescue FFI::NotFoundError
128
+ nil
129
+ end
130
+ end
131
+
132
+ raise Win::NotFoundError.new(function, ffi_libraries.map(&:name)) unless effective
133
+
134
+ api = API.new(self, function, effective, params, returns)
135
+
136
+ define_method(function) {|*args| api.call(*args)} # define CamelCase method wrapper for api call
137
+
138
+ if define_block
139
+ define_method(method_name) do |*args, &runtime_block|
140
+ define_block[api, *args, &runtime_block]
141
+ end
142
+ else
143
+ define_method(method_name) do |*args, &runtime_block| # define snake_case method with enhanced api
144
+ namespace.enforce_count(args, params)
145
+ result = api.call(*args)
146
+ result = runtime_block[result] if runtime_block
147
+ return result != 0 if boolean # Boolean function returns true/false instead of nonzero/zero
148
+ return nil if zeronil && result == 0 # Zeronil function returns nil instead of zero
149
+ result
150
+ end
151
+ end
152
+ aliases.each {|ali| alias_method ali, method_name } # define aliases
153
+ api # return API object
154
+ end
155
+
156
+ # Generates possible effective names for function in Win32 dll (function+A/W),
157
+ # Rubyesque name and aliases for method(s) defined based on function name,
158
+ # sets boolean flag for test functions (Is...)
159
+ #
160
+ def generate_names(function, options={})
161
+ effective_names = [function]
162
+ effective_names += ["#{function}A", "#{function}W"] unless function =~ /[WA]$/
163
+ aliases = ([options[:alias]] + [options[:aliases]]).flatten.compact
164
+ method_name = options[:rename] || function.snake_case
165
+ case method_name
166
+ when /^is_/
167
+ aliases << method_name.sub(/^is_/, '') + '?'
168
+ options[:boolean] = true
169
+ when /^set_/
170
+ aliases << method_name.sub(/^set_/, '')+ '='
171
+ when /^get_/
172
+ aliases << method_name.sub(/^get_/, '')
173
+ end
174
+ [method_name, effective_names, aliases]
175
+ end
176
+
177
+ # Ensures that args count is equal to params count plus diff
178
+ #
179
+ def enforce_count(args, params, diff = 0)
180
+ num_args = args.size
181
+ num_params = params.size + diff #params == 'V' ? 0 : params.size + diff
182
+ if num_args != num_params
183
+ raise ArgumentError, "wrong number of arguments (#{num_args} for #{num_params})"
184
+ end
185
+ end
186
+ end
187
+
188
+ #extend ClassMethods
189
+
190
+ def self.included(klass)
191
+ klass.extend ClassMethods
192
+ klass.extend FFI::Library
193
+ klass.ffi_lib 'user32', 'kernel32' # Default libraries
194
+ klass.ffi_convention :stdcall
195
+ klass.class_eval "def namespace; #{klass}; end"
196
+ end
197
+ end
198
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format nested
@@ -0,0 +1,38 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'spec'
4
+ require 'spec/autorun'
5
+
6
+ # Customize RSpec with my own extensions
7
+ module SpecMacros
8
+
9
+ # wrapper for it method that extracts description from example source code, such as:
10
+ # spec { use{ function(arg1 = 4, arg2 = 'string') }}
11
+ def spec &block
12
+ it description_from(*block.source_location), &block
13
+ end
14
+
15
+ # reads description line from source file and drops external brackets (like its{}, use{}
16
+ def description_from(file, line)
17
+ File.open(file) do |f|
18
+ f.lines.to_a[line-1].gsub( /(spec.*?{)|(use.*?{)|}/, '' ).strip
19
+ end
20
+ end
21
+ end
22
+
23
+ Spec::Runner.configure { |config| config.extend(SpecMacros) }
24
+
25
+ module WinTest
26
+
27
+ TEST_IMPOSSIBLE = 'Impossible'
28
+ TEST_CONVERSION_ERROR = /Can.t convert/
29
+
30
+ def use
31
+ lambda {yield}.should_not raise_error
32
+ end
33
+
34
+ def any_block
35
+ lambda {|*args| args}
36
+ end
37
+
38
+ end
@@ -0,0 +1,63 @@
1
+ require File.join(File.dirname(__FILE__), ".." , "spec_helper" )
2
+ require 'win/extensions'
3
+
4
+ module WinTest
5
+
6
+ describe String do
7
+ context '#snake_case' do
8
+ it 'transforms CamelCase strings' do
9
+ 'GetCharWidth32'.snake_case.should == 'get_char_width_32'
10
+ end
11
+
12
+ it 'leaves snake_case strings intact' do
13
+ 'keybd_event'.snake_case.should == 'keybd_event'
14
+ end
15
+ end
16
+
17
+ context '#to_w' do
18
+ it 'transcodes string to utf-16LE' do
19
+ 'GetCharWidth32'.to_w.encoding.name.should == 'UTF-16LE'
20
+ end
21
+
22
+ it 'ensures that encoded string is null-terminated' do
23
+ 'GetCharWidth32'.to_w.bytes.to_a[-2..-1].should == [0, 0]
24
+ end
25
+ end
26
+
27
+ context '#to_vkeys' do
28
+ it 'transforms number char into [equivalent key code]' do
29
+ ('0'..'9').each {|char| char.to_vkeys.should == char.unpack('C')}
30
+ end
31
+
32
+ it 'transforms uppercase letters into [shift, equivalent key code]' do
33
+ ('A'..'Z').each {|char| char.to_vkeys.should == [0x10, *char.unpack('C')]}
34
+ # Win.const_get(:VK_SHIFT) = 0x10 Bad coupling
35
+ end
36
+
37
+ it 'transforms lowercase letters into [(upcase) key code]' do
38
+ ('a'..'z').each {|char| char.to_vkeys.should == char.upcase.unpack('C')}
39
+ end
40
+
41
+ it 'transforms space into [equivalent key code]' do
42
+ " ".to_vkeys.should == " ".unpack('C')
43
+ end
44
+
45
+ it 'raises error if char is not implemented punctuation' do
46
+ ('!'..'/').each {|char| lambda {char.to_vkeys}.should raise_error TEST_CONVERSION_ERROR }
47
+ (':'..'@').each {|char| lambda {char.to_vkeys}.should raise_error TEST_CONVERSION_ERROR }
48
+ ('['..'`').each {|char| lambda {char.to_vkeys}.should raise_error TEST_CONVERSION_ERROR }
49
+ ('{'..'~').each {|char| lambda {char.to_vkeys}.should raise_error TEST_CONVERSION_ERROR }
50
+ end
51
+
52
+ it 'raises error if char is non-printable or non-ascii' do
53
+ lambda {1.chr.to_vkeys}.should raise_error TEST_CONVERSION_ERROR
54
+ lambda {230.chr.to_vkeys}.should raise_error TEST_CONVERSION_ERROR
55
+ end
56
+
57
+ it 'raises error if string is multi-char' do
58
+ lambda {'hello'.to_vkeys}.should raise_error TEST_CONVERSION_ERROR
59
+ lambda {'23'.to_vkeys}.should raise_error TEST_CONVERSION_ERROR
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,370 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+ require 'win/library'
3
+
4
+ module WinTest
5
+
6
+ module MyLib # namespace for defined functions
7
+ include Win::Library
8
+ end
9
+ include MyLib
10
+
11
+ def should_be symbol, api
12
+ case symbol
13
+ when :find_window
14
+ #api.dll_name.should == 'user32' # The name of the DLL that exports the API function
15
+ api.effective_function_name.should == 'FindWindowA' # Actual function returned by the constructor: 'GetUserName' ->'GetUserNameA' or 'GetUserNameW'
16
+ api.function_name.should == 'FindWindow' # The name of the function passed to the constructor
17
+ api.prototype.should == [:pointer, :pointer] # The prototype, returned as an array of characters
18
+ api.return_type.should == :ulong # The prototype, returned as an array of characters
19
+ end
20
+ end
21
+
22
+ def should_count_args(*methods, rights, wrongs)
23
+ rights = [rights].flatten
24
+ wrongs = [wrongs].flatten
25
+ methods.each do |method|
26
+ (0..8).each do |n|
27
+ if n == rights.size
28
+ expect {send method, *rights}.to_not raise_error
29
+ else
30
+ args = (1..n).map {wrongs[rand(wrongs.size)]}
31
+ expect {send method, *args}.
32
+ to raise_error "wrong number of arguments (#{args.size} for #{rights.size})"
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ def any_handle
39
+ MyLib.function 'FindWindow', 'PP', 'L' unless respond_to? :find_window
40
+ find_window(nil, nil)
41
+ end
42
+
43
+ def not_a_handle
44
+ 123
45
+ end
46
+
47
+ def redefined_methods
48
+ [:FindWindow, :IsWindow, :EnumWindows, :GetComputerName, :GetForegroundWindow]
49
+ end
50
+
51
+ def hide_method(*names) # hide original method(s) if it is defined
52
+ names.map(&:to_s).each do |name|
53
+ MyLib.module_eval do
54
+ aliases = generate_names(name).flatten + [name]
55
+ aliases.map(&:to_s).each do |ali|
56
+ if method_defined? ali
57
+ alias_method "original_#{ali}".to_sym, ali
58
+ remove_method ali
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ def restore_method(*names) # restore original method if it was hidden
66
+ names.map(&:to_s).each do |name|
67
+ MyLib.module_eval do
68
+ aliases = generate_names(name).flatten + [name]
69
+ aliases.map(&:to_s).each do |ali|
70
+ temp = "original_#{ali}".to_sym
71
+ if method_defined? temp
72
+ alias_method ali, temp
73
+ remove_method temp
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ describe Win::Library, ' defines wrappers for Win32::API functions' do
81
+
82
+ before(:each) { hide_method *redefined_methods } # hide original methods if defined
83
+ after(:each) { restore_method *redefined_methods } # restore original methods if hidden
84
+
85
+ context 'defining enhanced API function method' do
86
+ spec{ use{ MyLib.function('FindWindow', 'PP', 'l', rename: nil, aliases: nil, boolean: nil, zeronil: nil, &any_block) }}
87
+
88
+ it 'defines new instance methods with appropriate names' do
89
+ MyLib.function 'FindWindow', 'PP', 'L'
90
+ respond_to?(:FindWindow).should be_true
91
+ respond_to?(:find_window).should be_true
92
+ end
93
+
94
+ it 'constructs argument prototype from uppercase string, enforces the args count' do
95
+ expect { MyLib.function 'FindWindow', 'PP', 'L' }.to_not raise_error
96
+ should_count_args :find_window, :FindWindow, [nil, nil], [nil, TEST_IMPOSSIBLE, 'cmd']
97
+ end
98
+
99
+ it 'constructs argument prototype from (mixed) array, enforces the args count' do
100
+ expect { MyLib.function 'FindWindow', [:pointer, 'P'], 'L' }.to_not raise_error
101
+ should_count_args :find_window, :FindWindow, [nil, nil], [nil, TEST_IMPOSSIBLE, 'cmd']
102
+ end
103
+
104
+ it 'with :rename option, overrides snake_case name for defined method but leaves CamelCase intact' do
105
+ MyLib.function 'FindWindow', 'PP', 'L', :rename=> 'my_own_find'
106
+ expect {find_window(nil, nil)}.to raise_error NoMethodError
107
+ expect {FindWindow(nil, nil)}.to_not raise_error
108
+ expect {my_own_find(nil, nil)}.to_not raise_error
109
+ end
110
+
111
+ it 'defined snake_case method returns expected value when called' do
112
+ MyLib.function 'FindWindow', 'PP', 'L'
113
+ find_window(nil, nil).should_not == 0
114
+ find_window(nil, TEST_IMPOSSIBLE).should == 0
115
+ find_window(TEST_IMPOSSIBLE, nil).should == 0
116
+ find_window(TEST_IMPOSSIBLE, TEST_IMPOSSIBLE).should == 0
117
+ end
118
+
119
+ it 'defined CamelCase method returns expected value when called' do
120
+ MyLib.function 'FindWindow', 'PP', 'L'
121
+ FindWindow(nil, nil).should_not == 0
122
+ FindWindow(nil, TEST_IMPOSSIBLE).should == 0
123
+ FindWindow(TEST_IMPOSSIBLE, nil).should == 0
124
+ FindWindow(TEST_IMPOSSIBLE, TEST_IMPOSSIBLE).should == 0
125
+ end
126
+
127
+ # it 'returns underlying Win32::API object if defined method is called with (:api) argument ' do
128
+ # MyLib.function 'FindWindow', 'PP', 'L'
129
+ # expect {find_window(:api)}.to_not raise_error
130
+ # should_be :find_window, find_window(:api)
131
+ # end
132
+
133
+ it 'returns underlying Win32::API object' do
134
+ FWAPI = MyLib.function 'FindWindow', 'PP', 'L'
135
+ should_be :find_window, FWAPI
136
+ end
137
+ end
138
+
139
+ context 'defining aliases' do
140
+ it 'adds alias for defined method with :alias option' do
141
+ MyLib.function( 'FindWindow', 'PP', 'L', :alias => 'my_own_find')
142
+ expect {find_window(nil, nil)}.to_not raise_error
143
+ expect {my_own_find(nil, nil)}.to_not raise_error
144
+ end
145
+
146
+ it 'adds aliases for defined method with :aliases option' do
147
+ MyLib.function 'FindWindow', 'PP', 'L', :aliases => ['my_own_find', 'my_own_find1']
148
+ expect {find_window(nil, nil)}.to_not raise_error
149
+ expect {my_own_find(nil, nil)}.to_not raise_error
150
+ expect {my_own_find1(nil, nil)}.to_not raise_error
151
+ end
152
+
153
+ it 'adds Rubyesque alias to IsXxx API test function' do
154
+ MyLib.function 'IsWindow', 'L', 'B'
155
+ respond_to?(:window?).should be_true
156
+ respond_to?(:is_window).should be_true
157
+ end
158
+
159
+ it 'adds Rubyesque alias to GetXxx API getter function' do
160
+ MyLib.function 'GetComputerName', 'PP', 'I', :dll=> 'kernel32'
161
+ respond_to?(:get_computer_name).should be_true
162
+ respond_to?(:computer_name).should be_true
163
+ end
164
+ end
165
+
166
+ context 'auto-defining Ruby-like boolean methods if API function name starts with "Is_"' do
167
+ before(:each) {MyLib.function 'IsWindow', 'L', 'L'}
168
+
169
+ it 'defines new instance method name dropping Is_ and adding ?' do
170
+ respond_to?(:window?).should be_true
171
+ respond_to?(:is_window).should be_true
172
+ respond_to?(:IsWindow).should be_true
173
+ end
174
+
175
+ it 'defined CamelCase method returns zero/non-zero as expected' do
176
+ IsWindow(any_handle).should_not == true
177
+ IsWindow(any_handle).should_not == 0
178
+ IsWindow(not_a_handle).should == 0
179
+ end
180
+
181
+ it 'defined snake_case method returns false/true instead of zero/non-zero' do
182
+ window?(any_handle).should == true
183
+ window?(not_a_handle).should == false
184
+ is_window(any_handle).should == true
185
+ is_window(not_a_handle).should == false
186
+ end
187
+
188
+ it 'defined methods enforce the argument count' do
189
+ should_count_args :window?, :is_window, :IsWindow, [not_a_handle], [nil, not_a_handle, any_handle]
190
+ end
191
+ end
192
+
193
+ context 'defining API with :boolean option converts result to boolean' do
194
+ before(:each) { MyLib.function 'FindWindow', 'PP', 'L', :boolean => true }
195
+
196
+ it 'defines new instance method' do
197
+ respond_to?(:find_window).should be_true
198
+ respond_to?(:FindWindow).should be_true
199
+ end
200
+
201
+ it 'defined snake_case method returns false/true instead of zero/non-zero' do
202
+ find_window(nil, nil).should == true
203
+ find_window(nil, TEST_IMPOSSIBLE).should == false
204
+ end
205
+
206
+ it 'defined CamelCase method still returns zero/non-zero' do
207
+ FindWindow(nil, nil).should_not == true
208
+ FindWindow(nil, nil).should_not == 0
209
+ FindWindow(nil, TEST_IMPOSSIBLE).should == 0
210
+ end
211
+
212
+ it 'defined methods enforce the argument count' do
213
+ should_count_args :find_window, :FindWindow, [nil, nil], [nil, TEST_IMPOSSIBLE, 'cmd']
214
+ end
215
+ end
216
+
217
+ context 'defining API with :zeronil option converts zero result to nil' do
218
+ before(:each) {MyLib.function 'FindWindow', 'PP', 'L', :zeronil => true}
219
+
220
+ it 'defines new instance method' do
221
+ respond_to?(:find_window).should be_true
222
+ respond_to?(:FindWindow).should be_true
223
+ end
224
+
225
+ it 'defined CamelCase method still returns zero/non-zero' do
226
+ FindWindow(nil, nil).should_not == true
227
+ FindWindow(nil, nil).should_not == 0
228
+ FindWindow(nil, TEST_IMPOSSIBLE).should == 0
229
+ end
230
+
231
+ it 'defined method returns nil (but NOT false) instead of zero' do
232
+ find_window(nil, TEST_IMPOSSIBLE).should_not == false
233
+ find_window(nil, TEST_IMPOSSIBLE).should == nil
234
+ end
235
+
236
+ it 'defined method does not return true when result is non-zero' do
237
+ find_window(nil, nil).should_not == true
238
+ find_window(nil, nil).should_not == 0
239
+ end
240
+
241
+ it 'defined methods enforce the argument count' do
242
+ should_count_args :find_window, :FindWindow, [nil, nil], [nil, TEST_IMPOSSIBLE, 'cmd']
243
+ end
244
+ end
245
+
246
+ context 'using DLL other than default user32 with :dll option' do
247
+ before(:each) {MyLib.function 'GetComputerName', 'PP', 'I', :dll=> 'kernel32'}
248
+
249
+ it 'defines new instance method with appropriate name' do
250
+ respond_to?(:GetComputerName).should be_true
251
+ respond_to?(:get_computer_name).should be_true
252
+ respond_to?(:computer_name).should be_true
253
+ end
254
+
255
+ it 'returns expected result' do
256
+ MyLib.function 'GetComputerName', ['P', 'P'], 'I', :dll=> 'kernel32'
257
+ hostname = `hostname`.strip.upcase
258
+ name = " " * 128
259
+ get_computer_name(name, "128")
260
+ name.unpack("A*").first.should == hostname
261
+ end
262
+ end
263
+
264
+ context 'trying to define an invalid API function' do
265
+ it 'raises error when trying to define function with a wrong function name' do
266
+ expect { MyLib.function 'FindWindowImpossible', 'PP', 'L' }.
267
+ to raise_error( /Function 'FindWindowImpossible' not found/ )
268
+ end
269
+ end
270
+
271
+ context 'defining API function using definition block' do
272
+ it 'defines new instance method' do
273
+ MyLib.function( 'FindWindow', 'PP', 'L' ){|api, *args|}
274
+ respond_to?(:find_window).should be_true
275
+ respond_to?(:FindWindow).should be_true
276
+ end
277
+
278
+ it 'does not enforce argument count outside of block' do
279
+ MyLib.function( 'FindWindow', 'PP', 'L' ){|api, *args|}
280
+ expect { find_window }.to_not raise_error
281
+ expect { find_window(nil) }.to_not raise_error
282
+ expect { find_window(nil, 'Str', 1) }.to_not raise_error
283
+ end
284
+
285
+ it 'returns block return value when defined method is called' do
286
+ MyLib.function( 'FindWindow', 'PP', 'L' ){|api, *args| 'Value'}
287
+ find_window(nil).should == 'Value'
288
+ end
289
+
290
+ it 'passes arguments and underlying Win32::API object to the block' do
291
+ MyLib.function( 'FindWindow', 'PP', 'L' ) do |api, *args|
292
+ @api = api
293
+ @args = args
294
+ end
295
+ expect {find_window(1, 2, 3) }.to_not raise_error
296
+ @args.should == [1, 2, 3]
297
+ should_be :find_window, @api
298
+ end
299
+
300
+ it ':rename option overrides standard name for defined method' do
301
+ MyLib.function( 'FindWindow', 'PP', 'L', :rename => 'my_own_find' ){|api, *args|}
302
+ expect {find_window(nil, nil, nil)}.to raise_error
303
+ expect {my_own_find(nil, nil)}.to_not raise_error
304
+ end
305
+
306
+ it 'adds alias for defined method with :alias option' do
307
+ MyLib.function( 'FindWindow', 'PP', 'L', :alias => 'my_own_find' ){|api, *args|}
308
+ expect {find_window(nil, nil)}.to_not raise_error
309
+ expect {my_own_find(nil, nil)}.to_not raise_error
310
+ end
311
+
312
+ it 'adds aliases for defined method with :aliases option' do
313
+ MyLib.function( 'FindWindow', 'PP', 'L', :aliases => ['my_own_find', 'my_own_find1'] ) {|api, *args|}
314
+ expect {find_window(nil, nil)}.to_not raise_error
315
+ expect {my_own_find(nil, nil)}.to_not raise_error
316
+ expect {my_own_find1(nil, nil)}.to_not raise_error
317
+ end
318
+ end
319
+
320
+ context 'calling defined methods with attached block to preprocess the API function results' do
321
+ it 'defined method yields raw result to block attached to its invocation' do
322
+ MyLib.function 'FindWindow', 'PP', 'L', zeronil: true
323
+ find_window(nil, TEST_IMPOSSIBLE) {|result| result.should == 0 }
324
+ end
325
+
326
+ it 'defined method returns result of block attached to its invocation' do
327
+ MyLib.function 'FindWindow', 'PP', 'L', zeronil: true
328
+ return_value = find_window(nil, TEST_IMPOSSIBLE) {|result| 'Value'}
329
+ return_value.should == 'Value'
330
+ end
331
+
332
+ it 'defined method transforms result of block before returning it' do
333
+ MyLib.function 'FindWindow', 'PP', 'L', zeronil: true
334
+ return_value = find_window(nil, TEST_IMPOSSIBLE) {|result| 0 }
335
+ return_value.should_not == 0
336
+ return_value.should == nil
337
+ end
338
+ end
339
+
340
+ context 'defining API function without arguments - f(VOID)' do
341
+ it 'should enforce argument count to 0, NOT 1' do
342
+ MyLib.function 'GetForegroundWindow', [], 'L', zeronil: true
343
+ should_count_args :GetForegroundWindow, :get_foreground_window, :foreground_window, [], [nil, 0, 123]
344
+ end
345
+ end
346
+
347
+ context 'working with API function callbacks' do
348
+ it '#callback method creates a valid callback object' do
349
+ pending 'What about callbacks?'
350
+ expect { @callback = WinGui.callback('LP', 'I') {|handle, message| true} }.to_not raise_error
351
+ @callback.should be_a_kind_of(Win32::API::Callback)
352
+ end
353
+
354
+ it 'created callback object can be used as a valid arg of API function expecting callback' do
355
+ pending 'What about callbacks?'
356
+ MyLib.function 'EnumWindows', 'KP', 'L'
357
+ @callback = WinGui.callback('LP', 'I'){|handle, message| true }
358
+ expect { enum_windows(@callback, 'Message') }.to_not raise_error
359
+ end
360
+
361
+ it 'defined API functions expecting callback convert given block into callback' do
362
+ pending 'What about callbacks?'
363
+ pending ' What about prototype!? API is not exactly clear atm (.with_callback method?)'
364
+ MyLib.function 'EnumWindows', 'KP', 'L'
365
+ expect { enum_windows('Message'){|handle, message| true } }.to_not raise_error
366
+ end
367
+ end
368
+
369
+ end
370
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: win
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - arvicco
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-02-08 00:00:00 +03:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rspec
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.2.9
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: cucumber
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ description: A collection of Windows functions predefined for you using FFI
36
+ email: arvitallian@gmail.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - LICENSE
43
+ - README.rdoc
44
+ files:
45
+ - .document
46
+ - .gitignore
47
+ - LICENSE
48
+ - README.rdoc
49
+ - Rakefile
50
+ - VERSION
51
+ - features/step_definitions/win_steps.rb
52
+ - features/support/env.rb
53
+ - features/win.feature
54
+ - lib/win/extensions.rb
55
+ - lib/win/library.rb
56
+ - spec/spec.opts
57
+ - spec/spec_helper.rb
58
+ - spec/win/extensions_spec.rb
59
+ - spec/win/library_spec.rb
60
+ has_rdoc: true
61
+ homepage: http://github.com/arvicco/win
62
+ licenses: []
63
+
64
+ post_install_message:
65
+ rdoc_options:
66
+ - --charset=UTF-8
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: "0"
74
+ version:
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: "0"
80
+ version:
81
+ requirements: []
82
+
83
+ rubyforge_project:
84
+ rubygems_version: 1.3.5
85
+ signing_key:
86
+ specification_version: 3
87
+ summary: A collection of Windows functions predefined for you using FFI
88
+ test_files:
89
+ - spec/spec_helper.rb
90
+ - spec/win/extensions_spec.rb
91
+ - spec/win/library_spec.rb