rubyshelltools 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +101 -0
- data/assets/docs/examples.md +92 -0
- data/bin/rst +11 -0
- data/lib/core_extensions/numeric.rb +75 -0
- data/lib/errors/store_errors.rb +6 -0
- data/lib/load.rb +37 -0
- data/lib/modules/calendar/calendar.rb +155 -0
- data/lib/modules/calendar/calendar_event.rb +30 -0
- data/lib/modules/calendar/eventable.rb +50 -0
- data/lib/modules/persistent/disk_store.rb +90 -0
- data/lib/modules/persistent/memory_store.rb +37 -0
- data/lib/modules/persistent/persistent.rb +36 -0
- data/lib/modules/persistent/store.rb +118 -0
- data/lib/rst.rb +295 -0
- data/rst.rb +31 -0
- metadata +111 -0
data/README.md
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
Ruby Shell Tools
|
2
|
+
================
|
3
|
+
|
4
|
+
RST Version 0.0.1 is an experimental Ruby2-gem by Andreas Altendorfer <andreas@altendorfer.at>
|
5
|
+
|
6
|
+
Install from rubygems.org
|
7
|
+
-------------------------
|
8
|
+
|
9
|
+
gem install rubyshelltools
|
10
|
+
|
11
|
+
|
12
|
+
Clone from Github
|
13
|
+
-----------------
|
14
|
+
|
15
|
+
You can clone the project from **[Github](https://github.com/iboard/rst)**
|
16
|
+
|
17
|
+
git clone git://github.com/iboard/rst.git
|
18
|
+
|
19
|
+
### First steps ...
|
20
|
+
|
21
|
+
After downloading the project you can do
|
22
|
+
|
23
|
+
cd rst
|
24
|
+
bundle # install required Gems
|
25
|
+
rake # Run all specs
|
26
|
+
rake build # build the gem
|
27
|
+
rake install # build and install gem
|
28
|
+
|
29
|
+
|
30
|
+
Usage
|
31
|
+
-----
|
32
|
+
|
33
|
+
see file EXAMPLES.md:
|
34
|
+
|
35
|
+
* [At Github](https://github.com/iboard/rst/blob/master/assets/docs/examples.md#examples)
|
36
|
+
* [loacal copy](./file.examples.html)
|
37
|
+
|
38
|
+
or run
|
39
|
+
|
40
|
+
bin/rst --examples
|
41
|
+
|
42
|
+
if you have done `rake install` you don't have to use `bin/rst` in
|
43
|
+
the project-directory but you can use `rst ...` directly from the shell.
|
44
|
+
|
45
|
+
Documentation
|
46
|
+
-------------
|
47
|
+
|
48
|
+
run `yard; open doc/index.html` to build the rdocs and open
|
49
|
+
it in your browser.
|
50
|
+
|
51
|
+
The documentation can be found also at
|
52
|
+
[dav.iboard.cc](http://dav.iboard.cc/container/rst-doc)
|
53
|
+
|
54
|
+
TDD
|
55
|
+
---
|
56
|
+
|
57
|
+
And as always we are **green**
|
58
|
+
|
59
|
+
You can find the current output of [simplecov][] at [dav.iboard.cc](http://dav.iboard.cc/container/rst-coverage)
|
60
|
+
|
61
|
+
|
62
|
+
> $ rake
|
63
|
+
>
|
64
|
+
> Finished in _a few_ seconds
|
65
|
+
>
|
66
|
+
> _n_ examples, 0 failures
|
67
|
+
>
|
68
|
+
> Coverage report generated for RSpec to rst/coverage. _n_ / _n+-0_ LOC (**100.0%**) covered.
|
69
|
+
>
|
70
|
+
> $ yard
|
71
|
+
>
|
72
|
+
> Files: _n_
|
73
|
+
>
|
74
|
+
> Modules: _n_ ( 0 undocumented)
|
75
|
+
>
|
76
|
+
> Classes: _n_ ( 0 undocumented)
|
77
|
+
>
|
78
|
+
> Constants: _n_ ( 0 undocumented)
|
79
|
+
>
|
80
|
+
> Methods: _n_ ( 0 undocumented)
|
81
|
+
>
|
82
|
+
> **100.00% documented**
|
83
|
+
|
84
|
+
|
85
|
+
License
|
86
|
+
=======
|
87
|
+
|
88
|
+
This is free software
|
89
|
+
---------------------
|
90
|
+
|
91
|
+
Use it without restrications and on your own risk.
|
92
|
+
Leaving the copyright is appreciated, though.
|
93
|
+
|
94
|
+
|
95
|
+
Copyright
|
96
|
+
---------
|
97
|
+
|
98
|
+
(c) 2013 by Andreas Altendorfer
|
99
|
+
|
100
|
+
|
101
|
+
[simplecov]: http://github.com/colszowka/simplecov
|
@@ -0,0 +1,92 @@
|
|
1
|
+
EXAMPLES
|
2
|
+
========
|
3
|
+
|
4
|
+
rst --help
|
5
|
+
----------
|
6
|
+
|
7
|
+
Print out available options and commands
|
8
|
+
|
9
|
+
$ rst --help
|
10
|
+
Usage: rst [COMMAND [COMMAND ....]] [options] [FILES..]
|
11
|
+
|
12
|
+
Options:
|
13
|
+
-v, --[no-]verbose Run verbosely
|
14
|
+
--examples Show some usage examples
|
15
|
+
-h, --help Print help
|
16
|
+
-f, --from DATE Set from-date
|
17
|
+
-t, --to DATE Set to-date
|
18
|
+
-n, --name NAME Use this name for the command
|
19
|
+
-e, --new-event DATE,STRING Add an event
|
20
|
+
--list-calendars List available calendars
|
21
|
+
--delete-calendar CALENDARNAME
|
22
|
+
Delete an calendar and all it's entries!
|
23
|
+
--[no-]empty Show empty entries
|
24
|
+
Commands:
|
25
|
+
nil .......... no command. Interpret options only (useful in combination with -v)
|
26
|
+
ls ........... list directory and files
|
27
|
+
cal[endar] ... print a calendar --from --to
|
28
|
+
|
29
|
+
use --example for a more detailed list of commands.
|
30
|
+
|
31
|
+
rst ls *
|
32
|
+
--------
|
33
|
+
|
34
|
+
List directory and files
|
35
|
+
|
36
|
+
$ rst ls *
|
37
|
+
Gemfile Gemfile.lock README.md Rakefile assets bin lib
|
38
|
+
rst-0.0.0.gem rst.gemspec rst.rb specs
|
39
|
+
|
40
|
+
|
41
|
+
rst [nil] -v SOME FILES HERE
|
42
|
+
-----------------------------
|
43
|
+
|
44
|
+
show the arguments and options parsed from command-line
|
45
|
+
|
46
|
+
$ rst -v SOME FILES HERE
|
47
|
+
Binary : /Users/aa/.rvm/gems/ruby-head@ruby2/bin/rst
|
48
|
+
Command:
|
49
|
+
Options: [:verbose, true]
|
50
|
+
Files: SOME, FILES, HERE
|
51
|
+
|
52
|
+
rst calendar
|
53
|
+
------------
|
54
|
+
|
55
|
+
RST supports a (very) simple calendar-function. You can store (all-day)events for a given date.
|
56
|
+
The calendar can be stored persistently in a PStore file. The default
|
57
|
+
location of the file is within the GEM-path/data/development. You can
|
58
|
+
overwrite this by defining env-vars for RST_DATA and RST_ENV
|
59
|
+
|
60
|
+
$ export RST_DATA=$HOME/.rst/data
|
61
|
+
$ export RST_ENV=production
|
62
|
+
$ rst cal --new-event='1964-08-31,Birthday Andreas Altendorfer'
|
63
|
+
$ rst cal -e 'today,Win the jackpot' # e is an abbreviation for --new-event
|
64
|
+
$ rst cal -f 1964/1 # f is an abbreviation for --from
|
65
|
+
Mon, Aug 31 1964: Birthday Andreas Altendorfer
|
66
|
+
Fri, Mar 15 2013: Win the jackpot
|
67
|
+
$ find $HOME/.rst
|
68
|
+
/Users/you/.rst
|
69
|
+
/Users/you/.rst/data
|
70
|
+
/Users/you/.rst/data/production
|
71
|
+
/Users/you/.rst/data/production/CALENDAR_FILE
|
72
|
+
|
73
|
+
$ rst --list-calendars
|
74
|
+
birthdays : 5 entries
|
75
|
+
work : 2 entries
|
76
|
+
|
77
|
+
|
78
|
+
The full set of parameters is
|
79
|
+
|
80
|
+
$ rst calendar --from='1964-01-01' --to='today' --empty
|
81
|
+
Wed, Jan 01 1964:
|
82
|
+
Thu, Jan 02 1964:
|
83
|
+
Fri, Jan 03 1964:
|
84
|
+
Sat, Jan 04 1964:
|
85
|
+
#.... some other empty lines
|
86
|
+
Mon, Aug 31 1964: Birthday Andreas Altendorfer
|
87
|
+
# .... some thousand other empty lines
|
88
|
+
Fri, Mar 15 2013: Win the jackpot
|
89
|
+
|
90
|
+
* --empty ...... show empty lines
|
91
|
+
* --no-empty ... show no empty lines (default)
|
92
|
+
|
data/bin/rst
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# Monkey-patch the numeric class to support n.years,days,... as Rails do
|
2
|
+
class Numeric
|
3
|
+
|
4
|
+
#:nodoc:
|
5
|
+
SECOND = 1
|
6
|
+
#:nodoc:
|
7
|
+
SECONDS = SECOND
|
8
|
+
#:nodoc:
|
9
|
+
MINUTES = 60
|
10
|
+
#:nodoc:
|
11
|
+
MINUTE = MINUTES
|
12
|
+
#:nodoc:
|
13
|
+
HOURS = MINUTES * 60
|
14
|
+
#:nodoc:
|
15
|
+
HOUR = HOURS
|
16
|
+
#:nodoc:
|
17
|
+
DAYS = HOUR * 24
|
18
|
+
#:nodoc:
|
19
|
+
DAY = DAYS
|
20
|
+
#:nodoc:
|
21
|
+
WEEKS = DAY * 7
|
22
|
+
#:nodoc:
|
23
|
+
WEEK = WEEKS
|
24
|
+
#:nodoc:
|
25
|
+
MONTHS = DAY * 30.5
|
26
|
+
#:nodoc:
|
27
|
+
MONTH = MONTHS
|
28
|
+
#:nodoc:
|
29
|
+
YEAR = DAY * 365.25
|
30
|
+
#:nodoc:
|
31
|
+
YEARS = YEAR
|
32
|
+
|
33
|
+
#:nodoc:
|
34
|
+
def years
|
35
|
+
(self * YEAR).to_f
|
36
|
+
end
|
37
|
+
alias :year :years
|
38
|
+
|
39
|
+
#:nodoc:
|
40
|
+
def months
|
41
|
+
(self * MONTH).to_f
|
42
|
+
end
|
43
|
+
alias :month :months
|
44
|
+
|
45
|
+
#:nodoc:
|
46
|
+
def weeks
|
47
|
+
(self * WEEK).to_f
|
48
|
+
end
|
49
|
+
alias :week :weeks
|
50
|
+
|
51
|
+
#:nodoc:
|
52
|
+
def days
|
53
|
+
(self * DAYS).to_f
|
54
|
+
end
|
55
|
+
alias :day :days
|
56
|
+
|
57
|
+
#:nodoc:
|
58
|
+
def hours
|
59
|
+
(self * HOUR).to_f
|
60
|
+
end
|
61
|
+
alias :hour :hours
|
62
|
+
|
63
|
+
#:nodoc:
|
64
|
+
def minutes
|
65
|
+
(self * MINUTE).to_f
|
66
|
+
end
|
67
|
+
alias :minute :minutes
|
68
|
+
|
69
|
+
#:nodoc:
|
70
|
+
def seconds
|
71
|
+
(self * SECONDS).to_f
|
72
|
+
end
|
73
|
+
alias :second :seconds
|
74
|
+
|
75
|
+
end
|
data/lib/load.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
unless defined? LIB_LOADED
|
2
|
+
|
3
|
+
# prevent from double loading
|
4
|
+
LIB_LOADED = true
|
5
|
+
|
6
|
+
# Used with strftime(DEFAULT_DATE_FORMAT) when printing dates
|
7
|
+
DEFAULT_DATE_FORMAT = '%a, %b %d %Y'
|
8
|
+
|
9
|
+
# Filename for the calendar-store
|
10
|
+
CALENDAR_FILE = 'calendars.data'
|
11
|
+
|
12
|
+
require File.expand_path('../../rst',__FILE__)
|
13
|
+
|
14
|
+
# Load all necessary files
|
15
|
+
$LOAD_PATH.unshift(File.expand_path('..',__FILE__))
|
16
|
+
|
17
|
+
$LOAD_PATH.unshift(File.expand_path('../core_extensions',__FILE__))
|
18
|
+
require 'numeric'
|
19
|
+
|
20
|
+
$LOAD_PATH.unshift(File.expand_path('../errors',__FILE__))
|
21
|
+
require 'store_errors'
|
22
|
+
|
23
|
+
$LOAD_PATH.unshift(File.expand_path('../modules/calendar',__FILE__))
|
24
|
+
require 'calendar'
|
25
|
+
require 'eventable'
|
26
|
+
require 'calendar_event'
|
27
|
+
|
28
|
+
$LOAD_PATH.unshift(File.expand_path('../modules/persistent',__FILE__))
|
29
|
+
require 'persistent'
|
30
|
+
require 'store'
|
31
|
+
require 'memory_store'
|
32
|
+
require 'disk_store'
|
33
|
+
|
34
|
+
|
35
|
+
include RST
|
36
|
+
end
|
37
|
+
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'modules/persistent/persistent'
|
3
|
+
|
4
|
+
module RST
|
5
|
+
|
6
|
+
# Calendar-module provides the Calendar-class which is supposed to hold
|
7
|
+
# 'Eventable'-objects. Where Eventable is a module you can include to any class.
|
8
|
+
# A Calendar has a start- and ending-date, and a name. The name is used
|
9
|
+
# to store the calendar in a store using this name.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
#
|
13
|
+
# class Person
|
14
|
+
# include Eventable
|
15
|
+
#
|
16
|
+
# def initialize name, dob
|
17
|
+
# @name = name
|
18
|
+
# schedule! dob
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# # override abstract method to format the output
|
22
|
+
# def event_headline
|
23
|
+
# '#{name}\'s Birthday'
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# birthdays = Calendar.new('birthdays')
|
28
|
+
# birthdays << Person.new('Andi', '31. Aug. 1964')
|
29
|
+
# birthdays << Person.new('Heidi', '28. Aug. 1969')
|
30
|
+
# birthdays << Person.new('Julian', '17. Feb. 1995')
|
31
|
+
# birthdays << Person.new('Tina', '22. May. 1997')
|
32
|
+
#
|
33
|
+
# puts birthdays.list_days('31.8.1963','today')
|
34
|
+
# # => Mon, Aug 31 1964: Andi's Birthday
|
35
|
+
# # => Thu, Aug 28 1969: Heidi's Birthday
|
36
|
+
# # => Fri, Feb 17 1995: Julian's Birthday
|
37
|
+
# # => Thu, May 22 1997: Tina's Birthday
|
38
|
+
#
|
39
|
+
module Calendar
|
40
|
+
|
41
|
+
# Methods useful when dealing with Dates
|
42
|
+
module CalendarHelper
|
43
|
+
|
44
|
+
# You can use 'today' or any format which Date.parse can handle.
|
45
|
+
# @param [nil|String|Time|Date] param
|
46
|
+
# @return [Date] always returns a Date regardless of the type of input
|
47
|
+
def ensure_date(param)
|
48
|
+
if param.is_a?(Date) || param.is_a?(::Time)
|
49
|
+
param
|
50
|
+
elsif param =~ /today/i || param.nil?
|
51
|
+
Date.today
|
52
|
+
else
|
53
|
+
Date.parse(param)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Calendar has a name and will be stored in Persistent::DiskStore(CALENDAR_FILE)
|
59
|
+
# with it's name as the id. Thus you can save different calendars in
|
60
|
+
# the same file. If no name is given 'unnamed' will be the default.
|
61
|
+
class Calendar
|
62
|
+
|
63
|
+
include Persistent::Persistentable
|
64
|
+
include CalendarHelper
|
65
|
+
|
66
|
+
attr_reader :name, :start_date, :end_date, :events
|
67
|
+
|
68
|
+
# @param [String] _name Name and persistent-id of this calendar.
|
69
|
+
# @param [Date|Time|String] _start The date when the calendar starts
|
70
|
+
# @param [Date|Time|String] _end The date when the calendar ends
|
71
|
+
def initialize(_name='unnamed', _start=nil, _end=nil, _events=[])
|
72
|
+
@name = _name
|
73
|
+
@start_date = parse_date_param(_start)
|
74
|
+
@end_date = parse_date_param(_end)
|
75
|
+
@events = _events
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
# Setter for from_date
|
80
|
+
# @param [Date|String] _from - new starting date
|
81
|
+
def from=(_from)
|
82
|
+
@start_date = parse_date_param(_from)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Setter for to-date
|
86
|
+
# @param [Date|String] _to - new ending date
|
87
|
+
def to=(_to)
|
88
|
+
@end_date = parse_date_param(_to)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Override Persistentable's id-method
|
92
|
+
# @return [String] - the calendar-name is it's id
|
93
|
+
def id
|
94
|
+
@name || super
|
95
|
+
end
|
96
|
+
|
97
|
+
# Add Eventables to the calendar
|
98
|
+
# @param [Eventable] add - the Object to add
|
99
|
+
def <<(add)
|
100
|
+
events << add
|
101
|
+
end
|
102
|
+
|
103
|
+
# Calculate the span between start and end in seconds
|
104
|
+
# @return [Float]
|
105
|
+
def span
|
106
|
+
((end_date - start_date)*Numeric::DAYS).to_f
|
107
|
+
end
|
108
|
+
|
109
|
+
# Array of strings for each day with events on it.
|
110
|
+
#
|
111
|
+
# @example
|
112
|
+
# Mon, Aug 31 1964: Birthday Andreas Altendorfer
|
113
|
+
#
|
114
|
+
# @param [String|Date] start_on - output begins on this day
|
115
|
+
# @param [String|Date] end_on - output ends on this day
|
116
|
+
# @param [Boolean] show_empty - output days with no events
|
117
|
+
# @return [Array] of Strings DATE: EVENT + EVENT + ....
|
118
|
+
def list_days(start_on=start_date,end_on=end_date,show_empty=false)
|
119
|
+
(parse_date_param(start_on)..parse_date_param(end_on)).to_a.map { |_date|
|
120
|
+
format_events_for(_date,show_empty)
|
121
|
+
}.compact
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
private
|
126
|
+
# List Event-headlines for a given date
|
127
|
+
# @param [Date] date
|
128
|
+
# @return [Array] of CalendarEvents
|
129
|
+
def events_on(date)
|
130
|
+
events.select { |event| event.event_date == date }
|
131
|
+
end
|
132
|
+
|
133
|
+
# Convert strings to a date
|
134
|
+
# @param [Date|Time|String] param - default is 'today'
|
135
|
+
# @return [Date|Time]
|
136
|
+
def parse_date_param(param=Date.today)
|
137
|
+
ensure_date(param)
|
138
|
+
end
|
139
|
+
|
140
|
+
# Output date and Events on this date in one line
|
141
|
+
# @param [Date] _date
|
142
|
+
# @param [Boolean] show_empty - do not output lines with no events
|
143
|
+
# @return [String]
|
144
|
+
def format_events_for(_date,show_empty=false)
|
145
|
+
if show_empty ||
|
146
|
+
(_events = events_on(_date).map(&:event_headline).join(' + ').strip) != ''
|
147
|
+
[_date.strftime(DEFAULT_DATE_FORMAT), _events].compact.join(": ")
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module RST
|
2
|
+
|
3
|
+
module Calendar
|
4
|
+
|
5
|
+
# A CalendarEvent happens on a given date and
|
6
|
+
# has a label. It's supposed to be appended to a Calendar-object
|
7
|
+
# @see Calendar
|
8
|
+
class CalendarEvent
|
9
|
+
|
10
|
+
attr_reader :event_date, :label
|
11
|
+
|
12
|
+
include Eventable
|
13
|
+
include CalendarHelper
|
14
|
+
|
15
|
+
# @param [String|Date] _date - Date of the event (only all-day-events are possible yet)
|
16
|
+
# @param [String] _label - Events name
|
17
|
+
def initialize(_date,_label)
|
18
|
+
@event_date = ensure_date(_date)
|
19
|
+
@label = _label
|
20
|
+
end
|
21
|
+
|
22
|
+
# override abstract method for an Eventable
|
23
|
+
def event_headline
|
24
|
+
self.label.to_s.strip
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module RST
|
2
|
+
module Calendar
|
3
|
+
|
4
|
+
# Inject Eventable to any Class to make it event-able in a Calendar
|
5
|
+
# You have to define a method :event_headline for the class
|
6
|
+
# in order to list the event in a Calendar
|
7
|
+
# @example
|
8
|
+
#
|
9
|
+
# class Birthday < Struct.new(:name,:dob)
|
10
|
+
# include Eventable
|
11
|
+
# def initialize(*args)
|
12
|
+
# super
|
13
|
+
# schedule!(dob)
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# protected
|
17
|
+
# def event_headline
|
18
|
+
# "It's #{name}'s Birthday"
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# @see RST::Calendar::Calendar
|
23
|
+
module Eventable
|
24
|
+
|
25
|
+
# @return [Date] the date when the event is scheduled
|
26
|
+
def event_date
|
27
|
+
@event_date
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [Boolean] true if the object is scheduled (has an event_date)
|
31
|
+
def scheduled?
|
32
|
+
!event_date.nil?
|
33
|
+
end
|
34
|
+
|
35
|
+
# @param [Date|String] date schedule this object for the given date
|
36
|
+
# @return [Eventable] - self
|
37
|
+
def schedule!(date)
|
38
|
+
@event_date = date.is_a?(Date) ? date : Date.parse(date)
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
# used in calendar-output as a short entry
|
43
|
+
# @return [String]
|
44
|
+
def event_headline
|
45
|
+
"(untitled event)"
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module RST
|
2
|
+
module Persistent
|
3
|
+
|
4
|
+
# #DiskStore is responsible to save an Array of objects
|
5
|
+
# persistently to the disk - PStore is used to effort this.
|
6
|
+
# @see RST::Persistent::Store
|
7
|
+
# @api persistent
|
8
|
+
class DiskStore < Store
|
9
|
+
|
10
|
+
attr_reader :filename
|
11
|
+
|
12
|
+
# The first argument is the filename
|
13
|
+
# Following args are passed to base-class
|
14
|
+
# @example
|
15
|
+
#
|
16
|
+
# DiskStore.new('myfile')
|
17
|
+
# DiskStore.new(filename: 'myfile', other_option: '...')
|
18
|
+
#
|
19
|
+
# @param [String] _filename
|
20
|
+
# @param [Array] args - arguments passed to the super-class
|
21
|
+
def initialize(_filename='store.data',args={})
|
22
|
+
@filename = _filename
|
23
|
+
super(args)
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [String] full path of PStore-file
|
27
|
+
def path
|
28
|
+
@store.path
|
29
|
+
end
|
30
|
+
|
31
|
+
# @todo This is bad for performance and memory - refactor!
|
32
|
+
# @return [Array]
|
33
|
+
def all
|
34
|
+
_all = []
|
35
|
+
@store.transaction(true) do |s|
|
36
|
+
s.roots.each do |_r|
|
37
|
+
_all << s[_r]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
_all
|
41
|
+
end
|
42
|
+
|
43
|
+
# Delete the store
|
44
|
+
def delete!
|
45
|
+
File.unlink(store_path)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
# Initialize a PStore-instance
|
51
|
+
def setup_backend
|
52
|
+
@store = PStore.new(store_path)
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
# Determine the store-path from RST::STOREPATH and
|
57
|
+
# environment. Creates the path if not exists.
|
58
|
+
#
|
59
|
+
# @example
|
60
|
+
# .../data/development/store.data
|
61
|
+
#
|
62
|
+
# @return [String]
|
63
|
+
def store_path
|
64
|
+
prefix = ENV['RST_DATA'] || RST::STOREPATH
|
65
|
+
env = ENV['RST_ENV'] || 'development'
|
66
|
+
_dir = File.join( prefix, env )
|
67
|
+
FileUtils.mkdir_p(_dir)
|
68
|
+
File.join(_dir, filename)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Find and update or add object to the store.
|
72
|
+
# @param [Object] object
|
73
|
+
def update_or_add(object)
|
74
|
+
@store.transaction do |s|
|
75
|
+
s[object.id] = object
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# @param [Object] object - the object to be removed.
|
80
|
+
def remove_object(object)
|
81
|
+
@store.transaction do |s|
|
82
|
+
s.delete(object.id)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module RST
|
2
|
+
|
3
|
+
module Persistent
|
4
|
+
|
5
|
+
# # MemoryStore
|
6
|
+
# doesn't really store the objects but holds them in
|
7
|
+
# memory, in a simple Array. Perfect for testing
|
8
|
+
# @api persistent
|
9
|
+
class MemoryStore < Store
|
10
|
+
|
11
|
+
# @return [Array]
|
12
|
+
def all
|
13
|
+
@objects || []
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
# Initialize an empty array as an in-memory-store
|
19
|
+
def setup_backend
|
20
|
+
@objects = []
|
21
|
+
end
|
22
|
+
|
23
|
+
# Find and update or add an object to the store
|
24
|
+
# @param [Object] object
|
25
|
+
def update_or_add(object)
|
26
|
+
@objects -= [object]
|
27
|
+
@objects << object
|
28
|
+
end
|
29
|
+
|
30
|
+
# @param [Object] object - the object to be removed
|
31
|
+
def remove_object(object)
|
32
|
+
@objects -= [object]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module RST
|
5
|
+
|
6
|
+
# The Persistent-module injects Store-functions
|
7
|
+
# to any Object including it.
|
8
|
+
# @api persistent
|
9
|
+
module Persistent
|
10
|
+
|
11
|
+
KEY_LENGTH=8 # Length of Store-IDs
|
12
|
+
|
13
|
+
# The interface for persistent-able objects
|
14
|
+
module Persistentable
|
15
|
+
|
16
|
+
# Save the object to Store
|
17
|
+
def save
|
18
|
+
end
|
19
|
+
|
20
|
+
# Remove the object from Store
|
21
|
+
def delete
|
22
|
+
end
|
23
|
+
|
24
|
+
# If the object doesn't provide an id-method
|
25
|
+
# define one
|
26
|
+
unless respond_to?(:id)
|
27
|
+
# Set and return an unique ID
|
28
|
+
def id
|
29
|
+
@id ||= SecureRandom::hex(KEY_LENGTH)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'pstore'
|
2
|
+
require 'store_errors'
|
3
|
+
|
4
|
+
module RST
|
5
|
+
|
6
|
+
module Persistent
|
7
|
+
|
8
|
+
# # The abstract Store-base-class
|
9
|
+
#
|
10
|
+
# Store provides the interface for all store-able classes
|
11
|
+
#
|
12
|
+
# ## Usage:
|
13
|
+
#
|
14
|
+
# Derive a concrete store-class from 'Store' and overwrite the
|
15
|
+
# following methods:
|
16
|
+
#
|
17
|
+
# * setup_backend
|
18
|
+
# * sync_store
|
19
|
+
#
|
20
|
+
# @abstract
|
21
|
+
#
|
22
|
+
class Store
|
23
|
+
|
24
|
+
# Sets options-hash and calls the abstract
|
25
|
+
# 'setup_backend'-callback
|
26
|
+
def initialize(args={})
|
27
|
+
@options = {}.merge(args)
|
28
|
+
setup_backend
|
29
|
+
end
|
30
|
+
|
31
|
+
# Add an object and sync store
|
32
|
+
# @param [Persistentable] object - object including the Persistent-module
|
33
|
+
def <<(object)
|
34
|
+
update_or_add(object)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Remove an object from the store
|
38
|
+
# @param [Persistentable] object - the object to be removed from store
|
39
|
+
# @return [Store] self
|
40
|
+
def -(object)
|
41
|
+
remove_object(object)
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return Enumerable
|
46
|
+
# @abstract
|
47
|
+
def all
|
48
|
+
raise AbstractMethodCallError.new("Please, overwrite #{__callee__} in #{self.class.to_s}")
|
49
|
+
end
|
50
|
+
|
51
|
+
# @return [Object|nil] the first object in the store
|
52
|
+
def first
|
53
|
+
all.first
|
54
|
+
end
|
55
|
+
|
56
|
+
# @param [Array] ids to search
|
57
|
+
# @return [nil] if nothing found
|
58
|
+
# @return [Object] if exactly one object found
|
59
|
+
# @return [Array] of objects with matching ids if more than one matched
|
60
|
+
def find(*ids)
|
61
|
+
flatten all.select { |obj| ids.include?(obj.id) }
|
62
|
+
end
|
63
|
+
|
64
|
+
# Delete the store
|
65
|
+
# @abstract - override this for real persistent store-classes
|
66
|
+
def delete!
|
67
|
+
@objects = []
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
# callback called from initializer and aimed to initialize the
|
73
|
+
# objects-array, PStore, database-connection,...
|
74
|
+
# @abstract
|
75
|
+
def setup_backend
|
76
|
+
raise AbstractMethodCallError.new("Please override method :setup_backend in class #{self.class.to_s}")
|
77
|
+
end
|
78
|
+
|
79
|
+
# Make sure the current state of objects is stored
|
80
|
+
# @abstract
|
81
|
+
def sync_store
|
82
|
+
# RST.logger.warn("Store class #{self.class.to_s} should overwrite method :sync_store" )
|
83
|
+
end
|
84
|
+
|
85
|
+
# Flatten the result of a select
|
86
|
+
# @param [Array] result
|
87
|
+
# @return [Array] if result contains more than one element
|
88
|
+
# @return [Object] if result contains exactly one element
|
89
|
+
# @return [nil] if result is empty
|
90
|
+
def flatten result
|
91
|
+
if result.count == 1
|
92
|
+
result.first
|
93
|
+
elsif result.nil? || result.empty?
|
94
|
+
nil
|
95
|
+
else
|
96
|
+
result
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
# Find and update or add an object to the store
|
102
|
+
# @param [Object] object
|
103
|
+
# @abstract - override in other StoreClasses
|
104
|
+
def update_or_add(object)
|
105
|
+
raise AbstractMethodCallError.new("Please, overrwrite #{__callee__} in #{self.class.to_s}")
|
106
|
+
end
|
107
|
+
|
108
|
+
# @param [Object] object
|
109
|
+
# @abstract - override in concrete StoreClasses
|
110
|
+
def remove_object(object)
|
111
|
+
raise AbstractMethodCallError.new("Please, overrwrite #{__callee__} in #{self.class.to_s}")
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
data/lib/rst.rb
ADDED
@@ -0,0 +1,295 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
# # Ruby Shell Tools main namespace RST
|
5
|
+
#
|
6
|
+
# By now only two commands/features are available:
|
7
|
+
#
|
8
|
+
# * **ls** - List files from the filesystem (OS-like ls)
|
9
|
+
# * **cal[endar]** - Stores and lists events in one calendar-file,
|
10
|
+
# separated in different named calendars
|
11
|
+
#
|
12
|
+
# See [EXAMPLES.md](./file.examples.html) for detailed instruction.
|
13
|
+
#
|
14
|
+
#
|
15
|
+
# @author Andi Altendorfer <andreas@altendorfer.at>
|
16
|
+
# @see https://github.com/iboard/rst
|
17
|
+
# @see http://altendorfer.at
|
18
|
+
module RST
|
19
|
+
|
20
|
+
# Interprets and runs options and commands.
|
21
|
+
# @example
|
22
|
+
# runner = RstCommand.new(ARGV)
|
23
|
+
# puts runner.run
|
24
|
+
# See [EXAMPLES.md](../file.examples.html)
|
25
|
+
class RstCommand
|
26
|
+
|
27
|
+
# Default options are always present, even if the user will not provide them.
|
28
|
+
# They can be overwritten, though.
|
29
|
+
# @see #parse_options
|
30
|
+
DEFAULT_OPTIONS = { name: 'unnamed', from: 'today', to: 'today', show_empty: false }
|
31
|
+
|
32
|
+
# @group PUBLIC INTERFACE
|
33
|
+
|
34
|
+
# the hash stores options parsed in mehtod parse_options.
|
35
|
+
# @see #parse_options
|
36
|
+
attr_reader :options
|
37
|
+
|
38
|
+
# the first argument of ARGV is the command. It's extracted in run_command
|
39
|
+
# @see #run_command
|
40
|
+
attr_reader :command
|
41
|
+
|
42
|
+
# Initialize the Command-runner with arguments and parse them.
|
43
|
+
def initialize(args)
|
44
|
+
parse_options(args)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Call 'run_options' and 'run_command', reject empty returns and join
|
48
|
+
# output with CR
|
49
|
+
# @return [String]
|
50
|
+
def run
|
51
|
+
[run_options, run_command].compact.reject{|l| l.strip == '' }.join("\n")
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
# @group PREPARE OPTIONS AND COMMAND
|
57
|
+
|
58
|
+
# Interpret options from ARGV using OptionParser. No action here. The parsed
|
59
|
+
# options get stored in '@options' and then will be used in run_options.
|
60
|
+
# @see #options
|
61
|
+
# @see #DEFAULT_OPTIONS
|
62
|
+
# @see #run_options
|
63
|
+
# @see #run_option
|
64
|
+
def parse_options(args)
|
65
|
+
@options = DEFAULT_OPTIONS
|
66
|
+
OptionParser.new do |opts|
|
67
|
+
opts.banner = 'Usage: rst [COMMAND [COMMAND ....]] [options] [FILES..]'
|
68
|
+
|
69
|
+
opts.separator "\nOptions:"
|
70
|
+
|
71
|
+
opts.on('-v', '--[no-]verbose', 'Run verbosely') do |v|
|
72
|
+
@options[:verbose] = v
|
73
|
+
end
|
74
|
+
|
75
|
+
opts.on('--examples', 'Show some usage examples') do |x|
|
76
|
+
@options[:examples] = x
|
77
|
+
end
|
78
|
+
|
79
|
+
opts.on('-h', '--help', 'Print help') do |h|
|
80
|
+
puts opts
|
81
|
+
end
|
82
|
+
|
83
|
+
opts.on('-f', '--from DATE', String, 'Set from-date') do |from|
|
84
|
+
@options[:from] = from
|
85
|
+
end
|
86
|
+
|
87
|
+
opts.on('-t', '--to DATE', String, 'Set to-date') do |to|
|
88
|
+
@options[:to] = to
|
89
|
+
end
|
90
|
+
|
91
|
+
opts.on('-n', '--name NAME', String, 'Use this name for the command') do |name|
|
92
|
+
@options[:name] = name
|
93
|
+
end
|
94
|
+
|
95
|
+
opts.on('-e', '--new-event DATE,STRING', Array, 'Add an event') do |date,name|
|
96
|
+
@options[:new_event] = {date: date, label: name}
|
97
|
+
end
|
98
|
+
|
99
|
+
opts.on('--list-calendars', 'List available calendars') do
|
100
|
+
@options[:list_calendars] = true
|
101
|
+
end
|
102
|
+
|
103
|
+
opts.on('--delete-calendar CALENDARNAME', String, 'Delete an calendar and all it\'s entries!') do |name|
|
104
|
+
@options[:delete_calendar] = name
|
105
|
+
end
|
106
|
+
|
107
|
+
opts.on('--[no-]empty', 'Show empty entries') do |show|
|
108
|
+
@options[:show_empty] = show
|
109
|
+
end
|
110
|
+
|
111
|
+
opts.separator 'Commands:'
|
112
|
+
|
113
|
+
opts.separator <<-EOT
|
114
|
+
nil .......... no command. Interpret options only (useful in combination with -v)
|
115
|
+
ls ........... list directory and files
|
116
|
+
cal[endar] ... print a calendar --from --to
|
117
|
+
EOT
|
118
|
+
.gsub(/^\s+/,' ')
|
119
|
+
|
120
|
+
opts.separator "\n use --example for a more detailed list of commands."
|
121
|
+
end
|
122
|
+
.parse!(args)
|
123
|
+
rescue => e
|
124
|
+
puts "#{e.class} => #{e.message}"
|
125
|
+
puts "Try #{File.basename($0)} --help"
|
126
|
+
end
|
127
|
+
|
128
|
+
# Iterate over all given options and call run_option for each
|
129
|
+
# @see #run_option
|
130
|
+
# @return [String]
|
131
|
+
def run_options
|
132
|
+
options.map { |k,_v| run_option(k) }.compact.join("\n")
|
133
|
+
end
|
134
|
+
|
135
|
+
# Run the 'command', the first argument of ARGV.
|
136
|
+
# If a command is not known/invalid print out a hint to --help
|
137
|
+
# Known commands are:
|
138
|
+
# @see #directory_listing
|
139
|
+
# @see #print_calendar
|
140
|
+
# @see #command
|
141
|
+
# @return [String]
|
142
|
+
def run_command
|
143
|
+
case @command=ARGV.shift
|
144
|
+
when nil, 'nil', 'null'
|
145
|
+
''
|
146
|
+
when 'ls'
|
147
|
+
directory_listing(ARGV.empty? ? ['*'] : ARGV)
|
148
|
+
when 'cal', 'calendar'
|
149
|
+
print_calendar
|
150
|
+
else
|
151
|
+
"unknown command '#{cmd.inspect}' - try --help"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
|
156
|
+
# @group EXECUTE COMMANDS
|
157
|
+
|
158
|
+
# Do a Dir for each file-mask given.
|
159
|
+
# Duplicated files are stripped.
|
160
|
+
# @example
|
161
|
+
# rst ls lib/*rb
|
162
|
+
# @param [Array] files - a list of files/wildcards
|
163
|
+
# @return [String] - tab-delimited files found
|
164
|
+
def directory_listing(files)
|
165
|
+
files.map { |f| Dir[f].join("\t") }.uniq.join("\t")
|
166
|
+
end
|
167
|
+
|
168
|
+
# Output one line per day between start and end-date
|
169
|
+
# Start- and end-date are given with options --from, and --to
|
170
|
+
# Option --empty will output dates without events too.
|
171
|
+
# default is --no-empty which will suppress those lines in output
|
172
|
+
# @example
|
173
|
+
# rst cal --from 1.1.1990 --to today --name Birthdays
|
174
|
+
# @return [Array] - one string Date: Event + Event + ... for each day.
|
175
|
+
def print_calendar
|
176
|
+
cal = find_calendar( Persistent::DiskStore.new(CALENDAR_FILE) )
|
177
|
+
cal.list_days(options[:from], options[:to], options[:show_empty]).compact.join("\n")
|
178
|
+
end
|
179
|
+
|
180
|
+
# @group EXECUTE ACTION FROM OPTIONS
|
181
|
+
|
182
|
+
# Execute a single option
|
183
|
+
# @see #parse_options
|
184
|
+
# @see #run_options
|
185
|
+
# @param [String] option (examples, verbose, new_event,...)
|
186
|
+
def run_option(option)
|
187
|
+
case option.to_s
|
188
|
+
when 'examples'
|
189
|
+
File.read(File.join(DOCS,'examples.md')).strip
|
190
|
+
when 'verbose'
|
191
|
+
print_arguments
|
192
|
+
when 'new_event'
|
193
|
+
new_event = add_event
|
194
|
+
"Added: %s: %s" % [new_event.event_date.strftime(DEFAULT_DATE_FORMAT), new_event.event_headline.strip]
|
195
|
+
when 'list_calendars'
|
196
|
+
list_calendars
|
197
|
+
when 'delete_calendar'
|
198
|
+
delete_calendar
|
199
|
+
else
|
200
|
+
nil #noop ignore unknown options likely it's a param for an argument
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# Add an event to the calendar.
|
205
|
+
# @example
|
206
|
+
# --add-event DATE,LABEL
|
207
|
+
# @return [CalendarEvent] - the just created event
|
208
|
+
def add_event
|
209
|
+
event_environment do |ee|
|
210
|
+
ee[:calendar] << ee[:event]
|
211
|
+
ee[:store] << ee[:calendar]
|
212
|
+
ee[:event]
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
# remove a calendar from calendar-file named in options[:delete_calendar]
|
217
|
+
# @example
|
218
|
+
# --delete-calendar CALENDARNAME
|
219
|
+
def delete_calendar
|
220
|
+
store = Persistent::DiskStore.new(CALENDAR_FILE)
|
221
|
+
store -= OpenStruct.new(id: options[:delete_calendar])
|
222
|
+
nil
|
223
|
+
end
|
224
|
+
|
225
|
+
# List available calendars and count events of each one.
|
226
|
+
# @return [Array] One line/String for each calendar
|
227
|
+
def list_calendars
|
228
|
+
store = Persistent::DiskStore.new(CALENDAR_FILE)
|
229
|
+
store.all.map { |calendar|
|
230
|
+
cnt = calendar.events.count
|
231
|
+
"%-20.20s: %d %s" % [calendar.id, cnt > 0 ? cnt : 'no', cnt > 1 ? 'entries' : 'entry']
|
232
|
+
}
|
233
|
+
end
|
234
|
+
|
235
|
+
# Output the previous interpreted and parsed arguments
|
236
|
+
# Called when --verbose is given as an option
|
237
|
+
# @example
|
238
|
+
# rst --verbose
|
239
|
+
def print_arguments
|
240
|
+
puts "Binary : #{$0}"
|
241
|
+
puts "Command: #{command}"
|
242
|
+
puts "Options: #{options.map(&:inspect).join(', ')}"
|
243
|
+
puts "Files : #{ARGV.any? ? ARGV[1..-1].join(', ') : ''}"
|
244
|
+
end
|
245
|
+
|
246
|
+
# @group HELPERS
|
247
|
+
|
248
|
+
# Find or initialize a calendar-object. The name comes from option --name.
|
249
|
+
# @param [Store] store - the Store-object where to search for calendar
|
250
|
+
# @return [Calendar]
|
251
|
+
def find_calendar(store)
|
252
|
+
store.find(options[:name]) || Calendar::Calendar.new(options[:name], options[:from], options[:to])
|
253
|
+
end
|
254
|
+
|
255
|
+
# Find the date, label, and calendar_name for an event from options
|
256
|
+
# @yield [EventOptions]
|
257
|
+
def get_event_options
|
258
|
+
date = options[:new_event].fetch(:date) { Date.today.strftime('%Y-%m-%d') }
|
259
|
+
label = options[:new_event].fetch(:label) { 'unnamed event' }
|
260
|
+
calendar_name = options.fetch(:name) { 'calendar' }
|
261
|
+
yield(EventOptions.new(date, label, calendar_name))
|
262
|
+
end
|
263
|
+
|
264
|
+
|
265
|
+
# Initialize an EventEnvironment used to operate events in stores.
|
266
|
+
# @see #add_event
|
267
|
+
# @yield [EventEnvironment]
|
268
|
+
def event_environment
|
269
|
+
get_event_options do |eo|
|
270
|
+
event = Calendar::CalendarEvent.new(eo[:date], eo[:label])
|
271
|
+
store = Persistent::DiskStore.new(CALENDAR_FILE)
|
272
|
+
calendar = store.find(eo[:calendar_name]) || Calendar::Calendar.new(eo[:calendar_name])
|
273
|
+
yield(EventEnvironment.new(event,store,calendar))
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
end
|
278
|
+
|
279
|
+
# @group HELPER CLASSES
|
280
|
+
|
281
|
+
# EventEnvironment holds the CalendarEvent and the DiskStore, and Calendar to which the event belongs
|
282
|
+
# @api private
|
283
|
+
# @see RstCommand#event_environment
|
284
|
+
class EventEnvironment < Struct.new(:event,:store,:calendar)
|
285
|
+
end
|
286
|
+
|
287
|
+
# EventOptions holds the options date,label, and calendar_name
|
288
|
+
# @api private
|
289
|
+
# @see RstCommand#get_event_options
|
290
|
+
class EventOptions < Struct.new(:date,:label,:calendar_name,)
|
291
|
+
end
|
292
|
+
|
293
|
+
|
294
|
+
|
295
|
+
end
|
data/rst.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
# # Ruby Shell Tools Top-level file
|
4
|
+
module RST
|
5
|
+
# Gem-version
|
6
|
+
VERSION = '0.0.1'
|
7
|
+
|
8
|
+
# Path to the docs used by the software
|
9
|
+
DOCS = File.expand_path('../assets/docs', __FILE__)
|
10
|
+
|
11
|
+
# Default DataStore-path. You can overwrite this by
|
12
|
+
# defining `ENV[RST_DATA]`
|
13
|
+
STOREPATH = File.expand_path('../data/', __FILE__)
|
14
|
+
|
15
|
+
|
16
|
+
# intialize the logger
|
17
|
+
# @example Usage
|
18
|
+
# RST.logger.info('This will output to STDERR')
|
19
|
+
def logger
|
20
|
+
@logger ||= Logger.new(STDERR)
|
21
|
+
end
|
22
|
+
|
23
|
+
# initialize a new logger. Necessary from within specs to capture the
|
24
|
+
# output for testing.
|
25
|
+
# @param [File|Stream] _output
|
26
|
+
def logger!(_output)
|
27
|
+
@logger = Logger.new(_output)
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
end
|
metadata
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rubyshelltools
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Andi Altendorfer
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-03-16 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: redcarpet
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rspec
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: yard
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: Tools for the unix shell
|
63
|
+
email: andreas@altendorfer.at
|
64
|
+
executables:
|
65
|
+
- rst
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files:
|
68
|
+
- README.md
|
69
|
+
- assets/docs/examples.md
|
70
|
+
files:
|
71
|
+
- ./rst.rb
|
72
|
+
- lib/core_extensions/numeric.rb
|
73
|
+
- lib/errors/store_errors.rb
|
74
|
+
- lib/load.rb
|
75
|
+
- lib/modules/calendar/calendar.rb
|
76
|
+
- lib/modules/calendar/calendar_event.rb
|
77
|
+
- lib/modules/calendar/eventable.rb
|
78
|
+
- lib/modules/persistent/disk_store.rb
|
79
|
+
- lib/modules/persistent/memory_store.rb
|
80
|
+
- lib/modules/persistent/persistent.rb
|
81
|
+
- lib/modules/persistent/store.rb
|
82
|
+
- lib/rst.rb
|
83
|
+
- bin/rst
|
84
|
+
- README.md
|
85
|
+
- assets/docs/examples.md
|
86
|
+
homepage: http://rubygems.org/gems/rubyshelltools
|
87
|
+
licenses: []
|
88
|
+
post_install_message:
|
89
|
+
rdoc_options: []
|
90
|
+
require_paths:
|
91
|
+
- lib
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
none: false
|
94
|
+
requirements:
|
95
|
+
- - '>='
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
|
+
none: false
|
100
|
+
requirements:
|
101
|
+
- - '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
requirements: []
|
105
|
+
rubyforge_project:
|
106
|
+
rubygems_version: 1.8.25
|
107
|
+
signing_key:
|
108
|
+
specification_version: 3
|
109
|
+
summary: Ruby Shell Tools
|
110
|
+
test_files: []
|
111
|
+
has_rdoc:
|