rubyshelltools 0.0.1
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/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:
|