ditz-str 0.0.1

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.
Files changed (65) hide show
  1. data/.document +5 -0
  2. data/Gemfile +14 -0
  3. data/Gemfile.lock +24 -0
  4. data/LICENSE +674 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.rdoc +19 -0
  7. data/README.txt +143 -0
  8. data/Rakefile +55 -0
  9. data/VERSION +1 -0
  10. data/bin/ditz-str +189 -0
  11. data/bugs/issue-02615b8c3dd0382c92f350ce2158ecfe94d11ef8.yaml +22 -0
  12. data/bugs/issue-06a3bbf35a60c4da2d8ea0fdc86164263126d6b2.yaml +22 -0
  13. data/bugs/issue-0c00c1d7fdffaad304e62d79d9b3d5e92547055b.yaml +38 -0
  14. data/bugs/issue-20dad4b4533d6d76d496fe5970098f1eb8efd561.yaml +26 -0
  15. data/bugs/issue-360ae6529dbc66358fde6b532cbea79ece37a670.yaml +22 -0
  16. data/bugs/issue-5177d61bf3c2783f71ef63e6e2c5e720247ef699.yaml +18 -0
  17. data/bugs/issue-695b564c210da1965a2bb38eef782178aead6952.yaml +26 -0
  18. data/bugs/issue-7d0ce6429a9fb5fa09ce3376a8921a5ecb7ecfe5.yaml +34 -0
  19. data/bugs/issue-a04462fa22ab6e1b02cfdd052d1f6c6f491f08f5.yaml +22 -0
  20. data/bugs/issue-bca54ca5107eabc3b281701041cc36ea0641cbdd.yaml +26 -0
  21. data/bugs/issue-d0c7d04b014d705c5fd865e4d487b5e5b6983c33.yaml +26 -0
  22. data/bugs/issue-f94b879842aa0274aa74fc2833252d4a06ec65cc.yaml +22 -0
  23. data/bugs/project.yaml +18 -0
  24. data/data/ditz-str/blue-check.png +0 -0
  25. data/data/ditz-str/close.rhtml +39 -0
  26. data/data/ditz-str/component.rhtml +38 -0
  27. data/data/ditz-str/dropdown.css +11 -0
  28. data/data/ditz-str/dropdown.js +58 -0
  29. data/data/ditz-str/edit_issue.rhtml +53 -0
  30. data/data/ditz-str/green-bar.png +0 -0
  31. data/data/ditz-str/green-check.png +0 -0
  32. data/data/ditz-str/header.gif +0 -0
  33. data/data/ditz-str/header_over.gif +0 -0
  34. data/data/ditz-str/index.rhtml +148 -0
  35. data/data/ditz-str/issue.rhtml +152 -0
  36. data/data/ditz-str/issue_table.rhtml +28 -0
  37. data/data/ditz-str/new_component.rhtml +28 -0
  38. data/data/ditz-str/new_issue.rhtml +57 -0
  39. data/data/ditz-str/new_release.rhtml +29 -0
  40. data/data/ditz-str/plugins/git-sync.rb +83 -0
  41. data/data/ditz-str/plugins/git.rb +153 -0
  42. data/data/ditz-str/plugins/issue-claiming.rb +174 -0
  43. data/data/ditz-str/red-check.png +0 -0
  44. data/data/ditz-str/release.rhtml +111 -0
  45. data/data/ditz-str/style.css +236 -0
  46. data/data/ditz-str/unassigned.rhtml +37 -0
  47. data/data/ditz-str/yellow-bar.png +0 -0
  48. data/ditz-str.gemspec +121 -0
  49. data/lib/ditzstr/brick.rb +251 -0
  50. data/lib/ditzstr/file-storage.rb +54 -0
  51. data/lib/ditzstr/hook.rb +67 -0
  52. data/lib/ditzstr/html.rb +104 -0
  53. data/lib/ditzstr/lowline.rb +201 -0
  54. data/lib/ditzstr/model-objects.rb +346 -0
  55. data/lib/ditzstr/model.rb +265 -0
  56. data/lib/ditzstr/operator.rb +593 -0
  57. data/lib/ditzstr/trollop.rb +614 -0
  58. data/lib/ditzstr/util.rb +61 -0
  59. data/lib/ditzstr/view.rb +16 -0
  60. data/lib/ditzstr/views.rb +157 -0
  61. data/lib/ditzstr.rb +69 -0
  62. data/man/ditz.1 +38 -0
  63. data/test/helper.rb +18 -0
  64. data/test/test_ditz-str.rb +7 -0
  65. metadata +219 -0
@@ -0,0 +1,265 @@
1
+ require 'yaml'
2
+ require 'sha1'
3
+ require "ditzstr/lowline"; include Lowline
4
+ require "ditzstr/util"
5
+
6
+ class Time
7
+ alias :old_to_yaml :to_yaml
8
+ def to_yaml(opts = {})
9
+ self.utc.old_to_yaml(opts)
10
+ end
11
+ end
12
+
13
+ module DitzStr
14
+
15
+ class ModelObject
16
+ class ModelError < StandardError; end
17
+
18
+ def initialize
19
+ @values = {}
20
+ @serialized_values = {}
21
+ self.class.fields.map { |f, opts| @values[f] = [] if opts[:multi] }
22
+ end
23
+
24
+ ## yamlability
25
+ def self.yaml_domain; "ditz.rubyforge.org,2008-03-06" end
26
+ def self.yaml_other_thing; name.split('::').last.dcfirst end
27
+ def to_yaml_type; "!#{self.class.yaml_domain}/#{self.class.yaml_other_thing}" end
28
+ def self.inherited subclass
29
+ YAML.add_domain_type(yaml_domain, subclass.yaml_other_thing) do |type, val|
30
+ o = subclass.new
31
+ val.each do |k, v|
32
+ m = "__serialized_#{k}="
33
+ if o.respond_to? m
34
+ o.send m, v
35
+ else
36
+ $stderr.puts "warning: unknown field #{k.inspect} in YAML for #{type}; ignoring"
37
+ end
38
+ end
39
+ o.unchanged!
40
+ o
41
+ end
42
+ end
43
+
44
+ ## override these two to model per-field transformations between disk and
45
+ ## memory.
46
+ ##
47
+ ## convert disk form => memory form
48
+ def deserialized_form_of field, value
49
+ @serialized_values[field]
50
+ end
51
+
52
+ ## convert memory form => disk form
53
+ def serialized_form_of field, value
54
+ @values[field]
55
+ end
56
+
57
+ ## add a new field to a model object
58
+ def self.field name, opts={}
59
+ @fields ||= [] # can't use a hash because we need to preserve field order
60
+ raise ModelError, "field with name #{name} already defined" if @fields.any? { |k, v| k == name }
61
+ @fields << [name, opts]
62
+
63
+ if opts[:multi]
64
+ single_name = name.to_s.sub(/s$/, "") # oh yeah
65
+ define_method "add_#{single_name}" do |obj|
66
+ array = send(name)
67
+ raise ModelError, "already has a #{single_name} with name #{obj.name.inspect}" if obj.respond_to?(:name) && array.any? { |o| o.name == obj.name }
68
+ changed!
69
+ @serialized_values.delete name
70
+ array << obj
71
+ end
72
+
73
+ define_method "drop_#{single_name}" do |obj|
74
+ return unless @values[name].delete obj
75
+ @serialized_values.delete name
76
+ changed!
77
+ obj
78
+ end
79
+ end
80
+
81
+ define_method "#{name}=" do |o|
82
+ changed!
83
+ @serialized_values.delete name
84
+ @values[name] = o
85
+ end
86
+
87
+ define_method "__serialized_#{name}=" do |o|
88
+ changed!
89
+ @values.delete name
90
+ @serialized_values[name] = o
91
+ end
92
+
93
+ define_method "__serialized_#{name}" do
94
+ @serialized_values[name]
95
+ end
96
+
97
+ define_method name do
98
+ return @values[name] if @values.member?(name)
99
+ @values[name] = deserialized_form_of name, @serialized_values[name]
100
+ end
101
+ end
102
+
103
+ def self.field_names; @fields.map { |name, opts| name } end
104
+ class << self
105
+ attr_reader :fields, :values, :serialized_values
106
+ end
107
+
108
+ def self.changes_are_logged
109
+ define_method(:changes_are_logged?) { true }
110
+ field :log_events, :multi => true, :ask => false
111
+ end
112
+
113
+ def self.from fn
114
+ returning YAML::load_file(fn) do |o|
115
+ raise ModelError, "error loading from yaml file #{fn.inspect}: expected a #{self}, got a #{o.class}" unless o.class == self
116
+ o.pathname = fn if o.respond_to? :pathname=
117
+
118
+ o.class.fields.each do |f, opts|
119
+ m = "__serialized_#{f}"
120
+ if opts[:multi] && o.send(m).nil?
121
+ $stderr.puts "Warning: corrected nil multi-field #{f}"
122
+ o.send "#{m}=", []
123
+ end
124
+ end
125
+ end
126
+ end
127
+
128
+ def to_s
129
+ "<#{self.class.name}: " + self.class.field_names.map { |f| "#{f}: " + (@values[f].to_s || @serialized_values[f]).inspect }.join(", ") + ">"
130
+ end
131
+
132
+ def inspect; to_s end
133
+
134
+ ## depth-first search on all reachable ModelObjects. fuck yeah.
135
+ def each_modelobject
136
+ seen = {}
137
+ to_see = [self]
138
+ until to_see.empty?
139
+ cur = to_see.pop
140
+ seen[cur] = true
141
+ yield cur
142
+ cur.class.field_names.each do |f|
143
+ val = cur.send(f)
144
+ next if seen[val]
145
+ if val.is_a?(ModelObject)
146
+ to_see.push val
147
+ elsif val.is_a?(Array)
148
+ to_see += val.select { |v| v.is_a?(ModelObject) }
149
+ end
150
+ end
151
+ end
152
+ end
153
+
154
+ def save! fn
155
+ #FileUtils.mv fn, "#{fn}~", :force => true rescue nil
156
+ File.open(fn, "w") { |f| f.puts to_yaml }
157
+ self
158
+ end
159
+
160
+ def to_yaml opts={}
161
+ YAML::quick_emit(object_id, opts) do |out|
162
+ out.map(taguri, nil) do |map|
163
+ self.class.fields.each do |f, fops|
164
+ v = if @serialized_values.member?(f)
165
+ @serialized_values[f]
166
+ else
167
+ @serialized_values[f] = serialized_form_of f, @values[f]
168
+ end
169
+
170
+ map.add f.to_s, v
171
+ end
172
+ end
173
+ end
174
+ end
175
+
176
+ def log what, who, comment
177
+ add_log_event([Time.now, who, what, comment || ""])
178
+ self
179
+ end
180
+
181
+ def changed?; @changed ||= false end
182
+ def changed!; @changed = true end
183
+ def unchanged!; @changed = false end
184
+
185
+ class << self
186
+ ## creates the object, prompting the user when necessary. can take
187
+ ## a :with => { hash } parameter for pre-filling model fields.
188
+ ##
189
+ ## can also take a :defaults_from => obj parameter for pre-filling model
190
+ ## fields from another object with (some of) those fields. kinda like a
191
+ ## bizarre interactive copy constructor.
192
+ def create_interactively opts={}
193
+ o = self.new
194
+ generator_args = opts[:args] || []
195
+ @fields.each do |name, field_opts|
196
+ val = if opts[:with] && opts[:with][name]
197
+ opts[:with][name]
198
+ elsif(found, x = generate_field_value(o, field_opts, generator_args)) && found
199
+ x
200
+ else
201
+ q = field_opts[:prompt] || name.to_s.capitalize
202
+ if field_opts[:multiline]
203
+ ## multiline options currently aren't allowed to have a default
204
+ ## value, so just ask.
205
+ ask_multiline q
206
+ else
207
+ default = if opts[:defaults_from] && opts[:defaults_from].respond_to?(name) && (x = opts[:defaults_from].send(name))
208
+ x
209
+ else
210
+ default = generate_field_default o, field_opts, generator_args
211
+ end
212
+ ask q, :default => default
213
+ end
214
+ end
215
+ o.send "#{name}=", val
216
+ end
217
+ o
218
+ end
219
+
220
+ ## creates the object, filling in fields from 'vals', and throwing a
221
+ ## ModelError when it can't find all the requisite fields
222
+ def create generator_args, vals={}
223
+ o = self.new
224
+ @fields.each do |name, opts|
225
+ val = if vals[name]
226
+ vals[name]
227
+ elsif(found, x = generate_field_value(o, opts, generator_args)) && found
228
+ x
229
+ else
230
+ raise ModelError, "missing required field #{name}"
231
+ end
232
+ o.send "#{name}=", val
233
+ end
234
+ o
235
+ end
236
+
237
+ private
238
+
239
+ ## get the value for a field if it can be automatically determined
240
+ ## returns [success, value] (because a successful value can be ni)
241
+ def generate_field_value o, opts, args
242
+ if opts[:generator].is_a? Proc
243
+ [true, opts[:generator].call(*args)]
244
+ elsif opts[:generator]
245
+ [true, o.send(opts[:generator], *args)]
246
+ elsif opts[:ask] == false # nil counts as true here
247
+ [true, opts[:default] || (opts[:multi] ? [] : nil)]
248
+ else
249
+ [false, nil]
250
+ end
251
+ end
252
+
253
+ def generate_field_default o, opts, args
254
+ if opts[:default_generator].is_a? Proc
255
+ opts[:default_generator].call(*args)
256
+ elsif opts[:default_generator]
257
+ o.send opts[:default_generator], *args
258
+ elsif opts[:default]
259
+ opts[:default]
260
+ end
261
+ end
262
+ end
263
+ end
264
+
265
+ end