taco_it 1.3.1 → 1.4.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.
data/lib/taco/taco.rb ADDED
@@ -0,0 +1,158 @@
1
+ require 'tempfile'
2
+ require 'fileutils'
3
+ require 'time'
4
+
5
+ require 'taco/issue'
6
+
7
+ class Taco
8
+ HOME_DIR = '.taco'
9
+
10
+ attr_accessor :home
11
+
12
+ class NotFound < Exception; end
13
+ class Ambiguous < Exception; end
14
+
15
+ def initialize(root_path=nil)
16
+ @home = File.join(root_path || Dir.getwd, HOME_DIR)
17
+ end
18
+
19
+ def init!
20
+ raise IOError.new("Could not create #{@home}\nDirectory already exists.") if File.exists?(@home)
21
+
22
+ FileUtils.mkdir_p(@home)
23
+
24
+ "Initialized #{@home}"
25
+ end
26
+
27
+ def write!(issue_or_issues)
28
+ issues = issue_or_issues.is_a?(Array) ? issue_or_issues : [ issue_or_issues ]
29
+
30
+ issues.each do |issue|
31
+ the_json = issue.to_json # do this first so we don't bother the filesystem if the issue is invalid
32
+ open(File.join(@home, issue.id), 'w') { |f| f.write(the_json) }
33
+ end
34
+
35
+ issue_or_issues
36
+ end
37
+
38
+ def read(issue_id)
39
+ issue_path = File.join(@home, issue_id)
40
+
41
+ unless File.exist? issue_path
42
+ entries = Dir[File.join(@home, "*#{issue_id}*")]
43
+
44
+ raise NotFound.new("Issue not found.") unless entries.size > 0
45
+ unless entries.size == 1
46
+ issue_list = entries.map do |entry|
47
+ issue = read(File.basename(entry))
48
+ "#{issue.id} : #{issue.summary}"
49
+ end
50
+ raise Ambiguous.new("Found several matching issues:\n%s" % issue_list.join("\n"))
51
+ end
52
+
53
+ issue_path = entries[0]
54
+ issue_id = File.basename entries[0]
55
+ end
56
+
57
+ the_json = open(issue_path) { |f| f.read }
58
+
59
+ issue = Issue.from_json the_json
60
+
61
+ raise Issue::Invalid.new("Issue ID does not match filename: #{issue.id} != #{issue_id}") unless issue.id == issue_id
62
+
63
+ issue
64
+ end
65
+
66
+ def list(opts={})
67
+ filter_match = if opts.fetch(:filters, []).size > 0
68
+ conditions = opts[:filters].map do |filter|
69
+ attr, val = filter.split(':')
70
+ %Q|i.send("#{attr}") == "#{val}"|
71
+ end.join ' && '
72
+
73
+ # FIXME: eval-ing user input? madness!
74
+ eval "Proc.new { |i| #{conditions} }"
75
+ else
76
+ nil
77
+ end
78
+
79
+ ids = Dir.glob("#{@home}/*")
80
+
81
+ ids.map do |name|
82
+ id = File.basename name
83
+ issue = Issue.from_json(open(name) { |f| f.read })
84
+
85
+ next unless filter_match.nil? || filter_match.call(issue)
86
+
87
+ raise Issue::Invalid.new("Issue ID does not match filename: #{issue.id} != #{id}") unless issue.id == id
88
+
89
+ short_id = 8.upto(id.size).each do |n|
90
+ short_id = id[0...n]
91
+ break short_id unless ids.count { |i| i.include? short_id } > 1
92
+ end
93
+
94
+ if opts[:short_ids]
95
+ [ issue, short_id ]
96
+ else
97
+ issue
98
+ end
99
+ end.reject(&:nil?).sort_by { |thing| opts[:short_ids] ? thing[0] : thing}
100
+ end
101
+ end
102
+
103
+ class IssueEditor
104
+ def initialize(taco, retry_path)
105
+ @taco, @retry_path = taco, retry_path
106
+ end
107
+
108
+ def new_issue!(opts={})
109
+ if opts[:from_file]
110
+ text = open(opts[:from_file]) { |f| f.read }
111
+ else
112
+ raise ArgumentError.new("Please define $EDITOR in your environment.") unless ENV['EDITOR']
113
+ text = invoke_editor(opts[:template])
114
+ end
115
+
116
+ write_issue!(Issue.from_template(text), text) if text
117
+ end
118
+
119
+ def edit_issue!(issue, opts={})
120
+ if text = invoke_editor(opts[:template] || issue.to_template)
121
+ write_issue!(issue.update_from_template!(text), text)
122
+ end
123
+ end
124
+
125
+ private
126
+ def write_issue!(issue, text)
127
+ begin
128
+ @taco.write! issue
129
+ rescue Exception => e
130
+ open(@retry_path, 'w') { |f| f.write(text) } if text
131
+ raise e
132
+ end
133
+
134
+ File.unlink @retry_path rescue nil
135
+ issue
136
+ end
137
+
138
+ def invoke_editor(template)
139
+ text = nil
140
+ file = Tempfile.new('taco')
141
+
142
+ begin
143
+ file.write(template)
144
+ file.close
145
+
146
+ cmd = "$EDITOR #{file.path}"
147
+ system(cmd)
148
+
149
+ open(file.path) do |f|
150
+ text = f.read
151
+ end
152
+ ensure
153
+ File.unlink(file.path) rescue nil
154
+ end
155
+
156
+ text == template ? nil : text
157
+ end
158
+ end
@@ -0,0 +1,24 @@
1
+ class TacoRc
2
+ class ParseError < Exception; end
3
+
4
+ def initialize(path)
5
+ raise ArgumentError.new("no such file: #{path}") unless File.exists? path
6
+ @path = path
7
+ end
8
+
9
+ def update_schema!(schema)
10
+ open(@path) do |f|
11
+ f.readlines.each_with_index do |line, index|
12
+ next if line =~ /^#/ || line =~ /^\s*$/
13
+
14
+ raise ParseError.new("Parse error on line #{index+1} of #{@path}: line does not begin with schema_attr_update") unless line =~ /^\s*schema_attr_update /
15
+
16
+ begin
17
+ eval "#{schema}.#{line.strip}"
18
+ rescue KeyError, TypeError, NameError => e
19
+ raise ParseError.new("Parse error on line #{index+1} of #{@path}: #{e.to_s}")
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end