barkest_lcd 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5a99d7096928cf7602e31e972939a1c563943e3e
4
+ data.tar.gz: f0dc18d42e43c626695111307ebec7de8af86431
5
+ SHA512:
6
+ metadata.gz: 0702a57c8942c02aa0bcafd8a1ce43297e41f2f1e067c1f7496853b5326874729a9ed6dce65d88fa01bf1ef29bd5407554de3663fd9c9c8bd1d599a66e974896
7
+ data.tar.gz: 927118633b5bc710774e8077112a84c8b3f64fdfc2c9f18864e57b6bb267bf2cca88b207be20dc3d69a4287cf8688fae5137dd7ec5abb41e51cf0fd1eb24f847
@@ -0,0 +1,11 @@
1
+ .bundle/
2
+ .yardoc
3
+ Gemfile.lock
4
+ _yardoc/
5
+ coverage/
6
+ doc/
7
+ pkg/
8
+ spec/reports/
9
+ tmp/
10
+ .idea/
11
+ /**/.DS_Store
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in barkest_lcd.gemspec
4
+ gemspec
5
+
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Beau Barker
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,112 @@
1
+ # BarkestLcd
2
+
3
+ This gem is designed to make it easy to interface with some LCD displays.
4
+
5
+ I wanted to add a front panel LCD to a server I was building, so I started this project to address interacting with
6
+ the LCD panel I picked up.
7
+
8
+ Currently it works with the PicoLCD from [www.mini-box.com](http://www.mini-box.com/picoLCD-256x64-Sideshow-CDROM-Bay).
9
+
10
+
11
+ ## Installation
12
+
13
+ This gem depends on [HID API](http://www.signal11.us/oss/hidapi/).
14
+
15
+ Installation of the dependency is specific to the environment.
16
+
17
+ For OS X (using homebrew):
18
+
19
+ $ brew install hidapi
20
+
21
+ For Ubuntu/Debian:
22
+
23
+ $ sudo apt-get install libhidapi-libusb0
24
+ $ cd /usr/lib/x86_64-linux-gnu
25
+ $ sudo ln -s libhidapi-libusb.so.0.0.0 libhidapi.so
26
+
27
+
28
+ Add this line to your application's Gemfile:
29
+
30
+ ```ruby
31
+ gem 'barkest_lcd'
32
+ ```
33
+
34
+ And then execute:
35
+
36
+ $ bundle
37
+
38
+ Or install it yourself as:
39
+
40
+ $ gem install barkest_lcd
41
+
42
+
43
+ ## Usage
44
+
45
+ Let's assume you have one picoLCD 256x64 device attached to your computer. Usage is fairly simple.
46
+
47
+ This example will draw a 32x32 rectangle with an X in it.
48
+
49
+ ```ruby
50
+ my_device = BarkestLcd::PicoLcdGraphic.first
51
+ my_device.open
52
+ my_device.draw_rect(40, 4, 32, 32).draw_line(40, 4, 72, 36).draw_line(40, 36, 72, 4)
53
+ my_device.paint
54
+ ```
55
+
56
+ As you can see, most of the methods are chainable. Unless the method has an explicit return value (like `open?`) then
57
+ the method should be returning the device model. The `open`, `close`, and `paint` methods are all chainable as well.
58
+
59
+ There are currently some very basic drawing methods included by the `SimpleGraphic` module. These include `set_bit`,
60
+ `draw_vline`, `draw_hline`, `draw_line`, and `draw_rect`. The `clear` method will clear the screen.
61
+
62
+ Continuing, we will wait for the user to press the OK button.
63
+
64
+ ```ruby
65
+ OK_BUTTON = 6
66
+ ok_pressed = false
67
+
68
+ # set the callback to process keys when they are released.
69
+ my_device.on_key_up do |key|
70
+ if key == OK_BUTTON
71
+ ok_pressed = true
72
+ end
73
+ end
74
+
75
+ # use the "loop" function to process events with the device and update the screen.
76
+ until ok_pressed
77
+ my_device.loop
78
+ end
79
+ ```
80
+
81
+ In this case we set the `on_key_up` callback to set a flag when the OK button is pressed. If we wanted to do repeating
82
+ keys when a user holds down a button, we may want to set the `on_key_down` or use the `key_state` method within the loop.
83
+
84
+ Finally, I recommend closing the device when you are finished. Ruby will close the device when it exits, but if you
85
+ are handling errors and such, closing the device explicitly in an `ensure` block will help to protect you against being
86
+ unable to open the device again.
87
+
88
+ ```ruby
89
+ my_device.clear.paint
90
+ my_device.close
91
+ ```
92
+
93
+ It is not necessary to clear the screen before closing, but doing so ensures that you do not leave weird information
94
+ or screen artifacts up when your application is done.
95
+
96
+
97
+
98
+ ## Credits
99
+
100
+ * The [picoLCD 256x64 Suite Source](http://resources.mini-box.com/online/picolcd/256x64/1003/PicoLCD256x64_src.zip)
101
+ provided most of the information needed to make this gem interact with the picoLCD 256x64 from
102
+ [mini-box](http://www.mini-box.com).
103
+ * The [picoLCD256x64](https://github.com/itszero/picoLCD256x64) project provided some inspiration, but I ended up going
104
+ a completely different direction.
105
+ * The font came from [Silkscreen](http://www.kottke.org/plus/type/silkscreen/index.html).
106
+
107
+ ## License
108
+
109
+ Copyright (c) 2016 [Beau Barker](mailto:beau@barkerest.com)
110
+
111
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
112
+
@@ -0,0 +1,12 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << 'lib'
6
+ t.libs << 'test'
7
+ t.pattern = 'test/**/*_test.rb'
8
+ t.verbose = false
9
+ end
10
+
11
+ task :default => :test
12
+
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'barkest_lcd/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "barkest_lcd"
8
+ spec.version = BarkestLcd::VERSION
9
+ spec.authors = ["Beau Barker"]
10
+ spec.email = ["rtecoder@gmail.com"]
11
+
12
+ spec.summary = "Provides a simple interface to LCD displays."
13
+ spec.description = "This gem was originally created to interface with a PicoLCD 256x64 from www.mini-box.com."
14
+ spec.homepage = "http://www.barkerest.com/"
15
+ spec.license = "MIT"
16
+
17
+
18
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
19
+ spec.bindir = "exe"
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_dependency 'libusb', '~>0.5.1'
24
+ spec.add_dependency 'hidapi', '>=0.1.4'
25
+
26
+
27
+ spec.add_development_dependency 'bundler', '~> 1.12'
28
+ spec.add_development_dependency 'rake', '~> 10.0'
29
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "barkest_lcd"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
Binary file
Binary file
Binary file
@@ -0,0 +1,10 @@
1
+ require 'barkest_lcd/version'
2
+ require 'hidapi'
3
+
4
+ module BarkestLcd
5
+
6
+ end
7
+
8
+ Dir.glob(File.expand_path('../models/*.rb', __FILE__)).each do |file|
9
+ require file
10
+ end
@@ -0,0 +1,3 @@
1
+ module BarkestLcd
2
+ VERSION = "0.4.0"
3
+ end
@@ -0,0 +1,21 @@
1
+ module BarkestLcd
2
+ module ErrorLogger
3
+
4
+ public
5
+
6
+ ##
7
+ # Contains the last 100 errors that have occurred in this instance.
8
+ def error_history
9
+ @error_history ||= []
10
+ end
11
+
12
+ protected
13
+
14
+ def log_error(code, msg)
15
+ HIDAPI.debug "Encountered error (#{code.to_s(16).rjust(8, '0')}) #{msg}"
16
+ error_history << [ code, msg, Time.now ]
17
+ error_history.delete_at(0) while error_history.count > 100
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,404 @@
1
+ module BarkestLcd
2
+ ##
3
+ # A basic bitmap font.
4
+ class Font
5
+
6
+ ##
7
+ # Gets or sets the name of this font.
8
+ attr_accessor :name
9
+
10
+ ##
11
+ # Gets or sets the size of this font.
12
+ attr_accessor :size
13
+
14
+ ##
15
+ # Gets or sets the bold flag.
16
+ attr_accessor :bold
17
+
18
+ ##
19
+ # Creates a new bitmap font from a hash.
20
+ #
21
+ # glyphs = {
22
+ # :name => "Font Name", # optional
23
+ # :size => 8, # optional
24
+ # :bold => false, # optional
25
+ # 97 => { # ASCII character code.
26
+ # :char => "a", # printed character.
27
+ # :width => 8, # width in pixels
28
+ # :height => 8, # height in pixels
29
+ # :data => [ # array containing row data.
30
+ # " ", # rows may be strings or arrays.
31
+ # " ", # when strings, non-space characters are set pixels.
32
+ # " ** ", # when arrays, values of true or 1 are set pixels.
33
+ # " * ",
34
+ # " **** ",
35
+ # " * * ",
36
+ # " **** ",
37
+ # " ",
38
+ # ],
39
+ # },
40
+ # ...
41
+ # }
42
+ def initialize(glyphs)
43
+ raise ArgumentError unless glyphs.is_a?(Hash)
44
+
45
+ @name = glyphs.delete(:name) || 'Font'
46
+ @size = glyphs.delete(:size) || 8
47
+ @bold = glyphs.delete(:bold) || false
48
+ @glyphs = {}
49
+
50
+ glyphs.each do |k,v|
51
+ k = k.to_i
52
+ raise ArgumentError, 'hash must have numeric keys greater than or equal to 32 and less than 127' if k < 32 || k > 127
53
+ raise ArgumentError, 'hash must have hashes as values' unless v.is_a?(Hash)
54
+ raise ArgumentError, 'hash values must have a :char key' unless v[:char]
55
+ raise ArgumentError, 'hash values must have a :width key' unless v[:width]
56
+ raise ArgumentError, 'hash values must have a :height key' unless v[:height]
57
+ raise ArgumentError, 'hash values must have a :data key' unless v[:data]
58
+ raise ArgumentError, 'hash :data key must have an array value' unless v[:data].is_a?(Array)
59
+
60
+ glyph = {
61
+ char: v[:char].to_s.freeze,
62
+ width: v[:width].to_i.freeze,
63
+ height: v[:height].to_i.freeze,
64
+ data: []
65
+ }
66
+
67
+ raise ArgumentError, 'hash :data value must have exactly :height members' unless v[:data].length == glyph[:height]
68
+ v[:data].each_with_index do |row, row_index|
69
+ raise ArgumentError, 'hash :data value members must be arrays or strings' unless row.is_a?(Array) || row.is_a?(String)
70
+ raise ArgumentError, 'hash :data value members must have :width values' unless row.length == glyph[:width]
71
+
72
+ glyph[:data][row_index] = [ false ] * glyph[:width]
73
+ if row.is_a?(String)
74
+ row.chars.each_with_index do |ch, ch_index|
75
+ glyph[:data][row_index][ch_index] = true unless ch == ' ' # spaces are false, everything else is true.
76
+ end
77
+ else
78
+ row.each_with_index do |bit, bit_index|
79
+ glyph[:data][row_index][bit_index] = true if bit == true || bit == 1
80
+ end
81
+ end
82
+ glyph[:data][row_index].freeze
83
+
84
+ end
85
+
86
+ add_glyph_helpers_to glyph
87
+
88
+ glyph[:data].freeze
89
+
90
+ glyph.freeze
91
+
92
+ @glyphs[k] = glyph
93
+ end
94
+
95
+ @height = @glyphs.inject(0) { |h,(key,g)| g.height > h ? g.height : h }
96
+ @nil_glyph = add_glyph_helpers_to(
97
+ {
98
+ char: '',
99
+ width: 0,
100
+ height: 0,
101
+ data: []
102
+ }
103
+ ).freeze
104
+ end
105
+
106
+
107
+ ##
108
+ # Gets the height of the font.
109
+ attr_reader :height
110
+
111
+ ##
112
+ # Gets a glyph for the specified character.
113
+ #
114
+ # +char+ should be a string containing the character.
115
+ #
116
+ # Glyphs are hashes that also include helper methods.
117
+ #
118
+ # g = {
119
+ # char: ' ',
120
+ # width: 2,
121
+ # height: 8,
122
+ # data: [[false,false],[false,false],[false,false],[false,false],[false,false],[false,false],[false,false],[false,false]]
123
+ # }
124
+ # g.char == g[:char]
125
+ # g.width == g[:width]
126
+ # g.height == g[:height]
127
+ # g.data == g[:data]
128
+ #
129
+ def glyph(char)
130
+ char = char.to_s
131
+ ch = char.getbyte(0).to_i
132
+ @glyphs[ch] || @nil_glyph
133
+ end
134
+
135
+ ##
136
+ # Gets the glyphs to draw the specified string with.
137
+ def glyphs(string)
138
+ string.to_s.chars.map { |char| glyph(char) }
139
+ end
140
+
141
+ ##
142
+ # Measures the specified string.
143
+ #
144
+ # If you supply a +max_width+ it will try to fit the string into the specified width.
145
+ #
146
+ # With a +max_width+ it returns [ width, height, lines ].
147
+ # Without a +max_width+ it returns [ width, height ].
148
+ def measure(string, max_width = -1)
149
+
150
+ # handle newlines properly.
151
+ if string.include?("\n")
152
+ w = 0
153
+ h = 0
154
+ lines = []
155
+ string.split("\n").each do |line|
156
+ w2, h2, lines2 = measure(line, max_width)
157
+ w = w2 if w2 > w
158
+ h += h2
159
+ lines += lines2 if lines2
160
+ end
161
+ return [ w, h, lines ] if max_width > 0
162
+ return [ w, h ]
163
+ end
164
+
165
+ # convert to string and replace all whitespace with actual spaces.
166
+ # we don't support tabs or care about carriage returns.
167
+ # we also want to reduce all whitespace sequences to a single space.
168
+ string = string.to_s.gsub(/\s/, ' ').gsub(/\s\s+/, ' ')
169
+
170
+ if max_width > 0
171
+ # we are trying to fit the string into a specific width.
172
+
173
+ # no need to measure an empty string.
174
+ return [ 0, height, [ string ]] if string == ''
175
+
176
+ # measure the initial string.
177
+ w, h = measure(string)
178
+
179
+ # we fit or there are no spaces to wrap on.
180
+ if w <= max_width || !string.include?(' ')
181
+ return [w, h, [ string ]]
182
+ else
183
+
184
+ # prepare to wrap on word boundaries.
185
+ cur_line,_,next_line = string.rpartition(' ')
186
+
187
+ # keep chopping off words until we can't chop any more off or we fit.
188
+ while true
189
+ # measure the current line.
190
+ w, h = measure(cur_line)
191
+
192
+ if w <= max_width || !cur_line.include?(' ')
193
+ # we fit or we can't split anymore.
194
+
195
+ # measure up the rest of the string.
196
+ w2, h2, lines = measure(next_line, max_width)
197
+
198
+ # and adjust the size as needed.
199
+ w = w2 if w2 > w
200
+ h += h2
201
+
202
+ # return the adjusted size and the lines.
203
+ return [ w, h, [ cur_line ] + lines ]
204
+ end
205
+
206
+ # chop off the next word.
207
+ cur_line,_,next_word = cur_line.rpartition(' ')
208
+
209
+ # add the chopped off word to the beginning of the next line.
210
+ next_line = next_word + ' ' + next_line
211
+ end
212
+ end
213
+
214
+
215
+ else
216
+ # we are not trying to fit the string.
217
+
218
+ # no need to measure an empty string.
219
+ return [ 0, height ] if string == ''
220
+
221
+ h = height
222
+ w = glyphs(string).inject(0) { |_w,g| _w + g[:width] }
223
+
224
+ [w, h]
225
+ end
226
+ end
227
+
228
+ ##
229
+ # Generates a hash that can be loaded into a font.
230
+ def inspect(formatted = false)
231
+ ret = '{'
232
+ ret += "\n " if formatted
233
+ ret += ":name => #{name.inspect},"
234
+ ret += "\n " if formatted
235
+ ret += ":size => #{size.inspect},"
236
+ ret += "\n " if formatted
237
+ ret += ":bold => #{bold.inspect},"
238
+ ret += "\n " if formatted
239
+ @glyphs.each do |key, glyph|
240
+ ret += "#{key.inspect} => {"
241
+ ret += "\n " if formatted
242
+ ret += ":char => #{glyph[:char].inspect},"
243
+ ret += "\n " if formatted
244
+ ret += ":width => #{glyph[:width].inspect},"
245
+ ret += "\n " if formatted
246
+ ret += ":height => #{glyph[:height].inspect},"
247
+ ret += "\n " if formatted
248
+ ret += ':data => ['
249
+ ret += "\n " if formatted
250
+ glyph[:data].each do |row|
251
+ ret += "#{row.map{|bit| bit ? '#' : ' '}.join('').inspect},"
252
+ ret += "\n " if formatted
253
+ end
254
+ ret = ret.rstrip + "\n " if formatted
255
+ ret += '],'
256
+ ret += "\n " if formatted
257
+ ret += '},'
258
+ ret += "\n " if formatted
259
+ end
260
+ ret = ret.rstrip + "\n" if formatted
261
+ ret + '}'
262
+ end
263
+
264
+ def to_s # :nodoc:
265
+ "#{name} #{bold ? 'Bold' : 'Regular'} #{size}pt"
266
+ end
267
+
268
+ def freeze # :nodoc
269
+ name.freeze
270
+ size.freeze
271
+ bold.freeze
272
+ super
273
+ end
274
+
275
+ ##
276
+ # Attempts to load and process a font.
277
+ #
278
+ # Returns a Font on success, or nil on failure.
279
+ #
280
+ # There is a possibility that it will load an incorrect font.
281
+ #
282
+ # REQUIRES: rmagick
283
+ #
284
+ # Since the BarkestLcd gem doesn't require 'rmagick', this will always return false by default.
285
+ # If your application includes the 'rmagick' gem, then you can create fonts. Or you can use an
286
+ # IRB console to create fonts.
287
+ #
288
+ # The created fonts should be stored as a ruby object. The constants defined in the BarkestLcd::Font class
289
+ # were created in this manner.
290
+ def self.create(font_name = 'Helvetica', size = 8, bold = false)
291
+ return nil if font_name.to_s == ''
292
+ return nil if size < 4 || size > 144
293
+
294
+ begin
295
+ require 'rmagick'
296
+
297
+ max_black = (Magick::QuantumRange * 0.5).to_i
298
+
299
+ img = Magick::Image.new(200,200)
300
+
301
+ def img.char_width
302
+ get_pixels(0, 0, columns, 1).each_with_index do |px, x|
303
+ return x if px.to_color == 'magenta'
304
+ end
305
+ nil
306
+ end
307
+
308
+ def img.char_height
309
+ get_pixels(0, 0, 1, rows).each_with_index do |px, y|
310
+ return y if px.to_color == 'magenta'
311
+ end
312
+ nil
313
+ end
314
+
315
+ img.background_color = 'magenta'
316
+ img.erase!
317
+
318
+ draw = Magick::Draw.new
319
+ draw.font = font_name
320
+ draw.font_weight bold ? 'bold' : 'normal'
321
+ draw.pointsize = size
322
+ draw.gravity = Magick::NorthWestGravity
323
+ draw.text_antialias = false
324
+ draw.fill = 'black'
325
+ draw.undercolor = 'white'
326
+ draw.stroke = 'transparent'
327
+
328
+ font = {
329
+ name: font_name,
330
+ size: size,
331
+ bold: !!bold,
332
+ }
333
+
334
+ " 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ(){}[]<>`~!@#$%^&*-_=+\\|;:'\",./?".chars.each do |ch|
335
+
336
+ glyph = {
337
+ char: ch
338
+ }
339
+
340
+ char_code = ch.getbyte(0)
341
+
342
+ ch = "\\\\" if ch == "\\"
343
+ ch = "\\\"" if ch == "\""
344
+
345
+ img.erase!
346
+ draw.annotate img, 0, 0, 0, 0, ch
347
+
348
+ width = img.char_width
349
+ height = img.char_height
350
+
351
+ if width.nil?
352
+ puts "Error on char #{ch.inspect}: no width"
353
+ elsif height.nil?
354
+ puts "Error on char #{ch.inspect}: no height"
355
+ else
356
+ glyph[:width] = width
357
+ glyph[:height] = height
358
+ glyph[:data] = []
359
+
360
+ img.get_pixels(0, 0, width, height).each_with_index do |px, index|
361
+ x = (index % width).to_i
362
+ y = (index / width).to_i
363
+
364
+ glyph[:data][y] ||= []
365
+ glyph[:data][y][x] = px.intensity <= max_black
366
+ end
367
+
368
+ font[char_code] = glyph
369
+ end
370
+
371
+ end
372
+
373
+ Font.new(font)
374
+ rescue LoadError
375
+ nil
376
+ end
377
+ end
378
+
379
+ private
380
+
381
+ def add_glyph_helpers_to(glyph)
382
+ def glyph.char
383
+ self[:char]
384
+ end
385
+ def glyph.width
386
+ self[:width]
387
+ end
388
+ def glyph.height
389
+ self[:height]
390
+ end
391
+ def glyph.data
392
+ self[:data]
393
+ end
394
+ glyph
395
+ end
396
+
397
+ end
398
+ end
399
+
400
+
401
+ # Include the components of the model.
402
+ Dir.glob(File.expand_path('../font/*.rb', __FILE__)).each do |file|
403
+ require file
404
+ end