rubyshelltools 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/core_extensions/mutex.rb +17 -0
- data/lib/load.rb +6 -0
- data/lib/modules/calendar/calendar.rb +22 -9
- data/lib/modules/calendar/calendar_event.rb +17 -4
- data/lib/modules/calendar/eventable.rb +2 -1
- data/lib/modules/persistent/disk_store.rb +23 -14
- data/lib/modules/persistent/memory_store.rb +19 -7
- data/lib/modules/persistent/persistent.rb +38 -2
- data/lib/modules/persistent/store.rb +58 -40
- data/rst.rb +4 -2
- metadata +3 -2
@@ -0,0 +1,17 @@
|
|
1
|
+
# Not sure if this is a bug.
|
2
|
+
# Anyhow, PStore works only if the two methods are defined for Muttex.
|
3
|
+
# @see Persistent::DiskStore
|
4
|
+
class Mutex
|
5
|
+
if RUBY_VERSION >= "1.9"
|
6
|
+
#:nodoc
|
7
|
+
def marshal_dump
|
8
|
+
[]
|
9
|
+
end
|
10
|
+
|
11
|
+
#:nodoc
|
12
|
+
def marshal_load array
|
13
|
+
# do nothing
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
data/lib/load.rb
CHANGED
@@ -7,8 +7,13 @@ unless defined? LIB_LOADED
|
|
7
7
|
DEFAULT_DATE_FORMAT = '%a, %b %d %Y'
|
8
8
|
|
9
9
|
# Filename for the calendar-store
|
10
|
+
# @see Calendar::Calendar
|
10
11
|
CALENDAR_FILE = 'calendars.data'
|
11
12
|
|
13
|
+
# If no calendar-name is given
|
14
|
+
# @see Calendar::Calendar
|
15
|
+
DEFAULT_CALENDAR_NAME = 'unnamed'
|
16
|
+
|
12
17
|
require File.expand_path('../../rst',__FILE__)
|
13
18
|
|
14
19
|
# Load all necessary files
|
@@ -16,6 +21,7 @@ unless defined? LIB_LOADED
|
|
16
21
|
|
17
22
|
$LOAD_PATH.unshift(File.expand_path('../core_extensions',__FILE__))
|
18
23
|
require 'numeric'
|
24
|
+
require 'mutex'
|
19
25
|
|
20
26
|
$LOAD_PATH.unshift(File.expand_path('../errors',__FILE__))
|
21
27
|
require 'store_errors'
|
@@ -6,7 +6,9 @@ module RST
|
|
6
6
|
# Calendar-module provides the Calendar-class which is supposed to hold
|
7
7
|
# 'Eventable'-objects. Where Eventable is a module you can include to any class.
|
8
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.
|
9
|
+
# to store the calendar in a store using this name as the id.
|
10
|
+
#
|
11
|
+
# @see Persistent::DiskStore
|
10
12
|
#
|
11
13
|
# @example
|
12
14
|
#
|
@@ -38,7 +40,7 @@ module RST
|
|
38
40
|
#
|
39
41
|
module Calendar
|
40
42
|
|
41
|
-
#
|
43
|
+
# Some helper-methods useful when dealing with dates
|
42
44
|
module CalendarHelper
|
43
45
|
|
44
46
|
# You can use 'today' or any format which Date.parse can handle.
|
@@ -58,6 +60,9 @@ module RST
|
|
58
60
|
# Calendar has a name and will be stored in Persistent::DiskStore(CALENDAR_FILE)
|
59
61
|
# with it's name as the id. Thus you can save different calendars in
|
60
62
|
# the same file. If no name is given 'unnamed' will be the default.
|
63
|
+
# @see CALENDAR_FILE
|
64
|
+
# @see Persistent::DiskStore
|
65
|
+
# @see Calendar::CalendarEvent
|
61
66
|
class Calendar
|
62
67
|
|
63
68
|
include Persistent::Persistentable
|
@@ -68,7 +73,8 @@ module RST
|
|
68
73
|
# @param [String] _name Name and persistent-id of this calendar.
|
69
74
|
# @param [Date|Time|String] _start The date when the calendar starts
|
70
75
|
# @param [Date|Time|String] _end The date when the calendar ends
|
71
|
-
|
76
|
+
# @see DEFAULT_CALENDAR_NAME
|
77
|
+
def initialize(_name=DEFAULT_CALENDAR_NAME, _start=nil, _end=nil, _events=[])
|
72
78
|
@name = _name
|
73
79
|
@start_date = parse_date_param(_start)
|
74
80
|
@end_date = parse_date_param(_end)
|
@@ -89,6 +95,7 @@ module RST
|
|
89
95
|
end
|
90
96
|
|
91
97
|
# Override Persistentable's id-method
|
98
|
+
# @see Persistent::Persistentable
|
92
99
|
# @return [String] - the calendar-name is it's id
|
93
100
|
def id
|
94
101
|
@name || super
|
@@ -123,9 +130,9 @@ module RST
|
|
123
130
|
|
124
131
|
|
125
132
|
private
|
126
|
-
#
|
133
|
+
# All events on the given date
|
127
134
|
# @param [Date] date
|
128
|
-
# @return [Array] of CalendarEvents
|
135
|
+
# @return [Array] of [CalendarEvents]
|
129
136
|
def events_on(date)
|
130
137
|
events.select { |event| event.event_date == date }
|
131
138
|
end
|
@@ -139,14 +146,20 @@ module RST
|
|
139
146
|
|
140
147
|
# Output date and Events on this date in one line
|
141
148
|
# @param [Date] _date
|
142
|
-
# @param [Boolean] show_empty - do
|
149
|
+
# @param [Boolean] show_empty - do output lines with no events
|
143
150
|
# @return [String]
|
144
151
|
def format_events_for(_date,show_empty=false)
|
145
|
-
if show_empty ||
|
146
|
-
|
147
|
-
[_date.strftime(DEFAULT_DATE_FORMAT), _events].compact.join(": ")
|
152
|
+
if show_empty || (_line=event_headlines_for(_date)) != ''
|
153
|
+
"%s: %s" % [_date.strftime(DEFAULT_DATE_FORMAT), _line]
|
148
154
|
end
|
149
155
|
end
|
156
|
+
|
157
|
+
# Concatenate headlines of all events on this date
|
158
|
+
# @param [Date] _date
|
159
|
+
# @return [String]
|
160
|
+
def event_headlines_for(_date)
|
161
|
+
events_on(_date).map(&:event_headline).join(' + ').strip
|
162
|
+
end
|
150
163
|
|
151
164
|
end
|
152
165
|
|
@@ -2,9 +2,22 @@ module RST
|
|
2
2
|
|
3
3
|
module Calendar
|
4
4
|
|
5
|
-
# A CalendarEvent happens on a given date and
|
6
|
-
#
|
7
|
-
# @see Calendar
|
5
|
+
# A CalendarEvent happens on a given date and has a label.
|
6
|
+
# It's can be used in a Calendar
|
7
|
+
# @see Calendar::Calendar
|
8
|
+
# @see Calendar::Eventable
|
9
|
+
# @example
|
10
|
+
#
|
11
|
+
# calendar = Calendar::Calendar.new('my calendar')
|
12
|
+
# event = Calendar::CalendarEvent.new( 'today', "I'm happy" )
|
13
|
+
# calendar << event
|
14
|
+
# calendar.format_events_for(Date.today)
|
15
|
+
# => "Sun, Mar 17 2013: I'm happy"
|
16
|
+
# calendar << Calendar::CalendarEvent.new('2013-03-18', "Back to work :(")
|
17
|
+
# calendar.list_days('today','4/2013')
|
18
|
+
# => Sun, Mar 17 2013: I'm happy
|
19
|
+
# => Mon, Mar 18 2013: Back to work :(
|
20
|
+
#
|
8
21
|
class CalendarEvent
|
9
22
|
|
10
23
|
attr_reader :event_date, :label
|
@@ -15,8 +28,8 @@ module RST
|
|
15
28
|
# @param [String|Date] _date - Date of the event (only all-day-events are possible yet)
|
16
29
|
# @param [String] _label - Events name
|
17
30
|
def initialize(_date,_label)
|
18
|
-
@event_date = ensure_date(_date)
|
19
31
|
@label = _label
|
32
|
+
schedule! ensure_date(_date)
|
20
33
|
end
|
21
34
|
|
22
35
|
# override abstract method for an Eventable
|
@@ -4,21 +4,25 @@ module RST
|
|
4
4
|
# #DiskStore is responsible to save an Array of objects
|
5
5
|
# persistently to the disk - PStore is used to effort this.
|
6
6
|
# @see RST::Persistent::Store
|
7
|
+
# @see Mutex
|
7
8
|
# @api persistent
|
8
9
|
class DiskStore < Store
|
9
10
|
|
10
|
-
|
11
|
+
# [String] filename - used for PStore (no path! filename only)
|
12
|
+
# @see #store_path
|
13
|
+
attr_reader :filename
|
11
14
|
|
12
15
|
# The first argument is the filename
|
13
16
|
# Following args are passed to base-class
|
14
17
|
# @example
|
15
18
|
#
|
16
19
|
# DiskStore.new('myfile')
|
17
|
-
# DiskStore.new(
|
18
|
-
#
|
20
|
+
# DiskStore.new('myfile', other_option: '...')
|
21
|
+
# DiskStore.new( nil, other_option: '...') # use default filename
|
22
|
+
# @see DEFAULT_STORE_FILENAME
|
19
23
|
# @param [String] _filename
|
20
24
|
# @param [Array] args - arguments passed to the super-class
|
21
|
-
def initialize(_filename=
|
25
|
+
def initialize(_filename=DEFAULT_STORE_FILENAME,args={})
|
22
26
|
@filename = _filename
|
23
27
|
super(args)
|
24
28
|
end
|
@@ -45,9 +49,18 @@ module RST
|
|
45
49
|
File.unlink(store_path)
|
46
50
|
end
|
47
51
|
|
52
|
+
# Find and update or add object to the store.
|
53
|
+
# @param [Object] object
|
54
|
+
def update(object)
|
55
|
+
@store.transaction do |s|
|
56
|
+
s[object.id] = object
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
48
60
|
private
|
49
61
|
|
50
62
|
# Initialize a PStore-instance
|
63
|
+
# @see store_path
|
51
64
|
def setup_backend
|
52
65
|
@store = PStore.new(store_path)
|
53
66
|
end
|
@@ -57,8 +70,13 @@ module RST
|
|
57
70
|
# environment. Creates the path if not exists.
|
58
71
|
#
|
59
72
|
# @example
|
60
|
-
#
|
73
|
+
# RST_ENV=development
|
74
|
+
# RST_DATA=$HOME/.rst-data
|
75
|
+
#
|
76
|
+
# ~/.rst-data/development/store.data
|
61
77
|
#
|
78
|
+
# if no environment-variables defined, the defaulsts will be STOREPATH and 'development'
|
79
|
+
# @see STOREPATH
|
62
80
|
# @return [String]
|
63
81
|
def store_path
|
64
82
|
prefix = ENV['RST_DATA'] || RST::STOREPATH
|
@@ -68,21 +86,12 @@ module RST
|
|
68
86
|
File.join(_dir, filename)
|
69
87
|
end
|
70
88
|
|
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
89
|
# @param [Object] object - the object to be removed.
|
80
90
|
def remove_object(object)
|
81
91
|
@store.transaction do |s|
|
82
92
|
s.delete(object.id)
|
83
93
|
end
|
84
94
|
end
|
85
|
-
|
86
95
|
end
|
87
96
|
|
88
97
|
end
|
@@ -13,6 +13,18 @@ module RST
|
|
13
13
|
@objects || []
|
14
14
|
end
|
15
15
|
|
16
|
+
# Find and update or add an object to the store
|
17
|
+
# @param [Object] object
|
18
|
+
def update(object)
|
19
|
+
@objects -= [object]
|
20
|
+
@objects << object
|
21
|
+
end
|
22
|
+
|
23
|
+
# remove all objects
|
24
|
+
def delete!
|
25
|
+
@objects = []
|
26
|
+
end
|
27
|
+
|
16
28
|
private
|
17
29
|
|
18
30
|
# Initialize an empty array as an in-memory-store
|
@@ -20,17 +32,17 @@ module RST
|
|
20
32
|
@objects = []
|
21
33
|
end
|
22
34
|
|
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
35
|
# @param [Object] object - the object to be removed
|
31
36
|
def remove_object(object)
|
32
37
|
@objects -= [object]
|
33
38
|
end
|
39
|
+
|
40
|
+
# Make sure the current state of objects is stored
|
41
|
+
# @abstract - Overwrite in descendants thus every object gets persistently stored.
|
42
|
+
def sync_store
|
43
|
+
#noop for MemoryStore
|
44
|
+
end
|
45
|
+
|
34
46
|
end
|
35
47
|
end
|
36
48
|
end
|
@@ -6,30 +6,66 @@ module RST
|
|
6
6
|
# The Persistent-module injects Store-functions
|
7
7
|
# to any Object including it.
|
8
8
|
# @api persistent
|
9
|
+
# @see [Store]
|
10
|
+
# @example
|
11
|
+
#
|
12
|
+
# store = MemoryStore.new
|
13
|
+
# object= store.create { PersistentableObject.new }
|
14
|
+
# ...modify object ...
|
15
|
+
# object.save
|
16
|
+
#
|
17
|
+
# object = store.find(object.id)
|
18
|
+
# object.delete
|
19
|
+
#
|
9
20
|
module Persistent
|
10
21
|
|
11
|
-
KEY_LENGTH=
|
22
|
+
KEY_LENGTH=42 # Length of Store-IDs
|
12
23
|
|
13
24
|
# The interface for persistent-able objects
|
14
25
|
module Persistentable
|
15
26
|
|
27
|
+
attr_reader :store # @see Persistent::Store
|
28
|
+
|
16
29
|
# Save the object to Store
|
17
30
|
def save
|
31
|
+
store.update(self)
|
18
32
|
end
|
19
33
|
|
20
34
|
# Remove the object from Store
|
21
35
|
def delete
|
36
|
+
self.store -= self
|
22
37
|
end
|
23
38
|
|
24
39
|
# If the object doesn't provide an id-method
|
25
40
|
# define one
|
26
41
|
unless respond_to?(:id)
|
27
|
-
#
|
42
|
+
# Return an unique ID (create one if not exists yet)
|
43
|
+
# @return [String]
|
44
|
+
# @see KEY_LENGTH
|
28
45
|
def id
|
29
46
|
@id ||= SecureRandom::hex(KEY_LENGTH)
|
30
47
|
end
|
31
48
|
end
|
32
49
|
|
50
|
+
# Setter for store-attribute
|
51
|
+
# @param [Store] store
|
52
|
+
def store=(store)
|
53
|
+
move_store(store)
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
# Move the object to another store
|
59
|
+
# @abstract - Moving is not implemented yet, though. The method sets the store-attribute if not set already.
|
60
|
+
def move_store store
|
61
|
+
if @store && @store != store
|
62
|
+
raise NotImplementedError.new('An object can\'t be moved to another store')
|
63
|
+
elsif @store == store
|
64
|
+
self
|
65
|
+
else
|
66
|
+
@store = store
|
67
|
+
end
|
68
|
+
end
|
33
69
|
end
|
34
70
|
|
35
71
|
end
|
@@ -9,18 +9,13 @@ module RST
|
|
9
9
|
#
|
10
10
|
# Store provides the interface for all store-able classes
|
11
11
|
#
|
12
|
-
#
|
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
|
12
|
+
# @abstract public API-methods should not be overwritten by descendant classes.
|
13
|
+
# But descendants must overwrite all methods marked as abstract here.
|
21
14
|
#
|
22
15
|
class Store
|
23
16
|
|
17
|
+
# @group PUBLIC API
|
18
|
+
|
24
19
|
# Sets options-hash and calls the abstract
|
25
20
|
# 'setup_backend'-callback
|
26
21
|
def initialize(args={})
|
@@ -29,9 +24,9 @@ module RST
|
|
29
24
|
end
|
30
25
|
|
31
26
|
# Add an object and sync store
|
32
|
-
# @param [Persistentable] object - object including the Persistent-module
|
27
|
+
# @param [Persistentable] object - any object including the Persistent-module
|
33
28
|
def <<(object)
|
34
|
-
|
29
|
+
update(object)
|
35
30
|
end
|
36
31
|
|
37
32
|
# Remove an object from the store
|
@@ -42,46 +37,62 @@ module RST
|
|
42
37
|
self
|
43
38
|
end
|
44
39
|
|
45
|
-
# @return
|
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
|
40
|
+
# @return [Object|nil] the first object in the store or nil if the store is empty.
|
52
41
|
def first
|
53
42
|
all.first
|
54
43
|
end
|
55
44
|
|
56
|
-
# @param [Array] ids to search
|
57
|
-
# @return [nil]
|
45
|
+
# @param [Array|String] ids or id to search
|
46
|
+
# @return [nil] if nothing found
|
58
47
|
# @return [Object] if exactly one object found
|
59
|
-
# @return [Array]
|
48
|
+
# @return [Array] of objects with matching ids if more than one matched
|
60
49
|
def find(*ids)
|
61
50
|
flatten all.select { |obj| ids.include?(obj.id) }
|
62
51
|
end
|
63
52
|
|
64
|
-
|
65
|
-
#
|
66
|
-
|
67
|
-
|
53
|
+
|
54
|
+
# Create objects and set the object's store-attribute
|
55
|
+
# @example
|
56
|
+
#
|
57
|
+
# new_object = store.create
|
58
|
+
# MyClass.new(....)
|
59
|
+
# end
|
60
|
+
#
|
61
|
+
# @return [Persistentable] - the newly created object
|
62
|
+
def create
|
63
|
+
obj = yield
|
64
|
+
obj.store = self
|
65
|
+
self << obj
|
66
|
+
obj
|
68
67
|
end
|
69
68
|
|
70
|
-
|
69
|
+
# @group ABSTRACT METHODS TO BE OVERWRITTEN IN DESCENDANTS
|
71
70
|
|
72
|
-
#
|
73
|
-
# objects
|
74
|
-
|
75
|
-
|
76
|
-
raise AbstractMethodCallError.new("Please override method :setup_backend in class #{self.class.to_s}")
|
71
|
+
# @return Enumerable
|
72
|
+
# @abstract Overwrite in descendants thus it returns an Enumerable of all objects in the store
|
73
|
+
def all
|
74
|
+
raise AbstractMethodCallError.new("Please, overwrite #{__callee__} in #{self.class.to_s}")
|
77
75
|
end
|
78
76
|
|
79
|
-
#
|
80
|
-
# @abstract
|
81
|
-
def
|
82
|
-
|
77
|
+
# Delete the store
|
78
|
+
# @abstract - override this method in descendants thus the store removes all objects.
|
79
|
+
def delete!
|
80
|
+
raise AbstractMethodCallError.new("Please, overrwrite #{__callee__} in #{self.class.to_s}")
|
83
81
|
end
|
84
82
|
|
83
|
+
# Find and update or add an object to the store
|
84
|
+
# @param [Object] object
|
85
|
+
# @abstract - override in other StoreClasses
|
86
|
+
def update(object)
|
87
|
+
raise AbstractMethodCallError.new("Please, overrwrite #{__callee__} in #{self.class.to_s}")
|
88
|
+
end
|
89
|
+
|
90
|
+
# @endgroup
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
# @group PRIVATE API
|
95
|
+
|
85
96
|
# Flatten the result of a select
|
86
97
|
# @param [Array] result
|
87
98
|
# @return [Array] if result contains more than one element
|
@@ -97,12 +108,19 @@ module RST
|
|
97
108
|
end
|
98
109
|
end
|
99
110
|
|
111
|
+
# @group ABSTRACT METHODS TO BE OVERWRITTEN IN DESCENDANTS
|
100
112
|
|
101
|
-
#
|
102
|
-
#
|
103
|
-
# @abstract -
|
104
|
-
def
|
105
|
-
raise AbstractMethodCallError.new("Please
|
113
|
+
# callback called from initializer. Aimed to initialize the
|
114
|
+
# objects-array, PStore, database-connection,...
|
115
|
+
# @abstract - Overwrite in descendants thus they initialize the store.
|
116
|
+
def setup_backend
|
117
|
+
raise AbstractMethodCallError.new("Please override method :setup_backend in class #{self.class.to_s}")
|
118
|
+
end
|
119
|
+
|
120
|
+
# Make sure the current state of objects is stored
|
121
|
+
# @abstract - Overwrite in descendants thus every object gets persistently stored.
|
122
|
+
def sync_store
|
123
|
+
raise AbstractMethodCallError.new("Please override method :sync_store in class #{self.class.to_s}")
|
106
124
|
end
|
107
125
|
|
108
126
|
# @param [Object] object
|
data/rst.rb
CHANGED
@@ -3,7 +3,7 @@ require 'logger'
|
|
3
3
|
# # Ruby Shell Tools Top-level file
|
4
4
|
module RST
|
5
5
|
# Gem-version
|
6
|
-
VERSION = '0.0.
|
6
|
+
VERSION = '0.0.2'
|
7
7
|
|
8
8
|
# Path to the docs used by the software
|
9
9
|
DOCS = File.expand_path('../assets/docs', __FILE__)
|
@@ -11,8 +11,10 @@ module RST
|
|
11
11
|
# Default DataStore-path. You can overwrite this by
|
12
12
|
# defining `ENV[RST_DATA]`
|
13
13
|
STOREPATH = File.expand_path('../data/', __FILE__)
|
14
|
-
|
15
14
|
|
15
|
+
# @see Persistent::DiskStore
|
16
|
+
DEFAULT_STORE_FILENAME = 'rst_data.pstore'
|
17
|
+
|
16
18
|
# intialize the logger
|
17
19
|
# @example Usage
|
18
20
|
# RST.logger.info('This will output to STDERR')
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubyshelltools
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-03-
|
12
|
+
date: 2013-03-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: redcarpet
|
@@ -69,6 +69,7 @@ extra_rdoc_files:
|
|
69
69
|
- assets/docs/examples.md
|
70
70
|
files:
|
71
71
|
- ./rst.rb
|
72
|
+
- lib/core_extensions/mutex.rb
|
72
73
|
- lib/core_extensions/numeric.rb
|
73
74
|
- lib/errors/store_errors.rb
|
74
75
|
- lib/load.rb
|