ergo 0.3.0

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.
@@ -0,0 +1,58 @@
1
+ # We are going to try doing it this way. If i proves a problem
2
+ # we'll make a special class.
3
+ #
4
+ class TrueClass
5
+ def empty?
6
+ false
7
+ end
8
+
9
+ def &(other)
10
+ other
11
+ end
12
+
13
+ def |(other)
14
+ other
15
+ end
16
+ end
17
+
18
+ =begin
19
+ class TrueArray < Array
20
+
21
+ def each; end
22
+
23
+ def size
24
+ 1 # ?
25
+ end
26
+
27
+ def empty?
28
+ false
29
+ end
30
+
31
+ def &(other)
32
+ other.dup
33
+ end
34
+
35
+ def |(other)
36
+ other.dup
37
+ end
38
+
39
+ ## If this would have worked we would not have had
40
+ ## to override Array.
41
+ #def coerce(other)
42
+ # return self, other
43
+ #end
44
+ end
45
+ =end
46
+
47
+ class Array
48
+ alias and_without_t :&
49
+ alias or_without_t :|
50
+
51
+ def |(other)
52
+ TrueClass === other ? dup : or_without_t(other)
53
+ end
54
+
55
+ def &(other)
56
+ TrueClass === other ? dup : and_without_t(other)
57
+ end
58
+ end
@@ -0,0 +1,196 @@
1
+ module Ergo
2
+
3
+ ##
4
+ # Digest class is used to read and write lists of files with their
5
+ # associated checksums. This class uses SHA1.
6
+ #
7
+ class Digest
8
+
9
+ # The name of the master digest.
10
+ MASTER_NAME = 'Master'
11
+
12
+ # The digest file to use if the root directory has a `log/` directory.
13
+ DIRECTORY = ".ergo/digest"
14
+
15
+ # Get the name of the most recent digest given a selection of names
16
+ # from which to choose.
17
+ #
18
+ # names - Selection of names. [Array<String>]
19
+ #
20
+ # Returns the digests name. [String]
21
+ def self.latest(*names)
22
+ names = names.select do |name|
23
+ File.exist?(File.join(DIRECTORY, "#{name}.digest"))
24
+ end
25
+ names.max do |name|
26
+ File.mtime(File.join(DIRECTORY, "#{name}.digest"))
27
+ end
28
+ end
29
+
30
+ # Remove all digests.
31
+ def self.clear_digests
32
+ Dir.glob(File.join(DIRECTORY, "*.digest")).each do |file|
33
+ FileUtils.rm(file)
34
+ end
35
+ end
36
+
37
+ # Remove digest by name.
38
+ def self.remove_digest(name)
39
+ file = File.join(DIRECTORY, "#{name}.digest")
40
+ if file.exist?(file)
41
+ FileUtils.rm(file)
42
+ end
43
+ end
44
+
45
+ # Instance of Ignore is used to filter "boring files".
46
+ #
47
+ # Returns [Ignore]
48
+ attr :ignore
49
+
50
+ # Name of digest, which corresponds to the rule bookmark.
51
+ #
52
+ # Returns [Ignore]
53
+ attr :name
54
+
55
+ # Set of files as they appear on disk.
56
+ attr :current
57
+
58
+ # Set of files as saved in the digest.
59
+ attr :saved
60
+
61
+ # Initialize new instance of Digest.
62
+ #
63
+ # Options
64
+ # ignore - Instance of Ignore for filtering unwanted files. [Ignore]
65
+ # mark - Name of digest to load. [String]
66
+ #
67
+ def initialize(options={})
68
+ @ignore = options[:ignore]
69
+ @name = options[:name] || MASTER_NAME
70
+
71
+ @current = {}
72
+ @saved = {}
73
+
74
+ read
75
+ refresh
76
+ end
77
+
78
+ # The digest file's path.
79
+ #
80
+ # Returns [String]
81
+ def filename
82
+ File.join(DIRECTORY, "#{name}.digest")
83
+ end
84
+
85
+ # Load digest from file system.
86
+ #
87
+ # Returns nothing.
88
+ def read
89
+ file = filename
90
+
91
+ # if the digest doesn't exist fallback to master digest
92
+ unless File.exist?(file)
93
+ file = File.join(DIRECTORY, "#{MASTER_NAME}.digest")
94
+ end
95
+
96
+ return unless File.exist?(file)
97
+
98
+ File.read(file).lines.each do |line|
99
+ if md = /^(\w+)\s+(.*?)$/.match(line)
100
+ @saved[md[2]] = md[1]
101
+ end
102
+ end
103
+ end
104
+
105
+ =begin
106
+ # Gather current digest for all files.
107
+ #
108
+ # Returns nothing.
109
+ def refresh
110
+ Dir['**/*'].each do |path|
111
+ if File.directory?(path)
112
+ # how to handle directories as a whole?
113
+ elsif File.exist?(path)
114
+ id = checksum(path)
115
+ @current[path] = id
116
+ end
117
+ end
118
+ end
119
+ =end
120
+
121
+ # Gather current digest for all files.
122
+ #
123
+ # Returns nothing.
124
+ def refresh
125
+ list = Dir['**/*']
126
+ list = filter(list)
127
+ list.each do |path|
128
+ if File.directory?(path)
129
+ # how to handle directories as a whole?
130
+ elsif File.exist?(path)
131
+ id = checksum(path)
132
+ @current[path] = id
133
+ end
134
+ end
135
+ end
136
+
137
+ # Save current digest.
138
+ #
139
+ # Returns nothing.
140
+ def save
141
+ FileUtils.mkdir_p(DIRECTORY) unless File.directory?(DIRECTORY)
142
+ File.open(filename, 'w') do |f|
143
+ f << to_s
144
+ end
145
+ end
146
+
147
+ # Remove digest.
148
+ def remove
149
+ if File.exist?(filename)
150
+ FileUtils.rm(filename)
151
+ end
152
+ end
153
+
154
+ # Produce the test representation of the digest that is stored to disk.
155
+ #
156
+ # Returns digest file format. [String]
157
+ def to_s
158
+ s = ""
159
+ current.each do |path, id|
160
+ s << "#{id} #{path}\n"
161
+ end
162
+ s
163
+ end
164
+
165
+ # Compute the sha1 identifer for a file.
166
+ #
167
+ # file - path to a file
168
+ #
169
+ # Returns [String] SHA1 digest string.
170
+ def checksum(file)
171
+ sha = ::Digest::SHA1.new
172
+ File.open(file, 'r') do |fh|
173
+ fh.each_line do |l|
174
+ sha << l
175
+ end
176
+ end
177
+ sha.hexdigest
178
+ end
179
+
180
+ # Filter files of those to be ignored.
181
+ #
182
+ # Return [Array<String>]
183
+ def filter(list)
184
+ case ignore
185
+ when Ignore
186
+ ignore.filter(list)
187
+ when Array
188
+ list.reject{ |path| ignore.any?{ |ig| /^#{ig}/ =~ path } }
189
+ else
190
+ list
191
+ end
192
+ end
193
+
194
+ end
195
+
196
+ end
@@ -0,0 +1,146 @@
1
+ module Ergo
2
+
3
+ # This file can be used as an alternative to using the #ignore method
4
+ # to define what paths to ignore.
5
+ IGNORE_FILE = '.ergo/ignore'
6
+
7
+ ##
8
+ # Encapsulates list of file globs to be ignored.
9
+ #
10
+ class Ignore
11
+ include Enumerable
12
+
13
+ # Initialize new instance of Ignore.
14
+ #
15
+ # Returns nothing.
16
+ def initialize(options={})
17
+ @file = options[:file]
18
+ @root = options[:root]
19
+
20
+ @ignore = load_ignore
21
+ end
22
+
23
+ # Filter a list of files in accordance with the
24
+ # ignore list.
25
+ #
26
+ # files - The list of files. [Array<String>]
27
+ #
28
+ # Returns [Array<String>]
29
+ def filter(files)
30
+ list = []
31
+ files.each do |file|
32
+ hit = any? do |pattern|
33
+ match?(pattern, file)
34
+ end
35
+ list << file unless hit
36
+ end
37
+ list
38
+ end
39
+
40
+ # Returns [Array<String>]
41
+ #def ignore
42
+ # @ignore ||= load_ignore
43
+ #end
44
+
45
+ # Ignore file.
46
+ def file
47
+ @file ||= (
48
+ Dir["{.gitignore,.hgignore,#{IGNORE_FILE}}"].first
49
+ )
50
+ end
51
+
52
+ #
53
+ def each
54
+ to_a.each{ |g| yield g }
55
+ end
56
+
57
+ #
58
+ def size
59
+ to_a.size
60
+ end
61
+
62
+ #
63
+ def to_a
64
+ @ignore #||= load_ignore
65
+ end
66
+
67
+ #
68
+ def replace(*globs)
69
+ @ignore = globs.flatten
70
+ end
71
+
72
+ #
73
+ def concat(*globs)
74
+ @ignore.concat(globs.flatten)
75
+ end
76
+
77
+ #private
78
+
79
+ #def all_ignored_files
80
+ # list = []
81
+ # ignore.each do |glob|
82
+ # if glob.start_with?('/')
83
+ # list.concat Dir[File.join(@root, glob)]
84
+ # else
85
+ # list.concat Dir[File.join(@root, '**', glob)]
86
+ # end
87
+ # end
88
+ # list
89
+ #end
90
+
91
+ # Load ignore file. Removes blank lines and line starting with `#`.
92
+ #
93
+ # Returns [Array<String>]
94
+ def load_ignore
95
+ f = file
96
+ i = []
97
+ if f && File.exist?(f)
98
+ File.read(f).lines.each do |line|
99
+ glob = line.strip
100
+ next if glob.empty?
101
+ next if glob.start_with?('#')
102
+ i << glob
103
+ end
104
+ end
105
+ i
106
+ end
107
+
108
+ # Given a pattern and a file, does the file match the
109
+ # pattern? This code is based on the rules used by
110
+ # git's .gitignore file.
111
+ #
112
+ # TODO: The code is probably not quite right.
113
+ #
114
+ # Returns [Boolean]
115
+ def match?(pattern, file)
116
+ if pattern.start_with?('!')
117
+ return !match?(pattern.sub('!','').strip)
118
+ end
119
+
120
+ dir = pattern.end_with?('/')
121
+ pattern = pattern.chomp('/') if dir
122
+
123
+ if pattern.start_with?('/')
124
+ fnmatch?(pattern.sub('/',''), file)
125
+ else
126
+ if dir
127
+ fnmatch?(File.join(pattern, '**', '*'), file) ||
128
+ fnmatch?(pattern, file) && File.directory?(file)
129
+ elsif pattern.include?('/')
130
+ fnmatch?(pattern, file)
131
+ else
132
+ fnmatch?(File.join('**',pattern), file)
133
+ end
134
+ end
135
+ end
136
+
137
+ # Shortcut to `File.fnmatch?` method.
138
+ #
139
+ # Returns [Boolean]
140
+ def fnmatch?(pattern, file, mode=File::FNM_PATHNAME)
141
+ File.fnmatch?(pattern, file, File::FNM_PATHNAME)
142
+ end
143
+
144
+ end
145
+
146
+ end
@@ -0,0 +1,26 @@
1
+ module Ergo
2
+
3
+ # Match is a subclass of a string that also stores the
4
+ # MatchData then matched against it in a Regexp comparison.
5
+ #
6
+ class Match < String
7
+ # Initialize a new instance of Match.
8
+ #
9
+ # string - The string. [String]
10
+ # matchdata - The match data. [MatchData]
11
+ #
12
+ def intialize(string, matchdata)
13
+ replace(string)
14
+ @matchdata = matchdata
15
+ end
16
+
17
+ # The match data that resulted from
18
+ # a successful Regexp against the string.
19
+ #
20
+ # Returns [MatchData]
21
+ def matchdata
22
+ @matchdata
23
+ end
24
+ end
25
+
26
+ end
@@ -0,0 +1,134 @@
1
+ module Ergo
2
+
3
+ # Rule class encapsulates a *rule* definition.
4
+ #
5
+ class Rule
6
+ # Initialize new instanance of Rule.
7
+ #
8
+ # state - State condition. [Logic]
9
+ # procedure - Procedure to run if logic condition is met. [Proc]
10
+ #
11
+ # Options
12
+ # desc - Description of rule. [String]
13
+ # mark - List of bookmark names. [Array<String>]
14
+ #
15
+ def initialize(state, options={}, &procedure)
16
+ self.state = state
17
+ self.desc = options[:desc] || options[:description]
18
+ self.mark = options[:mark] || options[:bookmarks]
19
+ self.private = options[:private]
20
+
21
+ @proc = procedure
22
+ end
23
+
24
+ # Access logic condition.
25
+ #
26
+ # Returns [State]
27
+ attr :state
28
+
29
+ # Description of rule.
30
+ #
31
+ # Returns [String]
32
+ def description
33
+ @description
34
+ end
35
+
36
+ # Returns the description.
37
+ #
38
+ # Returns [String]
39
+ alias :to_s :description
40
+
41
+ # Rule bookmarks.
42
+ #
43
+ # Returns [Array<String>]
44
+ def bookmarks
45
+ @bookmarks
46
+ end
47
+
48
+ #
49
+ def bookmark?(name)
50
+ @bookmarks.include?(name.to_s)
51
+ end
52
+ alias :mark? :bookmark?
53
+
54
+ # Is the rule private? A private rule does not run with the "master book",
55
+ # only when it's specific book is invoked.
56
+ def private?
57
+ @private
58
+ end
59
+
60
+ # Rule procedure.
61
+ #
62
+ # Returns [Proc]
63
+ def to_proc
64
+ @proc
65
+ end
66
+
67
+ # Apply rule, running the rule's procedure if the state
68
+ # condition is satisfied.
69
+ #
70
+ # Returns nothing.
71
+ def apply(digest)
72
+ case state
73
+ when true
74
+ call
75
+ when false, nil
76
+ else
77
+ result_set = state.call(digest)
78
+ if result_set && !result_set.empty?
79
+ call(result_set)
80
+ end
81
+ end
82
+ end
83
+
84
+ # Alias for #apply.
85
+ alias :invoke :apply
86
+
87
+ # Convenience method for producing a rule list.
88
+ #
89
+ # Rertuns [Array]
90
+ def to_a
91
+ [description, bookmarks, private?]
92
+ end
93
+
94
+ protected
95
+
96
+ # Set state of rule.
97
+ def state=(state)
98
+ #raise unless State === state || Boolean === state
99
+ @state = state
100
+ end
101
+
102
+ # Set bookmark(s) of rule.
103
+ def mark=(names)
104
+ @bookmarks = Array(names).map{ |b| b.to_s }
105
+ end
106
+
107
+ # Set privacy of rule. A private rule does not run with the "master book",
108
+ # only when it's specific book is invoked.
109
+ def private=(boolean)
110
+ @private = !! boolean
111
+ end
112
+
113
+ # Set description of rule.
114
+ def desc=(string)
115
+ @description = string.to_s
116
+ end
117
+
118
+ # Run rule procedure.
119
+ #
120
+ # result_set - The result set returned by the logic condition.
121
+ #
122
+ # Returns whatever the procedure returns. [Object]
123
+ def call(*result_set)
124
+ if @proc.arity == 0
125
+ @proc.call
126
+ else
127
+ #@procedure.call(session, *args)
128
+ @proc.call(*result_set)
129
+ end
130
+ end
131
+
132
+ end
133
+
134
+ end