ergo 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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