ursm-ditz 0.4 → 0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/Changelog +15 -0
- data/INSTALL +20 -0
- data/LICENSE +674 -0
- data/Manifest.txt +40 -0
- data/PLUGINS.txt +140 -0
- data/README.txt +43 -27
- data/Rakefile +36 -3
- data/ReleaseNotes +6 -0
- data/bin/ditz +49 -63
- data/contrib/completion/ditz.bash +25 -9
- data/lib/ditz.rb +52 -7
- data/lib/ditz/file-storage.rb +54 -0
- data/lib/{hook.rb → ditz/hook.rb} +1 -1
- data/lib/{html.rb → ditz/html.rb} +42 -4
- data/lib/{lowline.rb → ditz/lowline.rb} +31 -11
- data/lib/{model-objects.rb → ditz/model-objects.rb} +53 -21
- data/lib/ditz/model.rb +321 -0
- data/lib/{operator.rb → ditz/operator.rb} +122 -67
- data/lib/ditz/plugins/git-sync.rb +83 -0
- data/lib/{plugins → ditz/plugins}/git.rb +57 -18
- data/lib/ditz/plugins/issue-claiming.rb +174 -0
- data/lib/ditz/plugins/issue-labeling.rb +161 -0
- data/lib/{util.rb → ditz/util.rb} +4 -0
- data/lib/{view.rb → ditz/view.rb} +0 -0
- data/lib/{views.rb → ditz/views.rb} +7 -4
- data/man/{ditz.1 → man1/ditz.1} +1 -1
- data/setup.rb +1585 -0
- data/share/ditz/blue-check.png +0 -0
- data/{lib → share/ditz}/component.rhtml +7 -5
- data/share/ditz/green-bar.png +0 -0
- data/share/ditz/green-check.png +0 -0
- data/share/ditz/index.rhtml +130 -0
- data/share/ditz/issue.rhtml +119 -0
- data/share/ditz/issue_table.rhtml +28 -0
- data/share/ditz/red-check.png +0 -0
- data/share/ditz/release.rhtml +98 -0
- data/share/ditz/style.css +226 -0
- data/share/ditz/unassigned.rhtml +23 -0
- data/share/ditz/yellow-bar.png +0 -0
- metadata +50 -28
- data/lib/index.rhtml +0 -113
- data/lib/issue.rhtml +0 -111
- data/lib/issue_table.rhtml +0 -33
- data/lib/model.rb +0 -208
- data/lib/plugins/issue-claiming.rb +0 -92
- data/lib/release.rhtml +0 -69
- data/lib/style.css +0 -127
- data/lib/trollop.rb +0 -518
- data/lib/unassigned.rhtml +0 -31
- data/lib/vendor/yaml_waml.rb +0 -28
@@ -0,0 +1,23 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
3
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
4
|
+
|
5
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
6
|
+
<head>
|
7
|
+
<title>Issues not assigned to any release</title>
|
8
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
9
|
+
<link rel="stylesheet" href="style.css" type="text/css" />
|
10
|
+
</head>
|
11
|
+
<body>
|
12
|
+
|
13
|
+
<div class="main">
|
14
|
+
<h1>Issues not assigned to any release</h1>
|
15
|
+
<div class="backptr"><%= link_to "index", "« #{project.name} project page" %></div>
|
16
|
+
|
17
|
+
<h2>All issues</h2>
|
18
|
+
<%= render "issue_table", :show_component => false, :show_release => true %>
|
19
|
+
</div>
|
20
|
+
<div class="footer">Generated by <a href="http://ditz.rubyforge.org/">ditz</a>.</div>
|
21
|
+
|
22
|
+
</body>
|
23
|
+
</html>
|
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ursm-ditz
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: "0.
|
4
|
+
version: "0.5"
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- William Morgan
|
@@ -9,56 +9,78 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-
|
12
|
+
date: 2008-10-01 00:00:00 -07:00
|
13
13
|
default_executable: ditz
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
|
-
name:
|
16
|
+
name: trollop
|
17
17
|
version_requirement:
|
18
18
|
version_requirements: !ruby/object:Gem::Requirement
|
19
19
|
requirements:
|
20
20
|
- - ">="
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version: 1.
|
22
|
+
version: "1.9"
|
23
23
|
version:
|
24
|
-
|
24
|
+
- !ruby/object:Gem::Dependency
|
25
|
+
name: kakutani-yaml_waml
|
26
|
+
version_requirement:
|
27
|
+
version_requirements: !ruby/object:Gem::Requirement
|
28
|
+
requirements:
|
29
|
+
- - ">="
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: "0.3"
|
32
|
+
version:
|
33
|
+
description: "Ditz is a simple, light-weight distributed issue tracker designed to work with distributed version control systems like git, darcs, Mercurial, and Bazaar. It can also be used with centralized systems like SVN. Ditz maintains an issue database directory on disk, with files written in a line-based and human-editable format. This directory can be kept under version control, alongside project code. Ditz provides a simple, console-based interface for creating and updating the issue database files, and some basic static HTML generation capabilities for producing world-readable status pages (for a demo, see the ditz ditz page). Ditz includes a robust plugin system for adding commands, model fields, and modifying output. See PLUGINS.txt for documentation on the pre-shipped plugins. Ditz currently offers no central public method of bug submission. == USING DITZ There are several different ways to use Ditz: 1. Treat issue change the same as code change: include it as part of commits, and merge it with changes from other developers, resolving conflicts in the usual manner. 2. Keep the issue database in the repository but in a separate branch. Issue changes can be managed by your VCS, but is not tied directly to code commits. 3. Keep the issue database separate and not under VCS at all."
|
25
34
|
email: wmorgan-ditz@masanjin.net
|
26
35
|
executables:
|
27
36
|
- ditz
|
28
37
|
extensions: []
|
29
38
|
|
30
39
|
extra_rdoc_files:
|
40
|
+
- Manifest.txt
|
41
|
+
- PLUGINS.txt
|
31
42
|
- README.txt
|
32
43
|
files:
|
33
44
|
- Changelog
|
45
|
+
- INSTALL
|
46
|
+
- LICENSE
|
47
|
+
- Manifest.txt
|
48
|
+
- PLUGINS.txt
|
34
49
|
- README.txt
|
35
50
|
- Rakefile
|
36
51
|
- ReleaseNotes
|
37
52
|
- bin/ditz
|
38
|
-
- lib/component.rhtml
|
39
|
-
- lib/ditz.rb
|
40
|
-
- lib/hook.rb
|
41
|
-
- lib/html.rb
|
42
|
-
- lib/index.rhtml
|
43
|
-
- lib/issue.rhtml
|
44
|
-
- lib/issue_table.rhtml
|
45
|
-
- lib/lowline.rb
|
46
|
-
- lib/model-objects.rb
|
47
|
-
- lib/model.rb
|
48
|
-
- lib/operator.rb
|
49
|
-
- lib/release.rhtml
|
50
|
-
- lib/trollop.rb
|
51
|
-
- lib/style.css
|
52
|
-
- lib/unassigned.rhtml
|
53
|
-
- lib/util.rb
|
54
|
-
- lib/view.rb
|
55
|
-
- lib/views.rb
|
56
|
-
- lib/plugins/git.rb
|
57
|
-
- lib/plugins/issue-claiming.rb
|
58
|
-
- lib/vendor/yaml_waml.rb
|
59
|
-
- contrib/completion/ditz.bash
|
60
53
|
- contrib/completion/_ditz.zsh
|
61
|
-
-
|
54
|
+
- contrib/completion/ditz.bash
|
55
|
+
- lib/ditz.rb
|
56
|
+
- lib/ditz/file-storage.rb
|
57
|
+
- lib/ditz/hook.rb
|
58
|
+
- lib/ditz/html.rb
|
59
|
+
- lib/ditz/lowline.rb
|
60
|
+
- lib/ditz/model-objects.rb
|
61
|
+
- lib/ditz/model.rb
|
62
|
+
- lib/ditz/operator.rb
|
63
|
+
- lib/ditz/plugins/git-sync.rb
|
64
|
+
- lib/ditz/plugins/git.rb
|
65
|
+
- lib/ditz/plugins/issue-claiming.rb
|
66
|
+
- lib/ditz/plugins/issue-labeling.rb
|
67
|
+
- lib/ditz/util.rb
|
68
|
+
- lib/ditz/view.rb
|
69
|
+
- lib/ditz/views.rb
|
70
|
+
- share/ditz/index.rhtml
|
71
|
+
- share/ditz/issue.rhtml
|
72
|
+
- share/ditz/issue_table.rhtml
|
73
|
+
- share/ditz/release.rhtml
|
74
|
+
- share/ditz/unassigned.rhtml
|
75
|
+
- share/ditz/component.rhtml
|
76
|
+
- share/ditz/style.css
|
77
|
+
- share/ditz/blue-check.png
|
78
|
+
- share/ditz/green-bar.png
|
79
|
+
- share/ditz/green-check.png
|
80
|
+
- share/ditz/red-check.png
|
81
|
+
- share/ditz/yellow-bar.png
|
82
|
+
- man/man1/ditz.1
|
83
|
+
- setup.rb
|
62
84
|
has_rdoc: true
|
63
85
|
homepage: http://ditz.rubyforge.org
|
64
86
|
post_install_message:
|
data/lib/index.rhtml
DELETED
@@ -1,113 +0,0 @@
|
|
1
|
-
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
2
|
-
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
3
|
-
|
4
|
-
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
5
|
-
<head>
|
6
|
-
<title><%= project.name %> Issue Tracker</title>
|
7
|
-
<meta http-equiv="Content-Type" content="text/html; charset=utf8" />
|
8
|
-
<link rel="stylesheet" href="style.css" type="text/css" />
|
9
|
-
</head>
|
10
|
-
<body>
|
11
|
-
|
12
|
-
<h1><%= project.name %> Issue Tracker</h1>
|
13
|
-
|
14
|
-
<h2>Upcoming Releases</h2>
|
15
|
-
<% if upcoming_releases.empty? %>
|
16
|
-
<p>No upcoming releases.</p>
|
17
|
-
<% else %>
|
18
|
-
<ul>
|
19
|
-
<% upcoming_releases.each do |r| %>
|
20
|
-
<%
|
21
|
-
issues = project.issues_for_release r
|
22
|
-
num_done = issues.count_of { |i| i.closed? }
|
23
|
-
pct_done = issues.size == 0 ? 100 : (100.0 * num_done / issues.size)
|
24
|
-
open_issues = project.group_issues(issues.select { |i| i.open? })
|
25
|
-
%>
|
26
|
-
<li>
|
27
|
-
<%= link_to r, "Release #{r.name}" %>:
|
28
|
-
<% if issues.empty? %>
|
29
|
-
no issues
|
30
|
-
<% else %>
|
31
|
-
<%= sprintf "%.0f%%", pct_done %> complete;
|
32
|
-
<% if open_issues.empty? %>
|
33
|
-
ready for release!
|
34
|
-
<% else %>
|
35
|
-
<%= open_issues.map { |n,is| n.to_s.pluralize is.size }.join(' and ') %> open.
|
36
|
-
<% end %>
|
37
|
-
<% end %>
|
38
|
-
</li>
|
39
|
-
<% end %>
|
40
|
-
</ul>
|
41
|
-
<% end %>
|
42
|
-
|
43
|
-
<h2>Past Releases</h2>
|
44
|
-
<% if past_releases.empty? %>
|
45
|
-
<p>No past releases.</p>
|
46
|
-
<% else %>
|
47
|
-
<ul>
|
48
|
-
<% past_releases.sort_by { |r| r.release_time }.reverse.each do |r| %>
|
49
|
-
<li><%= link_to r, "Release #{r.name}" %>, released <%= r.release_time.pretty_date %>. </li>
|
50
|
-
<% end %>
|
51
|
-
</ul>
|
52
|
-
<% end %>
|
53
|
-
|
54
|
-
<h2>Unassigned issues</h2>
|
55
|
-
<%
|
56
|
-
issues = project.unassigned_issues
|
57
|
-
open_issues = issues.select { |i| i.open? }
|
58
|
-
%>
|
59
|
-
<p>
|
60
|
-
<% if issues.empty? %>
|
61
|
-
No unassigned issues.
|
62
|
-
<% else %>
|
63
|
-
<%= link_to "unassigned", "unassigned issue".pluralize(issues.size).capitalize %>; <%= open_issues.size.to_pretty_s %> of them open.
|
64
|
-
<% end %>
|
65
|
-
</p>
|
66
|
-
|
67
|
-
<% if components.size > 1 %>
|
68
|
-
<h2>Open Issues by component</h2>
|
69
|
-
<ul>
|
70
|
-
<% components.each do |c| %>
|
71
|
-
<%
|
72
|
-
open_issues = project.group_issues(project.issues_for_component(c).select { |i| i.open? })
|
73
|
-
%>
|
74
|
-
<li>
|
75
|
-
<% if open_issues.empty? %>
|
76
|
-
<span class="dimmed">
|
77
|
-
<%= link_to c, c.name %>:
|
78
|
-
no open issues.
|
79
|
-
</span>
|
80
|
-
<% else %>
|
81
|
-
<%= link_to c, c.name %>:
|
82
|
-
<%= open_issues.map { |n,is| n.to_s.pluralize is.size }.join(' and ') %> open.
|
83
|
-
<% end %>
|
84
|
-
</li>
|
85
|
-
<% end %>
|
86
|
-
</ul>
|
87
|
-
<% end %>
|
88
|
-
|
89
|
-
<h2>Recent activity</h2>
|
90
|
-
|
91
|
-
<table>
|
92
|
-
<% project.issues.map { |i| i.log_events.map { |e| [e, i] } }.
|
93
|
-
flatten_one_level.
|
94
|
-
sort_by { |e| e.first.first }.
|
95
|
-
reverse[0 ... 10].
|
96
|
-
each do |(date, who, what, comment), i| %>
|
97
|
-
<tr>
|
98
|
-
<td><%= date.pretty_date %></td>
|
99
|
-
<td class="issuename">
|
100
|
-
<%= link_issue_names project, h(i.name) %>
|
101
|
-
<%= what %> by <%= who.shortened_email %></td>
|
102
|
-
</tr>
|
103
|
-
<% if comment && comment =~ /\S/ %>
|
104
|
-
<tr><td></td><td><i><%= link_issue_names project, h(comment) %></i></td></tr>
|
105
|
-
<% end %>
|
106
|
-
<% end %>
|
107
|
-
</table>
|
108
|
-
|
109
|
-
<p class="footer">Generated by <a
|
110
|
-
href="http://ditz.rubyforge.org/">ditz</a>.</p>
|
111
|
-
|
112
|
-
</body>
|
113
|
-
</html>
|
data/lib/issue.rhtml
DELETED
@@ -1,111 +0,0 @@
|
|
1
|
-
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
2
|
-
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
3
|
-
|
4
|
-
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
5
|
-
<head>
|
6
|
-
<title><%= issue.title %></title>
|
7
|
-
<meta http-equiv="Content-Type" content="text/html; charset=utf8" />
|
8
|
-
<link rel="stylesheet" href="style.css" type="text/css" />
|
9
|
-
</head>
|
10
|
-
|
11
|
-
<body>
|
12
|
-
|
13
|
-
<div><%= link_to "index", "« #{project.name} project page" %></div>
|
14
|
-
|
15
|
-
<h1><%= link_issue_names project, issue.title %></h1>
|
16
|
-
|
17
|
-
<%= link_issue_names project, p(issue.desc) %>
|
18
|
-
|
19
|
-
<table>
|
20
|
-
<tr>
|
21
|
-
<td class="attrname">Id:</td>
|
22
|
-
<td class="attrval"><%= issue.id %></td>
|
23
|
-
</tr>
|
24
|
-
|
25
|
-
<tr>
|
26
|
-
<td class="attrname">Type:</td>
|
27
|
-
<td class="attrval"><%= issue.type %></td>
|
28
|
-
</tr>
|
29
|
-
|
30
|
-
<tr>
|
31
|
-
<td class="attrname">Creation time:</td>
|
32
|
-
<td class="attrval"><%= issue.creation_time %></td>
|
33
|
-
</tr>
|
34
|
-
|
35
|
-
<tr>
|
36
|
-
<td class="attrname">Creator:</td>
|
37
|
-
<td class="attrval"><%=obscured_email issue.reporter %></td>
|
38
|
-
</tr>
|
39
|
-
|
40
|
-
<% unless issue.references.empty? %>
|
41
|
-
<tr>
|
42
|
-
<td class="attrname">References:</td>
|
43
|
-
<td class="attrval">
|
44
|
-
<% issue.references.each_with_index do |r, i| %>
|
45
|
-
[<%= i + 1 %>] <%= link_to r, r %><br/>
|
46
|
-
<% end %>
|
47
|
-
</td>
|
48
|
-
</tr>
|
49
|
-
|
50
|
-
<% end %>
|
51
|
-
|
52
|
-
<tr>
|
53
|
-
<td class="attrname">Release:</td>
|
54
|
-
<td class="attrval">
|
55
|
-
<% if release %>
|
56
|
-
<%= link_to release, release.name %>
|
57
|
-
<% if release.released? %>
|
58
|
-
(released <%= release.release_time.pretty_date %>)
|
59
|
-
<% else %>
|
60
|
-
(unreleased)
|
61
|
-
<% end %>
|
62
|
-
<% else %>
|
63
|
-
<%= link_to "unassigned", "unassigned" %>
|
64
|
-
<% end %>
|
65
|
-
</td>
|
66
|
-
</tr>
|
67
|
-
|
68
|
-
<tr>
|
69
|
-
<td class="attrname">Component:</td>
|
70
|
-
<td class="attrval"><%= link_to component, component.name %></td>
|
71
|
-
</tr>
|
72
|
-
|
73
|
-
<tr>
|
74
|
-
<td class="attrname">Status:</td>
|
75
|
-
<td class="attrval">
|
76
|
-
<%= issue.status_string %><% if issue.closed? %>: <%= issue.disposition_string %><% end %>
|
77
|
-
</td>
|
78
|
-
</tr>
|
79
|
-
|
80
|
-
<%= extra_summary_html %>
|
81
|
-
|
82
|
-
</table>
|
83
|
-
|
84
|
-
<%= extra_details_html %>
|
85
|
-
|
86
|
-
<h2>Issue log</h2>
|
87
|
-
|
88
|
-
<table>
|
89
|
-
<% issue.log_events.each_with_index do |(time, who, what, comment), i| %>
|
90
|
-
<% if i % 2 == 0 %>
|
91
|
-
<tr class="logentryeven">
|
92
|
-
<% else %>
|
93
|
-
<tr class="logentryodd">
|
94
|
-
<% end %>
|
95
|
-
<td class="logtime"><%=t time %></td>
|
96
|
-
<td class="logwho"><%=obscured_email who %></td>
|
97
|
-
<td class="logwhat"><%=h what %></td>
|
98
|
-
</tr>
|
99
|
-
<tr><td colspan="3" class="logcomment">
|
100
|
-
<% if comment.empty? %>
|
101
|
-
<% else %>
|
102
|
-
<%= link_issue_names project, p(comment) %>
|
103
|
-
<% end %>
|
104
|
-
</td></tr>
|
105
|
-
<% end %>
|
106
|
-
</table>
|
107
|
-
|
108
|
-
<p class="footer">Generated by <a
|
109
|
-
href="http://ditz.rubyforge.org/">ditz</a>.</p>
|
110
|
-
</body>
|
111
|
-
</html>
|
data/lib/issue_table.rhtml
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
<table>
|
2
|
-
<% issues.sort_by { |i| i.sort_order }.each do |i| %>
|
3
|
-
<tr>
|
4
|
-
<td class="issuestatus_<%= i.status %>">
|
5
|
-
<% if i.closed? %>
|
6
|
-
<%= i.disposition_string %>
|
7
|
-
<% else %>
|
8
|
-
<%= i.status_string %>
|
9
|
-
<% end %>
|
10
|
-
</td>
|
11
|
-
<td class="issuename">
|
12
|
-
<%= fancy_issue_link_for i %>
|
13
|
-
<%= i.bug? ? '(bug)' : '' %>
|
14
|
-
</td>
|
15
|
-
<% if show_release %>
|
16
|
-
<td class="issuerelease">
|
17
|
-
<% if i.release %>
|
18
|
-
<% r = project.release_for i.release %>
|
19
|
-
in <%= link_to r, "release #{i.release}" %>
|
20
|
-
(<%= r.status %>)
|
21
|
-
<% else %>
|
22
|
-
<% end %>
|
23
|
-
</td>
|
24
|
-
<% end %>
|
25
|
-
<% if show_component %>
|
26
|
-
<td class="issuecomponent">
|
27
|
-
component <%= link_to project.component_for(i.component), i.component %>
|
28
|
-
</td>
|
29
|
-
<% end %>
|
30
|
-
</tr>
|
31
|
-
<% end %>
|
32
|
-
</table>
|
33
|
-
|
data/lib/model.rb
DELETED
@@ -1,208 +0,0 @@
|
|
1
|
-
require 'yaml'
|
2
|
-
require 'sha1'
|
3
|
-
require "lowline"; include Lowline
|
4
|
-
require "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 Ditz
|
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 name do
|
94
|
-
return @values[name] if @values.member?(name)
|
95
|
-
@values[name] = deserialized_form_of name, @serialized_values[name]
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
def self.field_names; @fields.map { |name, opts| name } end
|
100
|
-
class << self
|
101
|
-
attr_reader :fields, :values, :serialized_values
|
102
|
-
end
|
103
|
-
|
104
|
-
def self.changes_are_logged
|
105
|
-
define_method(:changes_are_logged?) { true }
|
106
|
-
field :log_events, :multi => true, :ask => false
|
107
|
-
end
|
108
|
-
|
109
|
-
def self.from fn
|
110
|
-
returning YAML::load_file(fn) do |o|
|
111
|
-
raise ModelError, "error loading from yaml file #{fn.inspect}: expected a #{self}, got a #{o.class}" unless o.class == self
|
112
|
-
o.pathname = fn if o.respond_to? :pathname=
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
def to_s
|
117
|
-
"<#{self.class.name}: " + self.class.field_names.map { |f| "#{f}: " + (@values[f].to_s || @serialized_values[f]).inspect }.join(", ") + ">"
|
118
|
-
end
|
119
|
-
|
120
|
-
def inspect; to_s end
|
121
|
-
|
122
|
-
## depth-first search on all reachable ModelObjects. fuck yeah.
|
123
|
-
def each_modelobject
|
124
|
-
seen = {}
|
125
|
-
to_see = [self]
|
126
|
-
until to_see.empty?
|
127
|
-
cur = to_see.pop
|
128
|
-
seen[cur] = true
|
129
|
-
yield cur
|
130
|
-
cur.class.field_names.each do |f|
|
131
|
-
val = cur.send(f)
|
132
|
-
next if seen[val]
|
133
|
-
if val.is_a?(ModelObject)
|
134
|
-
to_see.push val
|
135
|
-
elsif val.is_a?(Array)
|
136
|
-
to_see += val.select { |v| v.is_a?(ModelObject) }
|
137
|
-
end
|
138
|
-
end
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
def save! fn
|
143
|
-
#FileUtils.mv fn, "#{fn}~", :force => true rescue nil
|
144
|
-
File.open(fn, "w") { |f| f.puts to_yaml }
|
145
|
-
self
|
146
|
-
end
|
147
|
-
|
148
|
-
def to_yaml opts={}
|
149
|
-
ret = YAML::quick_emit(object_id, opts) do |out|
|
150
|
-
out.map(taguri, nil) do |map|
|
151
|
-
self.class.fields.each do |f, fops|
|
152
|
-
v = if @serialized_values.member?(f)
|
153
|
-
@serialized_values[f]
|
154
|
-
else
|
155
|
-
@serialized_values[f] = serialized_form_of f, @values[f]
|
156
|
-
end
|
157
|
-
|
158
|
-
map.add f.to_s, v
|
159
|
-
end
|
160
|
-
end
|
161
|
-
end
|
162
|
-
ret.decode
|
163
|
-
end
|
164
|
-
|
165
|
-
def log what, who, comment
|
166
|
-
add_log_event([Time.now, who, what, comment || ""])
|
167
|
-
self
|
168
|
-
end
|
169
|
-
|
170
|
-
def changed?; @changed ||= false end
|
171
|
-
def changed!; @changed = true end
|
172
|
-
def unchanged!; @changed = false end
|
173
|
-
|
174
|
-
def self.create_interactively opts={}
|
175
|
-
o = self.new
|
176
|
-
args = opts[:args] || []
|
177
|
-
@fields.each do |name, field_opts|
|
178
|
-
val = if opts[:with] && opts[:with][name]
|
179
|
-
opts[:with][name]
|
180
|
-
elsif field_opts[:generator].is_a? Proc
|
181
|
-
field_opts[:generator].call(*args)
|
182
|
-
elsif field_opts[:generator]
|
183
|
-
o.send field_opts[:generator], *args
|
184
|
-
elsif field_opts[:ask] == false # nil counts as true here
|
185
|
-
field_opts[:default] || (field_opts[:multi] ? [] : nil)
|
186
|
-
else
|
187
|
-
q = field_opts[:prompt] || name.to_s.capitalize
|
188
|
-
if field_opts[:multiline]
|
189
|
-
ask_multiline q
|
190
|
-
else
|
191
|
-
default = if field_opts[:default_generator].is_a? Proc
|
192
|
-
field_opts[:default_generator].call(*args)
|
193
|
-
elsif field_opts[:default_generator]
|
194
|
-
o.send field_opts[:default_generator], *args
|
195
|
-
elsif field_opts[:default]
|
196
|
-
field_opts[:default]
|
197
|
-
end
|
198
|
-
|
199
|
-
ask q, :default => default
|
200
|
-
end
|
201
|
-
end
|
202
|
-
o.send("#{name}=", val)
|
203
|
-
end
|
204
|
-
o
|
205
|
-
end
|
206
|
-
end
|
207
|
-
|
208
|
-
end
|