taco_it 1.3.1 → 1.4.0

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