blaml 1.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
data/.autotest ADDED
@@ -0,0 +1,23 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'autotest/restart'
4
+
5
+ # Autotest.add_hook :initialize do |at|
6
+ # at.extra_files << "../some/external/dependency.rb"
7
+ #
8
+ # at.libs << ":../some/external"
9
+ #
10
+ # at.add_exception 'vendor'
11
+ #
12
+ # at.add_mapping(/dependency.rb/) do |f, _|
13
+ # at.files_matching(/test_.*rb$/)
14
+ # end
15
+ #
16
+ # %w(TestA TestB).each do |klass|
17
+ # at.extra_class_map[klass] = "test/test_misc.rb"
18
+ # end
19
+ # end
20
+
21
+ # Autotest.add_hook :run_command do |at|
22
+ # system "rake build"
23
+ # end
data/.gemtest ADDED
File without changes
data/History.rdoc ADDED
@@ -0,0 +1,6 @@
1
+ === 1.0.0 / 2011-02-02
2
+
3
+ * 1 major enhancement
4
+
5
+ * Birthday!
6
+
data/Manifest.txt ADDED
@@ -0,0 +1,11 @@
1
+ .autotest
2
+ History.rdoc
3
+ Manifest.txt
4
+ README.rdoc
5
+ Rakefile
6
+ lib/blaml.rb
7
+ lib/blaml/blamed_io.rb
8
+ lib/blaml/meta.rb
9
+ lib/blaml/to_ruby.rb
10
+ lib/blaml/tree_builder.rb
11
+ test/test_blaml.rb
data/README.rdoc ADDED
@@ -0,0 +1,77 @@
1
+ = Blaml
2
+
3
+ * http://github.com/yaksnrainbows/blaml
4
+
5
+ == DESCRIPTION:
6
+
7
+ Blaml is a parser based on Psych (ruby 1.9 only) for yaml files that have
8
+ been blamed with git.
9
+ Blaml parses the blamed yaml file and makes the blame metadata available on the
10
+ returned ruby object through a 'meta' attribute.
11
+
12
+ == FEATURES:
13
+
14
+ * Load blamed yaml files and access blame data as metadata.
15
+
16
+ * Currently only works on blamed files generated with 'git blame -f file.yml'
17
+
18
+ == SYNOPSIS:
19
+
20
+ b = Blaml.load `git blame -f file.yml`
21
+
22
+ b.meta
23
+ # => Returns metadata hash for most recent change in this data structure.
24
+
25
+ b.keys.first.meta
26
+ # => Returns metadata hash for when this key was changed.
27
+
28
+ b[:key]
29
+ # => "value"
30
+
31
+ b[:key].meta
32
+ # => {:updated_at => 2010-10-28 22:43:33 +0000, :author => 'user', :line => 12, :commit => '25kjpp43', :file => 'file.yml'}
33
+
34
+ b[:key].class
35
+ # => Blaml::MetaNode
36
+
37
+ b[:key] == "value"
38
+ # => true
39
+
40
+ b[:key].value.class
41
+ # => String
42
+
43
+ b.to_value
44
+ # => Returns the full original data structure (not wrapped in meta classes)
45
+ # => {:key => "value"}
46
+
47
+ == INSTALL:
48
+
49
+ $ gem install blaml
50
+
51
+ This task will install any missing dependencies, run the tests/specs,
52
+ and generate the RDoc.
53
+
54
+ == LICENSE:
55
+
56
+ (The MIT License)
57
+
58
+ Copyright (c) 2011 Jeremie Castagna
59
+
60
+ Permission is hereby granted, free of charge, to any person obtaining
61
+ a copy of this software and associated documentation files (the
62
+ 'Software'), to deal in the Software without restriction, including
63
+ without limitation the rights to use, copy, modify, merge, publish,
64
+ distribute, sublicense, and/or sell copies of the Software, and to
65
+ permit persons to whom the Software is furnished to do so, subject to
66
+ the following conditions:
67
+
68
+ The above copyright notice and this permission notice shall be
69
+ included in all copies or substantial portions of the Software.
70
+
71
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
72
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
73
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
74
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
75
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
76
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
77
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'psych'
4
+ require 'rubygems'
5
+ require 'hoe'
6
+
7
+ Hoe.spec 'blaml' do
8
+ developer('Jeremie Castagna', 'yaksnrainbows@gmail.com')
9
+
10
+ self.readme_file = "README.rdoc"
11
+ self.history_file = "History.rdoc"
12
+ self.extra_rdoc_files = FileList['*.rdoc']
13
+ end
14
+
15
+ # vim: syntax=ruby
data/lib/blaml.rb ADDED
@@ -0,0 +1,93 @@
1
+ require 'time'
2
+ require 'psych'
3
+ require 'yaml'
4
+
5
+ require 'blaml/blamed_io'
6
+ require 'blaml/tree_builder'
7
+ require 'blaml/meta'
8
+ require 'blaml/to_ruby'
9
+
10
+ class Blaml
11
+
12
+ # This gem's version.
13
+ VERSION = "1.0.0.pre"
14
+
15
+ ###
16
+ # Load +yaml+ in to a Ruby data structure. If multiple documents are
17
+ # provided, the object contained in the first document will be returned.
18
+ #
19
+ # Example:
20
+ #
21
+ # Psych.load("--- a") # => 'a'
22
+ # Psych.load("---\n - a\n - b") # => ['a', 'b']
23
+
24
+ def self.load yaml
25
+ result = parse(yaml)
26
+ result ? result.to_blamed_ruby : result
27
+ end
28
+
29
+
30
+ ###
31
+ # Load multiple documents given in +yaml+. Returns the parsed documents
32
+ # as a list. For example:
33
+ #
34
+ # Psych.load_stream("--- foo\n...\n--- bar\n...") # => ['foo', 'bar']
35
+ #
36
+ def self.load_stream yaml
37
+ parse_stream(yaml).children.map { |child| child.to_blamed_ruby }
38
+ end
39
+
40
+
41
+ ###
42
+ # Load the document contained in +filename+. Returns the yaml contained in
43
+ # +filename+ as a ruby object
44
+
45
+ def self.load_file filename
46
+ self.load File.open(filename)
47
+ end
48
+
49
+
50
+ ###
51
+ # Parse a YAML string in +yaml+. Returns the first object of a YAML AST.
52
+ #
53
+ # Example:
54
+ #
55
+ # Psych.parse("---\n - a\n - b") # => #<Psych::Nodes::Sequence:0x00>
56
+ #
57
+ # See Psych::Nodes for more information about YAML AST.
58
+
59
+ def self.parse yaml
60
+ children = parse_stream(BlamedIO.new(yaml)).children
61
+ children.empty? ? false : children.first.children.first
62
+ end
63
+
64
+
65
+ ###
66
+ # Parse a file at +filename+. Returns the YAML AST.
67
+
68
+ def self.parse_file filename
69
+ File.open filename do |f|
70
+ parse f
71
+ end
72
+ end
73
+
74
+
75
+ ###
76
+ # Returns a default parser
77
+
78
+ def self.parser blaml
79
+ Psych::Parser.new(TreeBuilder.new(blaml))
80
+ end
81
+
82
+
83
+ ###
84
+ # Parse a YAML blame string in +yaml+.
85
+ # Returns the full AST for the YAML document with blame metadata.
86
+ # See Psych::parse_stream for more info.
87
+
88
+ def self.parse_stream blaml
89
+ parser = self.parser blaml
90
+ parser.parse blaml
91
+ parser.handler.root
92
+ end
93
+ end
@@ -0,0 +1,165 @@
1
+ class Blaml
2
+
3
+ ##
4
+ # IO wrapper that removes and parses blame data and makes it
5
+ # available as metadata
6
+
7
+ class BlamedIO
8
+
9
+ DATE_MATCHER =
10
+ %r{(\d+[-\s:]){6}[^\s]+}
11
+
12
+ META_MATCHER =
13
+ %r{([^\s]+)\s+([^\s]+)\s+\(([^\s]+)\s+(#{DATE_MATCHER})\s+(\d+)\)\s}
14
+
15
+ # Accessor for currently available metadata.
16
+ attr_reader :metadata
17
+
18
+
19
+ ##
20
+ # Create new BlamedIO with a string or IO object.
21
+
22
+ def initialize io
23
+ io = StringIO.new io if String === io
24
+
25
+ @io = io
26
+ @metadata = []
27
+ @meta_mode = true
28
+ end
29
+
30
+
31
+ %w{close closed? eof eof? rewind}.each do |meth|
32
+ class_eval "def #{meth}; @io.#{meth}; end"
33
+ end
34
+
35
+
36
+ ##
37
+ # Retrieves the metadata for a given string that matches
38
+ # the next line in the IO stream and deletes it permanently.
39
+
40
+ def shift_metadata_for str
41
+ meta, line = @metadata.first
42
+
43
+ if line.nil? || line.empty? || line[0..0] == '#'
44
+ @metadata.shift
45
+ return meta if str.empty?
46
+ end
47
+
48
+ meta, line = @metadata.first
49
+
50
+ if str.length > line.length
51
+
52
+ while str.include? @metadata.first[1] do
53
+ tmp_meta, line = @metadata.shift
54
+ meta = tmp_meta if
55
+ !meta || !meta[:updated_at] ||
56
+ meta && meta[:updated_at] &&
57
+ tmp_meta && tmp_meta[:updated_at] &&
58
+ tmp_meta[:updated_at] > meta[:updated_at]
59
+ end
60
+
61
+ else
62
+ @metadata.first[1] = line.split(str, 2).last.strip
63
+ @metadata.first[1].gsub!(%r{^:(\s+|$)}, "")
64
+ end
65
+
66
+ #puts "#{str} -> #{meta.inspect}"
67
+ meta
68
+ end
69
+
70
+
71
+ ##
72
+ # Removes leading spaces and dashes from a line of yaml data.
73
+
74
+ def sanitize_data str
75
+ str.to_s.strip.gsub(%r{^(-\s)+}, "")
76
+ end
77
+
78
+
79
+ ##
80
+ # Read single char as an integer.
81
+
82
+ def getc
83
+ read(1).unpack('c')[0]
84
+ end
85
+
86
+
87
+ ##
88
+ # Read from the IO instance and parse the blame data.
89
+
90
+ def read length=nil, buffer=nil
91
+ buffer ||= ""
92
+ meta_line = ""
93
+
94
+ until buffer.length == length || @io.eof?
95
+ if @meta_mode
96
+ read_meta
97
+ @meta_mode = false
98
+ end
99
+
100
+ char = @io.getc
101
+ buffer << char
102
+ meta_line << char
103
+
104
+ if buffer[-1..-1] == $/
105
+ @meta_mode = true
106
+ @metadata.last << sanitize_data(meta_line)
107
+ meta_line = ""
108
+ end
109
+ end
110
+
111
+ #puts @metadata.map{|i| i.inspect}.join("\n")
112
+ buffer
113
+ end
114
+
115
+
116
+ ##
117
+ # Read a single line.
118
+
119
+ def readline sep_string=$/
120
+ buffer = ""
121
+ until buffer[-1..-1] == sep_string || @io.eof?
122
+ buffer << read(1)
123
+ end
124
+
125
+ buffer
126
+ end
127
+
128
+ alias gets readline
129
+
130
+
131
+ ##
132
+ # Reads blame metadata.
133
+
134
+ def read_meta
135
+ buffer = ""
136
+
137
+ start_pos = @io.pos
138
+
139
+ until buffer =~ META_MATCHER do
140
+
141
+ # Got to the end of line with no metadata.
142
+ # Assume we're reading a regular yml IO.
143
+ if @io.eof? || buffer =~ %r{#{$/}$}
144
+ @io.pos = start_pos
145
+ @metadata << [nil]
146
+ return
147
+ end
148
+
149
+ buffer << @io.getc
150
+ end
151
+
152
+ meta_key = {
153
+ :file => $2,
154
+ :line => $6.to_i,
155
+ :author => $3,
156
+ :commit => $1,
157
+ :updated_at => Time.parse($4)
158
+ }
159
+
160
+ @metadata << [meta_key]
161
+
162
+ true
163
+ end
164
+ end
165
+ end
data/lib/blaml/meta.rb ADDED
@@ -0,0 +1,190 @@
1
+ class Blaml
2
+
3
+
4
+ ##
5
+ # Simple wrapper class to assign metadata to object instances.
6
+
7
+ class MetaNode
8
+
9
+
10
+ # The object to assign metadata to.
11
+ attr_reader :value
12
+
13
+ # The metadata assigned to the wrapped object.
14
+ attr_writer :meta
15
+
16
+ ##
17
+ # Create a new MetaNode with the value to wrap and optional metadata.
18
+
19
+ def initialize value, meta=nil
20
+ @value = value
21
+ @meta = meta
22
+ end
23
+
24
+
25
+ ##
26
+ # Checks for equality against the value attribute.
27
+
28
+ def == obj
29
+ case obj
30
+ when MetaNode then obj.value == @value
31
+ else
32
+ @value == obj
33
+ end
34
+ end
35
+
36
+
37
+ ##
38
+ # Accessor for the meta attribute.
39
+ # Overridden in MetaArray and MetaHash classes.
40
+
41
+ def meta
42
+ @meta
43
+ end
44
+
45
+
46
+ %w{to_s to_i to_f to_sym inspect}.each do |meth|
47
+ class_eval "def #{meth}; @value.#{meth}; end"
48
+ end
49
+
50
+
51
+ ##
52
+ # Sends non-defined methods to the value attribute
53
+
54
+ def method_missing name, *args, &block
55
+ @value.send name, *args, &block
56
+ end
57
+
58
+
59
+ ##
60
+ # Accessor for the value attribute.
61
+ # Overridden in MetaArray and MetaHash classes.
62
+
63
+ def to_value
64
+ @value
65
+ end
66
+ end
67
+
68
+
69
+ ##
70
+ # Wraps Array instances with metadata.
71
+
72
+ class MetaArray < MetaNode
73
+
74
+ ##
75
+ # Returns the child metadata with the most recent change.
76
+
77
+ def meta
78
+ meta = nil
79
+
80
+ @value.each do |val|
81
+ next unless val.respond_to? :meta
82
+
83
+ meta = val.meta if !meta ||
84
+ meta && val.meta && val.meta[:updated_at] > meta[:updated_at]
85
+ end
86
+
87
+ meta
88
+ end
89
+
90
+
91
+ ##
92
+ # Strips MetaNode wrapper from the value and calls to_value
93
+ # on all array elements.
94
+
95
+ def to_value
96
+ @value.map{|v| v.respond_to?(:to_value) ? v.to_value : v }
97
+ end
98
+ end
99
+
100
+
101
+ ##
102
+ # Wraps Hash instances with metadata.
103
+
104
+ class MetaHash < MetaNode
105
+
106
+ ##
107
+ # Access a value of the wrapped hash.
108
+
109
+ def [] key
110
+ @value.each{|k,v| return v if k == key}
111
+ end
112
+
113
+
114
+ ##
115
+ # Assign a value of the wrapped hash.
116
+
117
+ def []= key, val
118
+ @value.each{|k,v| @value[k] = val and return if k == key}
119
+ end
120
+
121
+
122
+ ##
123
+ # Merge with and modify the wrapped hash.
124
+
125
+ def merge! hash
126
+ hash.each do |k,v|
127
+ key = @value.keys.find{|vk| vk == k || k == vk } || k
128
+ @value.delete key
129
+ @value[k] = v
130
+ end
131
+
132
+ self
133
+ end
134
+
135
+
136
+ ##
137
+ # Create a new MetaHash merged with the given hash or metahash.
138
+
139
+ def merge hash
140
+ clone = @value.dup
141
+ hash.each do |k,v|
142
+ key = clone.keys.find{|vk| vk == k || k == vk } || k
143
+ clone.delete key
144
+ clone[k] = v
145
+ end
146
+
147
+ clone
148
+ end
149
+
150
+
151
+ ##
152
+ # Returns the child metadata with the most recent change.
153
+
154
+ def meta
155
+ meta = nil
156
+
157
+ @value.each do |key, val|
158
+ if val.respond_to? :meta
159
+ meta = val.meta if !meta ||
160
+ meta && val.meta && val.meta[:updated_at] > meta[:updated_at]
161
+ end
162
+
163
+ if key.respond_to? :meta
164
+ meta = key.meta if !meta ||
165
+ meta && val.meta && key.meta[:updated_at] > meta[:updated_at]
166
+ end
167
+ end
168
+
169
+ meta
170
+ end
171
+
172
+
173
+ ##
174
+ # Strips MetaNode wrapper from the value and calls to_value
175
+ # on all hash elements.
176
+
177
+ def to_value
178
+ clone = Hash.new
179
+
180
+ @value.each do |k, v|
181
+ key = k.respond_to?(:to_value) ? k.to_value : k
182
+ val = v.respond_to?(:to_value) ? v.to_value : v
183
+
184
+ clone[key] = val
185
+ end
186
+
187
+ clone
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,55 @@
1
+ class Blaml
2
+
3
+ ##
4
+ # Subclass Psych's to_ruby to build node values with metadata.
5
+
6
+ class ToRuby < Psych::Visitors::ToRuby
7
+
8
+ ##
9
+ # Wraps ruby object with MetaNode.
10
+ # See Psych::Visitors::ToRuby
11
+
12
+ def visit_Psych_Nodes_Scalar o
13
+ MetaNode.new super, o.meta
14
+ end
15
+
16
+
17
+ ##
18
+ # Wraps ruby Arrays with MetaArray.
19
+ # See Psych::Visitors::ToRuby
20
+
21
+ def visit_Psych_Nodes_Sequence o
22
+ seq = super
23
+ return seq unless Array === seq
24
+
25
+ MetaArray.new seq
26
+ end
27
+
28
+
29
+
30
+ ##
31
+ # Wraps ruby Arrays with MetaHash.
32
+ # See Psych::Visitors::ToRuby
33
+
34
+ def visit_Psych_Nodes_Mapping o
35
+ mapp = super
36
+ return mapp unless Hash === mapp
37
+
38
+ MetaHash.new mapp
39
+ end
40
+ end
41
+ end
42
+
43
+
44
+ class Psych::Nodes::Node
45
+
46
+ # Metadata accessor.
47
+ attr_accessor :meta
48
+
49
+ ##
50
+ # Tells Psych nodes to use Blaml::ToRuby.
51
+
52
+ def to_blamed_ruby
53
+ Blaml::ToRuby.new.accept self
54
+ end
55
+ end
@@ -0,0 +1,30 @@
1
+ class Blaml
2
+
3
+ class TreeBuilder < Psych::TreeBuilder
4
+
5
+ def initialize meta_binding
6
+ @meta_binding = meta_binding
7
+ super()
8
+ end
9
+
10
+
11
+ ##
12
+ # Sets the @meta instance variable to the metadata
13
+ # matched for the string object.
14
+ # Returns the passed instance.
15
+
16
+ def add_meta obj
17
+ obj.meta = @meta_binding.shift_metadata_for obj.value
18
+ obj
19
+ end
20
+
21
+
22
+ ##
23
+ # Calls parent and applies metadata to return value.
24
+
25
+ def scalar value, anchor, tag, plain, quoted, style
26
+ super
27
+ add_meta @last.children.last
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,8 @@
1
+ require "test/unit"
2
+ require "blaml"
3
+
4
+ class TestBlaml < Test::Unit::TestCase
5
+ def test_sanity
6
+ assert true
7
+ end
8
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: blaml
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0.pre
5
+ prerelease: 6
6
+ platform: ruby
7
+ authors:
8
+ - Jeremie Castagna
9
+ autorequire: !!null
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-02-03 00:00:00.000000000 -08:00
13
+ default_executable: !!null
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: hoe
17
+ requirement: &2156751600 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: 2.9.0
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: *2156751600
26
+ description: ! 'Blaml is a parser based on Psych (ruby 1.9 only) for yaml files that
27
+ have
28
+
29
+ been blamed with git.
30
+
31
+ Blaml parses the blamed yaml file and makes the blame metadata available on the
32
+
33
+ returned ruby object through a ''meta'' attribute.'
34
+ email:
35
+ - yaksnrainbows@gmail.com
36
+ executables: []
37
+ extensions: []
38
+ extra_rdoc_files:
39
+ - Manifest.txt
40
+ - History.rdoc
41
+ - README.rdoc
42
+ files:
43
+ - .autotest
44
+ - History.rdoc
45
+ - Manifest.txt
46
+ - README.rdoc
47
+ - Rakefile
48
+ - lib/blaml.rb
49
+ - lib/blaml/blamed_io.rb
50
+ - lib/blaml/meta.rb
51
+ - lib/blaml/to_ruby.rb
52
+ - lib/blaml/tree_builder.rb
53
+ - test/test_blaml.rb
54
+ - .gemtest
55
+ has_rdoc: true
56
+ homepage: http://github.com/yaksnrainbows/blaml
57
+ licenses: []
58
+ post_install_message: !!null
59
+ rdoc_options:
60
+ - --main
61
+ - README.rdoc
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ! '>'
74
+ - !ruby/object:Gem::Version
75
+ version: 1.3.1
76
+ requirements: []
77
+ rubyforge_project: blaml
78
+ rubygems_version: 1.5.0
79
+ signing_key: !!null
80
+ specification_version: 3
81
+ summary: Blaml is a parser based on Psych (ruby 1.9 only) for yaml files that have
82
+ been blamed with git
83
+ test_files:
84
+ - test/test_blaml.rb