less_curse 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/Gemfile +4 -0
- data/LICENSE +674 -0
- data/README.md +106 -0
- data/Rakefile +2 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/examples/3col.rb +52 -0
- data/examples/file_browser.rb +40 -0
- data/examples/grid.rb +49 -0
- data/examples/list.rb +46 -0
- data/examples/list_with_display_func.rb +26 -0
- data/examples/long_list.rb +21 -0
- data/examples/popup.rb +32 -0
- data/less_curse.gemspec +26 -0
- data/lib/less_curse.rb +76 -0
- data/lib/less_curse/actions.rb +7 -0
- data/lib/less_curse/geometry.rb +24 -0
- data/lib/less_curse/grid.rb +25 -0
- data/lib/less_curse/null_logger.rb +9 -0
- data/lib/less_curse/renderer.rb +31 -0
- data/lib/less_curse/screen.rb +147 -0
- data/lib/less_curse/version.rb +3 -0
- data/lib/less_curse/widgets.rb +4 -0
- data/lib/less_curse/widgets/base.rb +42 -0
- data/lib/less_curse/widgets/list.rb +108 -0
- data/lib/less_curse/widgets/text_area.rb +42 -0
- data/lib/less_curse/widgets/text_view.rb +15 -0
- metadata +114 -0
data/README.md
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
# LessCurse
|
2
|
+
|
3
|
+
Simple terminal-based ncurses UIs.
|
4
|
+
|
5
|
+
LessCurse is a work-in-progress, but it's unclear how much work and how much progress will happen/is needed.
|
6
|
+
|
7
|
+
It will probably just be developed enough to serve as a dead-simple TUI for the trackt time-tracking system.
|
8
|
+
|
9
|
+
I'm happy about bug reports and ideas, but chances are high that I cannot react in the way I wish others would do.
|
10
|
+
|
11
|
+
## License
|
12
|
+
|
13
|
+
LessCurse is released under the GPL, Version 3 or any later version and Copyright 2016 by Felix Wolfsteller.
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
Add this line to your application's Gemfile:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
gem 'lesscurse'
|
21
|
+
```
|
22
|
+
|
23
|
+
And then execute:
|
24
|
+
|
25
|
+
$ bundle
|
26
|
+
|
27
|
+
Or install it yourself as:
|
28
|
+
|
29
|
+
$ gem install lesscurse
|
30
|
+
|
31
|
+
## Usage
|
32
|
+
|
33
|
+
See the examples folder for now. Note that executing the examples creates a log file next to it (e.g. examples/list.rb.log).
|
34
|
+
It boils down to initialize a Screen, populate it with Widgets and create lambdas to react to events (like a list item was selected) or key-presses.
|
35
|
+
|
36
|
+
## Concepts
|
37
|
+
|
38
|
+
### Screen
|
39
|
+
|
40
|
+
There is only one screen and it will fill up your terminal. Access it via `LessCurse::screen` (or `LessCurse.screen`).
|
41
|
+
|
42
|
+
The screen can and should be populated by widgets. You cannot decide on exact positioning of widgets, as a *tiling*-approach is used. By default, the screen will split vertically and each widget will take up an equal amount of space. However, slightly more complex `Grid`-layouts are possible (see below).
|
43
|
+
|
44
|
+
Always one widget is focused.
|
45
|
+
|
46
|
+
The screen can handle global keyboard input (shortcuts, in LessCurse#actions). Currently, the TAB key is used to switch the focus of widgets. CTRL_Q will quit the application.
|
47
|
+
|
48
|
+
### The Grid
|
49
|
+
|
50
|
+
A Grid can be used if the screen is to be tiled not only vertically, but also horizontally. Each row will take up an equal amount of space, where each widget in a row shares the space within that row equally.
|
51
|
+
|
52
|
+
### Widgets
|
53
|
+
|
54
|
+
All (four... :)) widgets inherit from `LessCurse::Widgets::Base` and provide following methods:
|
55
|
+
|
56
|
+
- `new(title: "Shows on top", data: "Shows somewhere")` [creates instance]
|
57
|
+
- `set_default_actions` [populates the @action map (keys to lambdas)]
|
58
|
+
- `draw` [(re)draws the widget]
|
59
|
+
- `handle_input(key)` [deals with input, that will be handed on from main module if focused]
|
60
|
+
has to return true if key press was dealt with
|
61
|
+
|
62
|
+
The default look of a widget has a box drawn around it, with an optional title at the top.
|
63
|
+
|
64
|
+
#### List
|
65
|
+
|
66
|
+
Shows a list where a single item can be selected with the UP and DOWN array keys.
|
67
|
+
The list is thought to be able to gobble up **Lists of Objects** which can be selected and are then represented as `List.selected_data`.
|
68
|
+
The text which is shown per Object defaults to `object.to_s` but can be overridden by passing a lambda into `List.display_func`.
|
69
|
+
|
70
|
+
#### TextView
|
71
|
+
|
72
|
+
Shows text. Doesnt even scroll yet.
|
73
|
+
|
74
|
+
#### TextArea
|
75
|
+
|
76
|
+
Allows creepy text input.
|
77
|
+
|
78
|
+
#### Buttons
|
79
|
+
|
80
|
+
Buttons do not look very much like buttons yet, but can execute a lambda on keypress (typically `RETURN`) when focused.
|
81
|
+
|
82
|
+
### Popups
|
83
|
+
|
84
|
+
|
85
|
+
## Development
|
86
|
+
|
87
|
+
After checking out the repo, run `bundle` to install dependencies. You can also run `bundle console` for an interactive prompt that will allow you to experiment.
|
88
|
+
|
89
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
90
|
+
|
91
|
+
### Design goals and decisions
|
92
|
+
|
93
|
+
LessCurse aims to solve a specific problem and the library user (me) should be able to hack a UI with ease and without caring too much about configuration.
|
94
|
+
|
95
|
+
Thus, LessCurse is **very** oppinionated.
|
96
|
+
|
97
|
+
- No complex layouts, only screen-tiling.
|
98
|
+
- Few options.
|
99
|
+
- No way to get lost in visual design decisions.
|
100
|
+
|
101
|
+
Widgets are for now not directly aware of the underlying (ncurses) window, this is a design decision workaround and yet on purpose.
|
102
|
+
|
103
|
+
## Contributing
|
104
|
+
|
105
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/econya/less_curse.
|
106
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "lesscurse"
|
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
|
data/bin/setup
ADDED
data/examples/3col.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Released under the GPLv3+, Copyright 2016 Felix Wolfsteller
|
3
|
+
|
4
|
+
require 'less_curse'
|
5
|
+
|
6
|
+
LessCurse.log_to! $PROGRAM_NAME + ".log"
|
7
|
+
|
8
|
+
def list_ui
|
9
|
+
simple_list = [1, 2, 3, 4, 5, 6, 7]
|
10
|
+
|
11
|
+
list_ui = LessCurse::Widgets::List.new
|
12
|
+
list_ui.data = simple_list
|
13
|
+
list_ui.on_select = LessCurse::Actions::QUIT
|
14
|
+
list_ui
|
15
|
+
end
|
16
|
+
|
17
|
+
def hash_ui
|
18
|
+
simple_hash = {rainbows: "fabolous",
|
19
|
+
unicorns: "magnificient",
|
20
|
+
sunrise: "fantastic",
|
21
|
+
sunset: "marvellous"}
|
22
|
+
LessCurse::Widgets::List.new data: simple_hash, title: "Hash keys"
|
23
|
+
end
|
24
|
+
|
25
|
+
def textview_ui
|
26
|
+
LessCurse::Widgets::TextView.new title: 'TextView',
|
27
|
+
data: "Showing Text Data\nSecond Line of Text Data"
|
28
|
+
end
|
29
|
+
|
30
|
+
begin
|
31
|
+
# Display list items
|
32
|
+
LessCurse.screen.add list_ui
|
33
|
+
|
34
|
+
# Display hash items
|
35
|
+
LessCurse.screen.add hash_ui
|
36
|
+
|
37
|
+
# Display important information
|
38
|
+
LessCurse.screen.add textview_ui
|
39
|
+
|
40
|
+
# Display it, really
|
41
|
+
LessCurse.show_screen
|
42
|
+
|
43
|
+
# Deal with keyboard input and keep us living
|
44
|
+
LessCurse.enter_loop!
|
45
|
+
|
46
|
+
# Try to debug
|
47
|
+
LessCurse.screen.windows.each do |widget, window|
|
48
|
+
puts "#{widget} -> #{window}"
|
49
|
+
end
|
50
|
+
ensure
|
51
|
+
LessCurse.close_screen
|
52
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Released under the GPLv3+, Copyright 2016 Felix Wolfsteller
|
3
|
+
|
4
|
+
require 'less_curse'
|
5
|
+
|
6
|
+
viewer = LessCurse::Widgets::TextView.new title: 'File Content',
|
7
|
+
data: 'Use TAB to change focus\n'\
|
8
|
+
'UP/DOWN to select file\n'\
|
9
|
+
'CTRL+Q to quit.'
|
10
|
+
|
11
|
+
file_list_ui = LessCurse::Widgets::List.new title: 'Files:',
|
12
|
+
data: Dir['*']
|
13
|
+
file_list_ui.on_select = lambda do |selected_file|
|
14
|
+
viewer.title = selected_file
|
15
|
+
if File.file? selected_file
|
16
|
+
viewer.data = File.new(selected_file).readlines
|
17
|
+
else
|
18
|
+
viewer.data = "[Probably a directory]"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
begin
|
23
|
+
# Display file list
|
24
|
+
LessCurse.screen.add file_list_ui
|
25
|
+
|
26
|
+
# Display file content (once file selected)
|
27
|
+
LessCurse.screen.add viewer
|
28
|
+
|
29
|
+
# Show off with header and footer
|
30
|
+
LessCurse.screen.header = "-- ** filebrowser ** --"
|
31
|
+
LessCurse.screen.footer = "[CTRL_Q to quit]"
|
32
|
+
|
33
|
+
# Display it, really
|
34
|
+
LessCurse.show_screen
|
35
|
+
|
36
|
+
# Deal with keyboard input and keep us living
|
37
|
+
LessCurse.enter_loop!
|
38
|
+
ensure
|
39
|
+
LessCurse.close_screen
|
40
|
+
end
|
data/examples/grid.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Released under the GPLv3+, Copyright 2016 Felix Wolfsteller
|
3
|
+
|
4
|
+
require 'less_curse'
|
5
|
+
|
6
|
+
LessCurse.log_to! $PROGRAM_NAME + ".log"
|
7
|
+
|
8
|
+
def list_ui
|
9
|
+
simple_list = [1, 2, 3, 4, 5, 6, 7]
|
10
|
+
|
11
|
+
list_ui = LessCurse::Widgets::List.new
|
12
|
+
list_ui.data = simple_list
|
13
|
+
list_ui.on_select = LessCurse::Actions::QUIT
|
14
|
+
list_ui
|
15
|
+
end
|
16
|
+
|
17
|
+
def hash_ui
|
18
|
+
simple_hash = {rainbows: "fabolous",
|
19
|
+
unicorns: "magnificient",
|
20
|
+
sunrise: "fantastic",
|
21
|
+
sunset: "marvellous"}
|
22
|
+
LessCurse::Widgets::List.new data: simple_hash, title: "Hash keys"
|
23
|
+
end
|
24
|
+
|
25
|
+
def textview_ui
|
26
|
+
LessCurse::Widgets::TextView.new title: 'TextView',
|
27
|
+
data: "Showing Text Data\nSecond Line of Text Data"
|
28
|
+
end
|
29
|
+
|
30
|
+
begin
|
31
|
+
grid = LessCurse::Grid.new [[list_ui, hash_ui],
|
32
|
+
[textview_ui]]
|
33
|
+
# Set grid
|
34
|
+
LessCurse.screen.add grid
|
35
|
+
|
36
|
+
# Display it, really
|
37
|
+
LessCurse.show_screen
|
38
|
+
|
39
|
+
# Deal with keyboard input and keep us living
|
40
|
+
LessCurse.enter_loop!
|
41
|
+
|
42
|
+
# Try to debug
|
43
|
+
LessCurse.screen.windows.each do |widget, window|
|
44
|
+
puts "#{widget} -> #{window}"
|
45
|
+
end
|
46
|
+
ensure
|
47
|
+
LessCurse.close_screen
|
48
|
+
end
|
49
|
+
|
data/examples/list.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Released under the GPLv3+, Copyright 2016 Felix Wolfsteller
|
3
|
+
|
4
|
+
require 'less_curse'
|
5
|
+
|
6
|
+
LessCurse.log_to! $PROGRAM_NAME + ".log"
|
7
|
+
|
8
|
+
begin
|
9
|
+
# Display list items
|
10
|
+
simple_list = [1, 2, 3, 4, 5, 6, 7]
|
11
|
+
|
12
|
+
list_ui = LessCurse::Widgets::List.new
|
13
|
+
list_ui.data = simple_list
|
14
|
+
list_ui.on_select = LessCurse::Actions::QUIT
|
15
|
+
|
16
|
+
LessCurse.screen.add list_ui
|
17
|
+
LessCurse.show_screen
|
18
|
+
LessCurse.enter_loop!
|
19
|
+
|
20
|
+
# Display hash items
|
21
|
+
simple_hash = {rainbows: "fabolous",
|
22
|
+
unicorns: "magnificient",
|
23
|
+
sunrise: "fantastic",
|
24
|
+
sunset: "marvellous"}
|
25
|
+
|
26
|
+
list_ui.data = simple_hash
|
27
|
+
list_ui.title = "Hash keys"
|
28
|
+
|
29
|
+
textview_ui = LessCurse::Widgets::TextView.new title: 'TextView',
|
30
|
+
data: "Showing Text Data\nSecond Line of Text Data"
|
31
|
+
|
32
|
+
list_ui.on_select = lambda do |selected_item|
|
33
|
+
textview_ui.data = "Selected: #{selected_item}"
|
34
|
+
end
|
35
|
+
|
36
|
+
LessCurse.screen.add textview_ui
|
37
|
+
LessCurse.show_screen
|
38
|
+
LessCurse.enter_loop!
|
39
|
+
|
40
|
+
LessCurse.screen.windows.each do |widget, window|
|
41
|
+
puts "#{widget} -> #{window}"
|
42
|
+
end
|
43
|
+
|
44
|
+
ensure
|
45
|
+
LessCurse.close_screen
|
46
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Released under the GPLv3+, Copyright 2016 Felix Wolfsteller
|
3
|
+
|
4
|
+
require 'less_curse'
|
5
|
+
|
6
|
+
LessCurse.log_to! $PROGRAM_NAME + ".log"
|
7
|
+
|
8
|
+
begin
|
9
|
+
# Display hash items
|
10
|
+
list_ui = LessCurse::Widgets::List.new
|
11
|
+
simple_hash = {rainbows: "fabolous",
|
12
|
+
unicorns: "magnificient",
|
13
|
+
sunrise: "fantastic",
|
14
|
+
sunset: "marvellous"}
|
15
|
+
|
16
|
+
list_ui.data = simple_hash
|
17
|
+
list_ui.title = "Hash keys"
|
18
|
+
|
19
|
+
list_ui.display_func = lambda {|i| i[0].to_s}
|
20
|
+
|
21
|
+
LessCurse.screen.add list_ui
|
22
|
+
LessCurse.show_screen
|
23
|
+
LessCurse.enter_loop!
|
24
|
+
ensure
|
25
|
+
LessCurse.close_screen
|
26
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Released under the GPLv3+, Copyright 2016 Felix Wolfsteller
|
3
|
+
|
4
|
+
require 'less_curse'
|
5
|
+
|
6
|
+
LessCurse.log_to! $PROGRAM_NAME + ".log"
|
7
|
+
|
8
|
+
begin
|
9
|
+
# Display list items
|
10
|
+
simple_list = (1..200).to_a
|
11
|
+
|
12
|
+
list_ui = LessCurse::Widgets::List.new
|
13
|
+
list_ui.data = simple_list
|
14
|
+
list_ui.on_select = LessCurse::Actions::QUIT
|
15
|
+
|
16
|
+
LessCurse.screen.add list_ui
|
17
|
+
LessCurse.show_screen
|
18
|
+
LessCurse.enter_loop!
|
19
|
+
ensure
|
20
|
+
LessCurse.close_screen
|
21
|
+
end
|
data/examples/popup.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Released under the GPLv3+, Copyright 2016 Felix Wolfsteller
|
3
|
+
|
4
|
+
require 'less_curse'
|
5
|
+
|
6
|
+
LessCurse.log_to! $PROGRAM_NAME + ".log"
|
7
|
+
|
8
|
+
begin
|
9
|
+
# Initialize Widgets
|
10
|
+
list_ui = LessCurse::Widgets::List.new
|
11
|
+
list_ui.data = [1, 2, 3, 4, 5, 6, 7]
|
12
|
+
|
13
|
+
button = LessCurse::Widgets::Button.new title: "Square that number in a popup"
|
14
|
+
button.on_press = lambda do |b|
|
15
|
+
selection_square = list_ui.selected_data * list_ui.selected_data
|
16
|
+
LessCurse.screen.show_popup :info, "#{selection_square}!"
|
17
|
+
end
|
18
|
+
|
19
|
+
# Display list items
|
20
|
+
LessCurse.screen.add list_ui
|
21
|
+
|
22
|
+
# Display button
|
23
|
+
LessCurse.screen.add button
|
24
|
+
|
25
|
+
# Display it, really
|
26
|
+
LessCurse.show_screen
|
27
|
+
|
28
|
+
# Deal with keyboard input and keep us living
|
29
|
+
LessCurse.enter_loop!
|
30
|
+
ensure
|
31
|
+
LessCurse.close_screen
|
32
|
+
end
|
data/less_curse.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'less_curse/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "less_curse"
|
8
|
+
spec.version = LessCurse::VERSION
|
9
|
+
spec.authors = ["Felix Wolfsteller"]
|
10
|
+
spec.email = ["felix.wolfsteller@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{ncurses abstraction layer for terminal-based applications.}
|
13
|
+
spec.description = %q{LessCurse is a ncurses abstraction layer for terminal-based user interfaces.}
|
14
|
+
spec.homepage = "https://github.com/econya/less_curse"
|
15
|
+
spec.license = "GPLv3+"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_dependency "ffi-ncurses", '~> 0.4'
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.11"
|
25
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
+
end
|
data/lib/less_curse.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'ffi-ncurses'
|
3
|
+
|
4
|
+
require 'less_curse/version'
|
5
|
+
require 'less_curse/geometry'
|
6
|
+
require 'less_curse/screen'
|
7
|
+
require 'less_curse/grid'
|
8
|
+
require 'less_curse/actions'
|
9
|
+
require 'less_curse/null_logger'
|
10
|
+
|
11
|
+
require "less_curse/renderer"
|
12
|
+
|
13
|
+
require "less_curse/widgets"
|
14
|
+
|
15
|
+
# The LessCurse module which gives you static access to your "Screen" and
|
16
|
+
# all the UI glory within it.
|
17
|
+
module LessCurse
|
18
|
+
@@actions = {
|
19
|
+
FFI::NCurses::KEY_TAB => lambda { screen.focus_next },
|
20
|
+
FFI::NCurses::KEY_BTAB => lambda { screen.focus_previous }
|
21
|
+
}
|
22
|
+
|
23
|
+
def self.screen
|
24
|
+
@@screen ||= Screen.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.show_screen
|
28
|
+
@@screen.show
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.close_screen
|
32
|
+
FFI::NCurses.endwin
|
33
|
+
# flushinp, delwin?
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.window rectangle
|
37
|
+
FFI::NCurses.newwin rectangle.size.height, rectangle.size.width,
|
38
|
+
rectangle.position.y, rectangle.position.x
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.enter_loop!
|
42
|
+
loop do
|
43
|
+
break if !handle_input
|
44
|
+
screen.repaint
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Read from keyboard, handle keypress oneself or dispatch
|
49
|
+
# to focused widget.
|
50
|
+
def self.handle_input
|
51
|
+
key = FFI::NCurses.wgetch FFI::NCurses::stdscr
|
52
|
+
debug_msg "key press: #{key} / keyname: #{FFI::NCurses::keyname key} / #{FFI::NCurses::KEY_CTRL_Q}"
|
53
|
+
|
54
|
+
# That will let us break out of the loop
|
55
|
+
return false if key == FFI::NCurses::KEY_CTRL_Q
|
56
|
+
|
57
|
+
if @@actions[key]
|
58
|
+
# Global actions first
|
59
|
+
@@actions[key].call
|
60
|
+
else
|
61
|
+
screen.focused_widget.handle_input key
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.debug_msg msg
|
66
|
+
logger.debug msg
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.logger
|
70
|
+
@@logger ||= LessCurse::NullLogger.new
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.log_to! file
|
74
|
+
@@logger = Logger.new(file)
|
75
|
+
end
|
76
|
+
end
|