[#262] Convert line endings to UNIX

This commit is contained in:
Eric Davis 2011-05-29 12:00:18 -07:00
parent 05a4c4616f
commit a85b6780d2
35 changed files with 9625 additions and 9625 deletions

View File

@ -1,6 +1,6 @@
<h2><%=h @status %></h2> <h2><%=h @status %></h2>
<p id="errorExplanation"><%=h @message %></p> <p id="errorExplanation"><%=h @message %></p>
<p><a href="javascript:history.back()">Back</a></p> <p><a href="javascript:history.back()">Back</a></p>
<% html_title @status %> <% html_title @status %>

View File

@ -1,112 +1,112 @@
<% content_for :styles do %> <% content_for :styles do %>
body { font:80% Verdana,Tahoma,Arial,sans-serif; } body { font:80% Verdana,Tahoma,Arial,sans-serif; }
h1, h2, h3, h4 { font-family: Trebuchet MS,Georgia,"Times New Roman",serif; } h1, h2, h3, h4 { font-family: Trebuchet MS,Georgia,"Times New Roman",serif; }
pre, code { font-size:120%; } pre, code { font-size:120%; }
pre code { font-size:100%; } pre code { font-size:100%; }
pre { pre {
margin: 1em 1em 1em 1.6em; margin: 1em 1em 1em 1.6em;
padding: 2px; padding: 2px;
background-color: #fafafa; background-color: #fafafa;
border: 1px solid #dadada; border: 1px solid #dadada;
width:95%; width:95%;
overflow-x: auto; overflow-x: auto;
} }
a.new { color: #b73535; } a.new { color: #b73535; }
.CodeRay .c { color:#666; } .CodeRay .c { color:#666; }
.CodeRay .cl { color:#B06; font-weight:bold } .CodeRay .cl { color:#B06; font-weight:bold }
.CodeRay .dl { color:black } .CodeRay .dl { color:black }
.CodeRay .fu { color:#06B; font-weight:bold } .CodeRay .fu { color:#06B; font-weight:bold }
.CodeRay .il { background: #eee } .CodeRay .il { background: #eee }
.CodeRay .il .idl { font-weight: bold; color: #888 } .CodeRay .il .idl { font-weight: bold; color: #888 }
.CodeRay .iv { color:#33B } .CodeRay .iv { color:#33B }
.CodeRay .r { color:#080; font-weight:bold } .CodeRay .r { color:#080; font-weight:bold }
.CodeRay .s { background-color:#fff0f0 } .CodeRay .s { background-color:#fff0f0 }
.CodeRay .s .dl { color:#710 } .CodeRay .s .dl { color:#710 }
<% end %> <% end %>
<% html_title "Wiki Formatting" %> <% html_title "Wiki Formatting" %>
<h1><a name="1" class="wiki-page"></a>Wiki Formatting</h1> <h1><a name="1" class="wiki-page"></a>Wiki Formatting</h1>
<h2><a name="2" class="wiki-page"></a>Links</h2> <h2><a name="2" class="wiki-page"></a>Links</h2>
<h3><a name="3" class="wiki-page"></a>ChiliProject links</h3> <h3><a name="3" class="wiki-page"></a>ChiliProject links</h3>
<p>ChiliProject allows hyperlinking between issues, changesets and wiki pages from anywhere wiki formatting is used.</p> <p>ChiliProject allows hyperlinking between issues, changesets and wiki pages from anywhere wiki formatting is used.</p>
<ul> <ul>
<li>Link to an issue: <strong>#124</strong> (displays <del><a href="#" class="issue" title="bulk edit doesn't change the category or fixed version properties (Closed)">#124</a></del>, link is striked-through if the issue is closed)</li> <li>Link to an issue: <strong>#124</strong> (displays <del><a href="#" class="issue" title="bulk edit doesn't change the category or fixed version properties (Closed)">#124</a></del>, link is striked-through if the issue is closed)</li>
<li>Link to a changeset: <strong>r758</strong> (displays <a href="#" class="changeset" title="Search engine now only searches objects the user is allowed to view.">r758</a>)</li> <li>Link to a changeset: <strong>r758</strong> (displays <a href="#" class="changeset" title="Search engine now only searches objects the user is allowed to view.">r758</a>)</li>
<li>Link to a changeset with a non-numeric hash: <strong>commit:c6f4d0fd</strong> (displays <a href="#" class="changeset">c6f4d0fd</a>).</li> <li>Link to a changeset with a non-numeric hash: <strong>commit:c6f4d0fd</strong> (displays <a href="#" class="changeset">c6f4d0fd</a>).</li>
<li>Link to a changeset of another project: <strong>sandbox:r758</strong> (displays <a href="#" class="changeset" title="Search engine now only searches objects the user is allowed to view.">sandbox:r758</a>)</li> <li>Link to a changeset of another project: <strong>sandbox:r758</strong> (displays <a href="#" class="changeset" title="Search engine now only searches objects the user is allowed to view.">sandbox:r758</a>)</li>
<li>Link to a changeset with a non-numeric hash: <strong>sandbox:c6f4d0fd</strong> (displays <a href="#" class="changeset">sandbox:c6f4d0fd</a>).</li> <li>Link to a changeset with a non-numeric hash: <strong>sandbox:c6f4d0fd</strong> (displays <a href="#" class="changeset">sandbox:c6f4d0fd</a>).</li>
</ul> </ul>
<p>Wiki links:</p> <p>Wiki links:</p>
<ul> <ul>
<li><strong>[[Guide]]</strong> displays a link to the page named 'Guide': <a href="#" class="wiki-page">Guide</a></li> <li><strong>[[Guide]]</strong> displays a link to the page named 'Guide': <a href="#" class="wiki-page">Guide</a></li>
<li><strong>[[Guide#further-reading]]</strong> takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: <a href="#" class="wiki-page">Guide</a></li> <li><strong>[[Guide#further-reading]]</strong> takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: <a href="#" class="wiki-page">Guide</a></li>
<li><strong>[[Guide|User manual]]</strong> displays a link to the same page but with a different text: <a href="#" class="wiki-page">User manual</a></li> <li><strong>[[Guide|User manual]]</strong> displays a link to the same page but with a different text: <a href="#" class="wiki-page">User manual</a></li>
</ul> </ul>
<p>You can also link to pages of an other project wiki:</p> <p>You can also link to pages of an other project wiki:</p>
<ul> <ul>
<li><strong>[[sandbox:some page]]</strong> displays a link to the page named 'Some page' of the Sandbox wiki</li> <li><strong>[[sandbox:some page]]</strong> displays a link to the page named 'Some page' of the Sandbox wiki</li>
<li><strong>[[sandbox:]]</strong> displays a link to the Sandbox wiki main page</li> <li><strong>[[sandbox:]]</strong> displays a link to the Sandbox wiki main page</li>
</ul> </ul>
<p>Wiki links are displayed in red if the page doesn't exist yet, eg: <a href="#" class="wiki-page new">Nonexistent page</a>.</p> <p>Wiki links are displayed in red if the page doesn't exist yet, eg: <a href="#" class="wiki-page new">Nonexistent page</a>.</p>
<p>Links to other resources:</p> <p>Links to other resources:</p>
<ul> <ul>
<li>Documents: <li>Documents:
<ul> <ul>
<li><strong>document#17</strong> (link to document with id 17)</li> <li><strong>document#17</strong> (link to document with id 17)</li>
<li><strong>document:Greetings</strong> (link to the document with title "Greetings")</li> <li><strong>document:Greetings</strong> (link to the document with title "Greetings")</li>
<li><strong>document:"Some document"</strong> (double quotes can be used when document title contains spaces)</li> <li><strong>document:"Some document"</strong> (double quotes can be used when document title contains spaces)</li>
<li><strong>sandbox:document:"Some document"</strong> (link to a document with title "Some document" in other project "sandbox")</li> <li><strong>sandbox:document:"Some document"</strong> (link to a document with title "Some document" in other project "sandbox")</li>
</ul></li> </ul></li>
</ul> </ul>
<ul> <ul>
<li>Versions: <li>Versions:
<ul> <ul>
<li><strong>version#3</strong> (link to version with id 3)</li> <li><strong>version#3</strong> (link to version with id 3)</li>
<li><strong>version:1.0.0</strong> (link to version named "1.0.0")</li> <li><strong>version:1.0.0</strong> (link to version named "1.0.0")</li>
<li><strong>version:"1.0 beta 2"</strong></li> <li><strong>version:"1.0 beta 2"</strong></li>
<li><strong>sandbox:version:1.0.0</strong> (link to version "1.0.0" in the project "sandbox")</li> <li><strong>sandbox:version:1.0.0</strong> (link to version "1.0.0" in the project "sandbox")</li>
</ul></li> </ul></li>
</ul> </ul>
<ul> <ul>
<li>Attachments: <li>Attachments:
<ul> <ul>
<li><strong>attachment:file.zip</strong> (link to the attachment of the current object named file.zip)</li> <li><strong>attachment:file.zip</strong> (link to the attachment of the current object named file.zip)</li>
<li>For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)</li> <li>For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)</li>
</ul></li> </ul></li>
</ul> </ul>
<ul> <ul>
<li>Repository files: <li>Repository files:
<ul> <ul>
<li><strong>source:some/file</strong> (link to the file located at /some/file in the project's repository)</li> <li><strong>source:some/file</strong> (link to the file located at /some/file in the project's repository)</li>
<li><strong>source:some/file@52</strong> (link to the file's revision 52)</li> <li><strong>source:some/file@52</strong> (link to the file's revision 52)</li>
<li><strong>source:some/file#L120</strong> (link to line 120 of the file)</li> <li><strong>source:some/file#L120</strong> (link to line 120 of the file)</li>
<li><strong>source:some/file@52#L120</strong> (link to line 120 of the file's revision 52)</li> <li><strong>source:some/file@52#L120</strong> (link to line 120 of the file's revision 52)</li>
<li><strong>source:"some file@52#L120"</strong> (use double quotes when the URL contains spaces</li> <li><strong>source:"some file@52#L120"</strong> (use double quotes when the URL contains spaces</li>
<li><strong>export:some/file</strong> (force the download of the file)</li> <li><strong>export:some/file</strong> (force the download of the file)</li>
<li><strong>sandbox:source:some/file</strong> (link to the file located at /some/file in the repository of the project "sandbox")</li> <li><strong>sandbox:source:some/file</strong> (link to the file located at /some/file in the repository of the project "sandbox")</li>
<li><strong>sandbox:export:some/file</strong> (force the download of the file)</li> <li><strong>sandbox:export:some/file</strong> (force the download of the file)</li>
</ul></li> </ul></li>
</ul> </ul>
<ul> <ul>
<li>Forum messages: <li>Forum messages:
<ul> <ul>
<li><strong>message#1218</strong> (link to message with id 1218)</li> <li><strong>message#1218</strong> (link to message with id 1218)</li>
@ -124,139 +124,139 @@
<p>Escaping:</p> <p>Escaping:</p>
<ul> <ul>
<li>You can prevent ChiliProject links from being parsed by preceding them with an exclamation mark: !</li> <li>You can prevent ChiliProject links from being parsed by preceding them with an exclamation mark: !</li>
</ul> </ul>
<h3><a name="4" class="wiki-page"></a>External links</h3> <h3><a name="4" class="wiki-page"></a>External links</h3>
<p>HTTP URLs and email addresses are automatically turned into clickable links:</p> <p>HTTP URLs and email addresses are automatically turned into clickable links:</p>
<pre> <pre>
https://www.chiliproject.org, someone@foo.bar https://www.chiliproject.org, someone@foo.bar
</pre> </pre>
<p>displays: <a class="external" href="https://www.chiliproject.org">https://www.chiliproject.org</a>, <a href="mailto:someone@foo.bar" class="email">someone@foo.bar</a></p> <p>displays: <a class="external" href="https://www.chiliproject.org">https://www.chiliproject.org</a>, <a href="mailto:someone@foo.bar" class="email">someone@foo.bar</a></p>
<p>If you want to display a specific text instead of the URL, you can use the standard textile syntax:</p> <p>If you want to display a specific text instead of the URL, you can use the standard textile syntax:</p>
<pre> <pre>
"ChiliProject web site":https://www.chiliproject.org "ChiliProject web site":https://www.chiliproject.org
</pre> </pre>
<p>displays: <a href="https://www.chiliproject.org" class="external">ChiliProject web site</a></p> <p>displays: <a href="https://www.chiliproject.org" class="external">ChiliProject web site</a></p>
<h2><a name="5" class="wiki-page"></a>Text formatting</h2> <h2><a name="5" class="wiki-page"></a>Text formatting</h2>
<p>For things such as headlines, bold, tables, lists, ChiliProject supports Textile syntax. See <a class="external" href="http://www.textism.com/tools/textile/">http://www.textism.com/tools/textile/</a> for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.</p> <p>For things such as headlines, bold, tables, lists, ChiliProject supports Textile syntax. See <a class="external" href="http://www.textism.com/tools/textile/">http://www.textism.com/tools/textile/</a> for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.</p>
<h3><a name="6" class="wiki-page"></a>Font style</h3> <h3><a name="6" class="wiki-page"></a>Font style</h3>
<pre> <pre>
* *bold* * *bold*
* _italic_ * _italic_
* _*bold italic*_ * _*bold italic*_
* +underline+ * +underline+
* -strike-through- * -strike-through-
</pre> </pre>
<p>Display:</p> <p>Display:</p>
<ul> <ul>
<li><strong>bold</strong></li> <li><strong>bold</strong></li>
<li><em>italic</em></li> <li><em>italic</em></li>
<li><em>*bold italic*</em></li> <li><em>*bold italic*</em></li>
<li><ins>underline</ins></li> <li><ins>underline</ins></li>
<li><del>strike-through</del></li> <li><del>strike-through</del></li>
</ul> </ul>
<h3><a name="7" class="wiki-page"></a>Inline images</h3> <h3><a name="7" class="wiki-page"></a>Inline images</h3>
<ul> <ul>
<li><strong>!image_url!</strong> displays an image located at image_url (textile syntax)</li> <li><strong>!image_url!</strong> displays an image located at image_url (textile syntax)</li>
<li><strong>!>image_url!</strong> right floating image</li> <li><strong>!>image_url!</strong> right floating image</li>
<li>If you have an image attached to your wiki page, it can be displayed inline using its filename: <strong>!attached_image.png!</strong></li> <li>If you have an image attached to your wiki page, it can be displayed inline using its filename: <strong>!attached_image.png!</strong></li>
</ul> </ul>
<h3><a name="8" class="wiki-page"></a>Headings</h3> <h3><a name="8" class="wiki-page"></a>Headings</h3>
<pre> <pre>
h1. Heading h1. Heading
h2. Subheading h2. Subheading
h3. Subsubheading h3. Subsubheading
</pre> </pre>
<p>ChiliProject assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.</p> <p>ChiliProject assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.</p>
<h3><a name="9" class="wiki-page"></a>Paragraphs</h3> <h3><a name="9" class="wiki-page"></a>Paragraphs</h3>
<pre> <pre>
p>. right aligned p>. right aligned
p=. centered p=. centered
</pre> </pre>
<p style="text-align:center;">This is a centered paragraph.</p> <p style="text-align:center;">This is a centered paragraph.</p>
<h3><a name="10" class="wiki-page"></a>Blockquotes</h3> <h3><a name="10" class="wiki-page"></a>Blockquotes</h3>
<p>Start the paragraph with <strong>bq.</strong></p> <p>Start the paragraph with <strong>bq.</strong></p>
<pre> <pre>
bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern. bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
To go live, all you need to add is a database and a web server. To go live, all you need to add is a database and a web server.
</pre> </pre>
<p>Display:</p> <p>Display:</p>
<blockquote> <blockquote>
<p>Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.<br />To go live, all you need to add is a database and a web server.</p> <p>Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.<br />To go live, all you need to add is a database and a web server.</p>
</blockquote> </blockquote>
<h3><a name="11" class="wiki-page"></a>Table of content</h3> <h3><a name="11" class="wiki-page"></a>Table of content</h3>
<pre> <pre>
{{toc}} => left aligned toc {{toc}} => left aligned toc
{{>toc}} => right aligned toc {{>toc}} => right aligned toc
</pre> </pre>
<h2><a name="12" class="wiki-page"></a>Macros</h2> <h2><a name="12" class="wiki-page"></a>Macros</h2>
<p>ChiliProject has the following builtin macros:</p> <p>ChiliProject has the following builtin macros:</p>
<p><dl><dt><code>hello_world</code></dt><dd><p>Sample macro.</p></dd><dt><code>include</code></dt><dd><p>Include a wiki page. Example:</p> <p><dl><dt><code>hello_world</code></dt><dd><p>Sample macro.</p></dd><dt><code>include</code></dt><dd><p>Include a wiki page. Example:</p>
<pre><code>{{include(Foo)}}</code></pre></dd><dt><code>macro_list</code></dt><dd><p>Displays a list of all available macros, including description if available.</p></dd></dl></p> <pre><code>{{include(Foo)}}</code></pre></dd><dt><code>macro_list</code></dt><dd><p>Displays a list of all available macros, including description if available.</p></dd></dl></p>
<h2><a name="13" class="wiki-page"></a>Code highlighting</h2> <h2><a name="13" class="wiki-page"></a>Code highlighting</h2>
<p>Code highlightment relies on <a href="http://coderay.rubychan.de/" class="external">CodeRay</a>, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.</p> <p>Code highlightment relies on <a href="http://coderay.rubychan.de/" class="external">CodeRay</a>, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.</p>
<p>You can highlight code in your wiki page using this syntax:</p> <p>You can highlight code in your wiki page using this syntax:</p>
<pre> <pre>
&lt;pre&gt;&lt;code class="ruby"&gt; &lt;pre&gt;&lt;code class="ruby"&gt;
Place you code here. Place you code here.
&lt;/code&gt;&lt;/pre&gt; &lt;/code&gt;&lt;/pre&gt;
</pre> </pre>
<p>Example:</p> <p>Example:</p>
<pre><code class="ruby CodeRay"><span class="no"> 1</span> <span class="c"># The Greeter class</span> <pre><code class="ruby CodeRay"><span class="no"> 1</span> <span class="c"># The Greeter class</span>
<span class="no"> 2</span> <span class="r">class</span> <span class="cl">Greeter</span> <span class="no"> 2</span> <span class="r">class</span> <span class="cl">Greeter</span>
<span class="no"> 3</span> <span class="r">def</span> <span class="fu">initialize</span>(name) <span class="no"> 3</span> <span class="r">def</span> <span class="fu">initialize</span>(name)
<span class="no"> 4</span> <span class="iv">@name</span> = name.capitalize <span class="no"> 4</span> <span class="iv">@name</span> = name.capitalize
<span class="no"> 5</span> <span class="r">end</span> <span class="no"> 5</span> <span class="r">end</span>
<span class="no"> 6</span> <span class="no"> 6</span>
<span class="no"> 7</span> <span class="r">def</span> <span class="fu">salute</span> <span class="no"> 7</span> <span class="r">def</span> <span class="fu">salute</span>
<span class="no"> 8</span> puts <span class="s"><span class="dl">"</span><span class="k">Hello </span><span class="il"><span class="idl">#{</span><span class="iv">@name</span><span class="idl">}</span></span><span class="k">!</span><span class="dl">"</span></span> <span class="no"> 8</span> puts <span class="s"><span class="dl">"</span><span class="k">Hello </span><span class="il"><span class="idl">#{</span><span class="iv">@name</span><span class="idl">}</span></span><span class="k">!</span><span class="dl">"</span></span>
<span class="no"> 9</span> <span class="r">end</span> <span class="no"> 9</span> <span class="r">end</span>
<span class="no"><strong>10</strong></span> <span class="r">end</span> <span class="no"><strong>10</strong></span> <span class="r">end</span>
</code> </code>
</pre> </pre>

View File

@ -1,42 +1,42 @@
<% form_tag(project_project_enumerations_path(@project), :method => :put, :class => "tabular") do %> <% form_tag(project_project_enumerations_path(@project), :method => :put, :class => "tabular") do %>
<table class="list"> <table class="list">
<thead><tr> <thead><tr>
<th><%= l(:field_name) %></th> <th><%= l(:field_name) %></th>
<th><%= l(:enumeration_system_activity) %></th> <th><%= l(:enumeration_system_activity) %></th>
<% TimeEntryActivity.new.available_custom_fields.each do |value| %> <% TimeEntryActivity.new.available_custom_fields.each do |value| %>
<th><%= h value.name %></th> <th><%= h value.name %></th>
<% end %> <% end %>
<th style="width:15%;"><%= l(:field_active) %></th> <th style="width:15%;"><%= l(:field_active) %></th>
</tr></thead> </tr></thead>
<% @project.activities(true).each do |enumeration| %> <% @project.activities(true).each do |enumeration| %>
<% fields_for "enumerations[#{enumeration.id}]", enumeration do |ff| %> <% fields_for "enumerations[#{enumeration.id}]", enumeration do |ff| %>
<tr class="<%= cycle('odd', 'even') %>"> <tr class="<%= cycle('odd', 'even') %>">
<td> <td>
<%= ff.hidden_field :parent_id, :value => enumeration.id unless enumeration.project %> <%= ff.hidden_field :parent_id, :value => enumeration.id unless enumeration.project %>
<%= h(enumeration) %> <%= h(enumeration) %>
</td> </td>
<td align="center" style="width:15%;"><%= checked_image !enumeration.project %></td> <td align="center" style="width:15%;"><%= checked_image !enumeration.project %></td>
<% enumeration.custom_field_values.each do |value| %> <% enumeration.custom_field_values.each do |value| %>
<td align="center"> <td align="center">
<%= custom_field_tag "enumerations[#{enumeration.id}]", value %> <%= custom_field_tag "enumerations[#{enumeration.id}]", value %>
</td> </td>
<% end %> <% end %>
<td align="center" style="width:15%;"> <td align="center" style="width:15%;">
<%= ff.check_box :active %> <%= ff.check_box :active %>
</td> </td>
</tr> </tr>
<% end %> <% end %>
<% end %> <% end %>
</table> </table>
<div class="contextual"> <div class="contextual">
<%= link_to(l(:button_reset), project_project_enumerations_path(@project), <%= link_to(l(:button_reset), project_project_enumerations_path(@project),
:method => :delete, :method => :delete,
:confirm => l(:text_are_you_sure), :confirm => l(:text_are_you_sure),
:class => 'icon icon-del') %> :class => 'icon icon-del') %>
</div> </div>
<%= submit_tag l(:button_save) %> <%= submit_tag l(:button_save) %>
<% end %> <% end %>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -21,19 +21,19 @@ class Setup < ActiveRecord::Migration
# model removed # model removed
class Permission < ActiveRecord::Base; end class Permission < ActiveRecord::Base; end
def self.up def self.up
create_table "attachments", :force => true do |t| create_table "attachments", :force => true do |t|
t.column "container_id", :integer, :default => 0, :null => false t.column "container_id", :integer, :default => 0, :null => false
t.column "container_type", :string, :limit => 30, :default => "", :null => false t.column "container_type", :string, :limit => 30, :default => "", :null => false
t.column "filename", :string, :default => "", :null => false t.column "filename", :string, :default => "", :null => false
t.column "disk_filename", :string, :default => "", :null => false t.column "disk_filename", :string, :default => "", :null => false
t.column "filesize", :integer, :default => 0, :null => false t.column "filesize", :integer, :default => 0, :null => false
t.column "content_type", :string, :limit => 60, :default => "" t.column "content_type", :string, :limit => 60, :default => ""
t.column "digest", :string, :limit => 40, :default => "", :null => false t.column "digest", :string, :limit => 40, :default => "", :null => false
t.column "downloads", :integer, :default => 0, :null => false t.column "downloads", :integer, :default => 0, :null => false
t.column "author_id", :integer, :default => 0, :null => false t.column "author_id", :integer, :default => 0, :null => false
t.column "created_on", :timestamp t.column "created_on", :timestamp
end end
create_table "auth_sources", :force => true do |t| create_table "auth_sources", :force => true do |t|
t.column "type", :string, :limit => 30, :default => "", :null => false t.column "type", :string, :limit => 30, :default => "", :null => false
@ -49,143 +49,143 @@ class Setup < ActiveRecord::Migration
t.column "attr_mail", :string, :limit => 30 t.column "attr_mail", :string, :limit => 30
t.column "onthefly_register", :boolean, :default => false, :null => false t.column "onthefly_register", :boolean, :default => false, :null => false
end end
create_table "custom_fields", :force => true do |t| create_table "custom_fields", :force => true do |t|
t.column "type", :string, :limit => 30, :default => "", :null => false t.column "type", :string, :limit => 30, :default => "", :null => false
t.column "name", :string, :limit => 30, :default => "", :null => false t.column "name", :string, :limit => 30, :default => "", :null => false
t.column "field_format", :string, :limit => 30, :default => "", :null => false t.column "field_format", :string, :limit => 30, :default => "", :null => false
t.column "possible_values", :text t.column "possible_values", :text
t.column "regexp", :string, :default => "" t.column "regexp", :string, :default => ""
t.column "min_length", :integer, :default => 0, :null => false t.column "min_length", :integer, :default => 0, :null => false
t.column "max_length", :integer, :default => 0, :null => false t.column "max_length", :integer, :default => 0, :null => false
t.column "is_required", :boolean, :default => false, :null => false t.column "is_required", :boolean, :default => false, :null => false
t.column "is_for_all", :boolean, :default => false, :null => false t.column "is_for_all", :boolean, :default => false, :null => false
end end
create_table "custom_fields_projects", :id => false, :force => true do |t| create_table "custom_fields_projects", :id => false, :force => true do |t|
t.column "custom_field_id", :integer, :default => 0, :null => false t.column "custom_field_id", :integer, :default => 0, :null => false
t.column "project_id", :integer, :default => 0, :null => false t.column "project_id", :integer, :default => 0, :null => false
end end
create_table "custom_fields_trackers", :id => false, :force => true do |t| create_table "custom_fields_trackers", :id => false, :force => true do |t|
t.column "custom_field_id", :integer, :default => 0, :null => false t.column "custom_field_id", :integer, :default => 0, :null => false
t.column "tracker_id", :integer, :default => 0, :null => false t.column "tracker_id", :integer, :default => 0, :null => false
end end
create_table "custom_values", :force => true do |t| create_table "custom_values", :force => true do |t|
t.column "customized_type", :string, :limit => 30, :default => "", :null => false t.column "customized_type", :string, :limit => 30, :default => "", :null => false
t.column "customized_id", :integer, :default => 0, :null => false t.column "customized_id", :integer, :default => 0, :null => false
t.column "custom_field_id", :integer, :default => 0, :null => false t.column "custom_field_id", :integer, :default => 0, :null => false
t.column "value", :text t.column "value", :text
end
create_table "documents", :force => true do |t|
t.column "project_id", :integer, :default => 0, :null => false
t.column "category_id", :integer, :default => 0, :null => false
t.column "title", :string, :limit => 60, :default => "", :null => false
t.column "description", :text
t.column "created_on", :timestamp
end end
add_index "documents", ["project_id"], :name => "documents_project_id" create_table "documents", :force => true do |t|
t.column "project_id", :integer, :default => 0, :null => false
create_table "enumerations", :force => true do |t| t.column "category_id", :integer, :default => 0, :null => false
t.column "opt", :string, :limit => 4, :default => "", :null => false t.column "title", :string, :limit => 60, :default => "", :null => false
t.column "name", :string, :limit => 30, :default => "", :null => false
end
create_table "issue_categories", :force => true do |t|
t.column "project_id", :integer, :default => 0, :null => false
t.column "name", :string, :limit => 30, :default => "", :null => false
end
add_index "issue_categories", ["project_id"], :name => "issue_categories_project_id"
create_table "issue_histories", :force => true do |t|
t.column "issue_id", :integer, :default => 0, :null => false
t.column "status_id", :integer, :default => 0, :null => false
t.column "author_id", :integer, :default => 0, :null => false
t.column "notes", :text
t.column "created_on", :timestamp
end
add_index "issue_histories", ["issue_id"], :name => "issue_histories_issue_id"
create_table "issue_statuses", :force => true do |t|
t.column "name", :string, :limit => 30, :default => "", :null => false
t.column "is_closed", :boolean, :default => false, :null => false
t.column "is_default", :boolean, :default => false, :null => false
t.column "html_color", :string, :limit => 6, :default => "FFFFFF", :null => false
end
create_table "issues", :force => true do |t|
t.column "tracker_id", :integer, :default => 0, :null => false
t.column "project_id", :integer, :default => 0, :null => false
t.column "subject", :string, :default => "", :null => false
t.column "description", :text t.column "description", :text
t.column "due_date", :date t.column "created_on", :timestamp
t.column "category_id", :integer
t.column "status_id", :integer, :default => 0, :null => false
t.column "assigned_to_id", :integer
t.column "priority_id", :integer, :default => 0, :null => false
t.column "fixed_version_id", :integer
t.column "author_id", :integer, :default => 0, :null => false
t.column "lock_version", :integer, :default => 0, :null => false
t.column "created_on", :timestamp
t.column "updated_on", :timestamp
end
add_index "issues", ["project_id"], :name => "issues_project_id"
create_table "members", :force => true do |t|
t.column "user_id", :integer, :default => 0, :null => false
t.column "project_id", :integer, :default => 0, :null => false
t.column "role_id", :integer, :default => 0, :null => false
t.column "created_on", :timestamp
end
create_table "news", :force => true do |t|
t.column "project_id", :integer
t.column "title", :string, :limit => 60, :default => "", :null => false
t.column "summary", :string, :limit => 255, :default => ""
t.column "description", :text
t.column "author_id", :integer, :default => 0, :null => false
t.column "created_on", :timestamp
end end
add_index "news", ["project_id"], :name => "news_project_id" add_index "documents", ["project_id"], :name => "documents_project_id"
create_table "permissions", :force => true do |t| create_table "enumerations", :force => true do |t|
t.column "controller", :string, :limit => 30, :default => "", :null => false t.column "opt", :string, :limit => 4, :default => "", :null => false
t.column "action", :string, :limit => 30, :default => "", :null => false t.column "name", :string, :limit => 30, :default => "", :null => false
t.column "description", :string, :limit => 60, :default => "", :null => false end
t.column "is_public", :boolean, :default => false, :null => false
t.column "sort", :integer, :default => 0, :null => false create_table "issue_categories", :force => true do |t|
t.column "mail_option", :boolean, :default => false, :null => false t.column "project_id", :integer, :default => 0, :null => false
t.column "mail_enabled", :boolean, :default => false, :null => false t.column "name", :string, :limit => 30, :default => "", :null => false
end end
create_table "permissions_roles", :id => false, :force => true do |t| add_index "issue_categories", ["project_id"], :name => "issue_categories_project_id"
t.column "permission_id", :integer, :default => 0, :null => false
t.column "role_id", :integer, :default => 0, :null => false create_table "issue_histories", :force => true do |t|
end t.column "issue_id", :integer, :default => 0, :null => false
t.column "status_id", :integer, :default => 0, :null => false
add_index "permissions_roles", ["role_id"], :name => "permissions_roles_role_id" t.column "author_id", :integer, :default => 0, :null => false
t.column "notes", :text
create_table "projects", :force => true do |t| t.column "created_on", :timestamp
t.column "name", :string, :limit => 30, :default => "", :null => false end
t.column "description", :string, :default => "", :null => false
t.column "homepage", :string, :limit => 60, :default => "" add_index "issue_histories", ["issue_id"], :name => "issue_histories_issue_id"
create_table "issue_statuses", :force => true do |t|
t.column "name", :string, :limit => 30, :default => "", :null => false
t.column "is_closed", :boolean, :default => false, :null => false
t.column "is_default", :boolean, :default => false, :null => false
t.column "html_color", :string, :limit => 6, :default => "FFFFFF", :null => false
end
create_table "issues", :force => true do |t|
t.column "tracker_id", :integer, :default => 0, :null => false
t.column "project_id", :integer, :default => 0, :null => false
t.column "subject", :string, :default => "", :null => false
t.column "description", :text
t.column "due_date", :date
t.column "category_id", :integer
t.column "status_id", :integer, :default => 0, :null => false
t.column "assigned_to_id", :integer
t.column "priority_id", :integer, :default => 0, :null => false
t.column "fixed_version_id", :integer
t.column "author_id", :integer, :default => 0, :null => false
t.column "lock_version", :integer, :default => 0, :null => false
t.column "created_on", :timestamp
t.column "updated_on", :timestamp
end
add_index "issues", ["project_id"], :name => "issues_project_id"
create_table "members", :force => true do |t|
t.column "user_id", :integer, :default => 0, :null => false
t.column "project_id", :integer, :default => 0, :null => false
t.column "role_id", :integer, :default => 0, :null => false
t.column "created_on", :timestamp
end
create_table "news", :force => true do |t|
t.column "project_id", :integer
t.column "title", :string, :limit => 60, :default => "", :null => false
t.column "summary", :string, :limit => 255, :default => ""
t.column "description", :text
t.column "author_id", :integer, :default => 0, :null => false
t.column "created_on", :timestamp
end
add_index "news", ["project_id"], :name => "news_project_id"
create_table "permissions", :force => true do |t|
t.column "controller", :string, :limit => 30, :default => "", :null => false
t.column "action", :string, :limit => 30, :default => "", :null => false
t.column "description", :string, :limit => 60, :default => "", :null => false
t.column "is_public", :boolean, :default => false, :null => false
t.column "sort", :integer, :default => 0, :null => false
t.column "mail_option", :boolean, :default => false, :null => false
t.column "mail_enabled", :boolean, :default => false, :null => false
end
create_table "permissions_roles", :id => false, :force => true do |t|
t.column "permission_id", :integer, :default => 0, :null => false
t.column "role_id", :integer, :default => 0, :null => false
end
add_index "permissions_roles", ["role_id"], :name => "permissions_roles_role_id"
create_table "projects", :force => true do |t|
t.column "name", :string, :limit => 30, :default => "", :null => false
t.column "description", :string, :default => "", :null => false
t.column "homepage", :string, :limit => 60, :default => ""
t.column "is_public", :boolean, :default => true, :null => false t.column "is_public", :boolean, :default => true, :null => false
t.column "parent_id", :integer t.column "parent_id", :integer
t.column "projects_count", :integer, :default => 0 t.column "projects_count", :integer, :default => 0
t.column "created_on", :timestamp t.column "created_on", :timestamp
t.column "updated_on", :timestamp t.column "updated_on", :timestamp
end end
create_table "roles", :force => true do |t| create_table "roles", :force => true do |t|
t.column "name", :string, :limit => 30, :default => "", :null => false t.column "name", :string, :limit => 30, :default => "", :null => false
end end
create_table "tokens", :force => true do |t| create_table "tokens", :force => true do |t|
t.column "user_id", :integer, :default => 0, :null => false t.column "user_id", :integer, :default => 0, :null => false
@ -193,98 +193,98 @@ class Setup < ActiveRecord::Migration
t.column "value", :string, :limit => 40, :default => "", :null => false t.column "value", :string, :limit => 40, :default => "", :null => false
t.column "created_on", :datetime, :null => false t.column "created_on", :datetime, :null => false
end end
create_table "trackers", :force => true do |t| create_table "trackers", :force => true do |t|
t.column "name", :string, :limit => 30, :default => "", :null => false t.column "name", :string, :limit => 30, :default => "", :null => false
t.column "is_in_chlog", :boolean, :default => false, :null => false t.column "is_in_chlog", :boolean, :default => false, :null => false
end end
create_table "users", :force => true do |t| create_table "users", :force => true do |t|
t.column "login", :string, :limit => 30, :default => "", :null => false t.column "login", :string, :limit => 30, :default => "", :null => false
t.column "hashed_password", :string, :limit => 40, :default => "", :null => false t.column "hashed_password", :string, :limit => 40, :default => "", :null => false
t.column "firstname", :string, :limit => 30, :default => "", :null => false t.column "firstname", :string, :limit => 30, :default => "", :null => false
t.column "lastname", :string, :limit => 30, :default => "", :null => false t.column "lastname", :string, :limit => 30, :default => "", :null => false
t.column "mail", :string, :limit => 60, :default => "", :null => false t.column "mail", :string, :limit => 60, :default => "", :null => false
t.column "mail_notification", :boolean, :default => true, :null => false t.column "mail_notification", :boolean, :default => true, :null => false
t.column "admin", :boolean, :default => false, :null => false t.column "admin", :boolean, :default => false, :null => false
t.column "status", :integer, :default => 1, :null => false t.column "status", :integer, :default => 1, :null => false
t.column "last_login_on", :datetime t.column "last_login_on", :datetime
t.column "language", :string, :limit => 2, :default => "" t.column "language", :string, :limit => 2, :default => ""
t.column "auth_source_id", :integer t.column "auth_source_id", :integer
t.column "created_on", :timestamp t.column "created_on", :timestamp
t.column "updated_on", :timestamp t.column "updated_on", :timestamp
end end
create_table "versions", :force => true do |t| create_table "versions", :force => true do |t|
t.column "project_id", :integer, :default => 0, :null => false t.column "project_id", :integer, :default => 0, :null => false
t.column "name", :string, :limit => 30, :default => "", :null => false t.column "name", :string, :limit => 30, :default => "", :null => false
t.column "description", :string, :default => "" t.column "description", :string, :default => ""
t.column "effective_date", :date t.column "effective_date", :date
t.column "created_on", :timestamp t.column "created_on", :timestamp
t.column "updated_on", :timestamp t.column "updated_on", :timestamp
end end
add_index "versions", ["project_id"], :name => "versions_project_id" add_index "versions", ["project_id"], :name => "versions_project_id"
create_table "workflows", :force => true do |t| create_table "workflows", :force => true do |t|
t.column "tracker_id", :integer, :default => 0, :null => false t.column "tracker_id", :integer, :default => 0, :null => false
t.column "old_status_id", :integer, :default => 0, :null => false t.column "old_status_id", :integer, :default => 0, :null => false
t.column "new_status_id", :integer, :default => 0, :null => false t.column "new_status_id", :integer, :default => 0, :null => false
t.column "role_id", :integer, :default => 0, :null => false t.column "role_id", :integer, :default => 0, :null => false
end end
# project # project
Permission.create :controller => "projects", :action => "show", :description => "label_overview", :sort => 100, :is_public => true Permission.create :controller => "projects", :action => "show", :description => "label_overview", :sort => 100, :is_public => true
Permission.create :controller => "projects", :action => "changelog", :description => "label_change_log", :sort => 105, :is_public => true Permission.create :controller => "projects", :action => "changelog", :description => "label_change_log", :sort => 105, :is_public => true
Permission.create :controller => "reports", :action => "issue_report", :description => "label_report_plural", :sort => 110, :is_public => true Permission.create :controller => "reports", :action => "issue_report", :description => "label_report_plural", :sort => 110, :is_public => true
Permission.create :controller => "projects", :action => "settings", :description => "label_settings", :sort => 150 Permission.create :controller => "projects", :action => "settings", :description => "label_settings", :sort => 150
Permission.create :controller => "projects", :action => "edit", :description => "button_edit", :sort => 151 Permission.create :controller => "projects", :action => "edit", :description => "button_edit", :sort => 151
# members # members
Permission.create :controller => "projects", :action => "list_members", :description => "button_list", :sort => 200, :is_public => true Permission.create :controller => "projects", :action => "list_members", :description => "button_list", :sort => 200, :is_public => true
Permission.create :controller => "projects", :action => "add_member", :description => "button_add", :sort => 220 Permission.create :controller => "projects", :action => "add_member", :description => "button_add", :sort => 220
Permission.create :controller => "members", :action => "edit", :description => "button_edit", :sort => 221 Permission.create :controller => "members", :action => "edit", :description => "button_edit", :sort => 221
Permission.create :controller => "members", :action => "destroy", :description => "button_delete", :sort => 222 Permission.create :controller => "members", :action => "destroy", :description => "button_delete", :sort => 222
# versions # versions
Permission.create :controller => "projects", :action => "add_version", :description => "button_add", :sort => 320 Permission.create :controller => "projects", :action => "add_version", :description => "button_add", :sort => 320
Permission.create :controller => "versions", :action => "edit", :description => "button_edit", :sort => 321 Permission.create :controller => "versions", :action => "edit", :description => "button_edit", :sort => 321
Permission.create :controller => "versions", :action => "destroy", :description => "button_delete", :sort => 322 Permission.create :controller => "versions", :action => "destroy", :description => "button_delete", :sort => 322
# issue categories # issue categories
Permission.create :controller => "projects", :action => "add_issue_category", :description => "button_add", :sort => 420 Permission.create :controller => "projects", :action => "add_issue_category", :description => "button_add", :sort => 420
Permission.create :controller => "issue_categories", :action => "edit", :description => "button_edit", :sort => 421 Permission.create :controller => "issue_categories", :action => "edit", :description => "button_edit", :sort => 421
Permission.create :controller => "issue_categories", :action => "destroy", :description => "button_delete", :sort => 422 Permission.create :controller => "issue_categories", :action => "destroy", :description => "button_delete", :sort => 422
# issues # issues
Permission.create :controller => "projects", :action => "list_issues", :description => "button_list", :sort => 1000, :is_public => true Permission.create :controller => "projects", :action => "list_issues", :description => "button_list", :sort => 1000, :is_public => true
Permission.create :controller => "projects", :action => "export_issues_csv", :description => "label_export_csv", :sort => 1001, :is_public => true Permission.create :controller => "projects", :action => "export_issues_csv", :description => "label_export_csv", :sort => 1001, :is_public => true
Permission.create :controller => "issues", :action => "show", :description => "button_view", :sort => 1005, :is_public => true Permission.create :controller => "issues", :action => "show", :description => "button_view", :sort => 1005, :is_public => true
Permission.create :controller => "issues", :action => "download", :description => "button_download", :sort => 1010, :is_public => true Permission.create :controller => "issues", :action => "download", :description => "button_download", :sort => 1010, :is_public => true
Permission.create :controller => "projects", :action => "add_issue", :description => "button_add", :sort => 1050, :mail_option => 1, :mail_enabled => 1 Permission.create :controller => "projects", :action => "add_issue", :description => "button_add", :sort => 1050, :mail_option => 1, :mail_enabled => 1
Permission.create :controller => "issues", :action => "edit", :description => "button_edit", :sort => 1055 Permission.create :controller => "issues", :action => "edit", :description => "button_edit", :sort => 1055
Permission.create :controller => "issues", :action => "change_status", :description => "label_change_status", :sort => 1060, :mail_option => 1, :mail_enabled => 1 Permission.create :controller => "issues", :action => "change_status", :description => "label_change_status", :sort => 1060, :mail_option => 1, :mail_enabled => 1
Permission.create :controller => "issues", :action => "destroy", :description => "button_delete", :sort => 1065 Permission.create :controller => "issues", :action => "destroy", :description => "button_delete", :sort => 1065
Permission.create :controller => "issues", :action => "add_attachment", :description => "label_attachment_new", :sort => 1070 Permission.create :controller => "issues", :action => "add_attachment", :description => "label_attachment_new", :sort => 1070
Permission.create :controller => "issues", :action => "destroy_attachment", :description => "label_attachment_delete", :sort => 1075 Permission.create :controller => "issues", :action => "destroy_attachment", :description => "label_attachment_delete", :sort => 1075
# news # news
Permission.create :controller => "projects", :action => "list_news", :description => "button_list", :sort => 1100, :is_public => true Permission.create :controller => "projects", :action => "list_news", :description => "button_list", :sort => 1100, :is_public => true
Permission.create :controller => "news", :action => "show", :description => "button_view", :sort => 1101, :is_public => true Permission.create :controller => "news", :action => "show", :description => "button_view", :sort => 1101, :is_public => true
Permission.create :controller => "projects", :action => "add_news", :description => "button_add", :sort => 1120 Permission.create :controller => "projects", :action => "add_news", :description => "button_add", :sort => 1120
Permission.create :controller => "news", :action => "edit", :description => "button_edit", :sort => 1121 Permission.create :controller => "news", :action => "edit", :description => "button_edit", :sort => 1121
Permission.create :controller => "news", :action => "destroy", :description => "button_delete", :sort => 1122 Permission.create :controller => "news", :action => "destroy", :description => "button_delete", :sort => 1122
# documents # documents
Permission.create :controller => "projects", :action => "list_documents", :description => "button_list", :sort => 1200, :is_public => true Permission.create :controller => "projects", :action => "list_documents", :description => "button_list", :sort => 1200, :is_public => true
Permission.create :controller => "documents", :action => "show", :description => "button_view", :sort => 1201, :is_public => true Permission.create :controller => "documents", :action => "show", :description => "button_view", :sort => 1201, :is_public => true
Permission.create :controller => "documents", :action => "download", :description => "button_download", :sort => 1202, :is_public => true Permission.create :controller => "documents", :action => "download", :description => "button_download", :sort => 1202, :is_public => true
Permission.create :controller => "projects", :action => "add_document", :description => "button_add", :sort => 1220 Permission.create :controller => "projects", :action => "add_document", :description => "button_add", :sort => 1220
Permission.create :controller => "documents", :action => "edit", :description => "button_edit", :sort => 1221 Permission.create :controller => "documents", :action => "edit", :description => "button_edit", :sort => 1221
Permission.create :controller => "documents", :action => "destroy", :description => "button_delete", :sort => 1222 Permission.create :controller => "documents", :action => "destroy", :description => "button_delete", :sort => 1222
Permission.create :controller => "documents", :action => "add_attachment", :description => "label_attachment_new", :sort => 1223 Permission.create :controller => "documents", :action => "add_attachment", :description => "label_attachment_new", :sort => 1223
Permission.create :controller => "documents", :action => "destroy_attachment", :description => "label_attachment_delete", :sort => 1224 Permission.create :controller => "documents", :action => "destroy_attachment", :description => "label_attachment_delete", :sort => 1224
# files # files
Permission.create :controller => "projects", :action => "list_files", :description => "button_list", :sort => 1300, :is_public => true Permission.create :controller => "projects", :action => "list_files", :description => "button_list", :sort => 1300, :is_public => true
Permission.create :controller => "versions", :action => "download", :description => "button_download", :sort => 1301, :is_public => true Permission.create :controller => "versions", :action => "download", :description => "button_download", :sort => 1301, :is_public => true
Permission.create :controller => "projects", :action => "add_file", :description => "button_add", :sort => 1320 Permission.create :controller => "projects", :action => "add_file", :description => "button_add", :sort => 1320
Permission.create :controller => "versions", :action => "destroy_file", :description => "button_delete", :sort => 1322 Permission.create :controller => "versions", :action => "destroy_file", :description => "button_delete", :sort => 1322
# create default administrator account # create default administrator account
user = User.create :login => "admin", user = User.create :login => "admin",
:hashed_password => "d033e22ae348aeb5660fc2140aec35850c4da997", :hashed_password => "d033e22ae348aeb5660fc2140aec35850c4da997",
:admin => true, :admin => true,
@ -296,29 +296,29 @@ class Setup < ActiveRecord::Migration
:status => 1 :status => 1
end end
def self.down def self.down
drop_table :attachments drop_table :attachments
drop_table :auth_sources drop_table :auth_sources
drop_table :custom_fields drop_table :custom_fields
drop_table :custom_fields_projects drop_table :custom_fields_projects
drop_table :custom_fields_trackers drop_table :custom_fields_trackers
drop_table :custom_values drop_table :custom_values
drop_table :documents drop_table :documents
drop_table :enumerations drop_table :enumerations
drop_table :issue_categories drop_table :issue_categories
drop_table :issue_histories drop_table :issue_histories
drop_table :issue_statuses drop_table :issue_statuses
drop_table :issues drop_table :issues
drop_table :members drop_table :members
drop_table :news drop_table :news
drop_table :permissions drop_table :permissions
drop_table :permissions_roles drop_table :permissions_roles
drop_table :projects drop_table :projects
drop_table :roles drop_table :roles
drop_table :trackers drop_table :trackers
drop_table :tokens drop_table :tokens
drop_table :users drop_table :users
drop_table :versions drop_table :versions
drop_table :workflows drop_table :workflows
end end
end end

View File

@ -1,148 +1,148 @@
require 'rexml/document' require 'rexml/document'
require 'SVG/Graph/Graph' require 'SVG/Graph/Graph'
require 'SVG/Graph/BarBase' require 'SVG/Graph/BarBase'
module SVG module SVG
module Graph module Graph
# === Create presentation quality SVG bar graphs easily # === Create presentation quality SVG bar graphs easily
# #
# = Synopsis # = Synopsis
# #
# require 'SVG/Graph/Bar' # require 'SVG/Graph/Bar'
# #
# fields = %w(Jan Feb Mar); # fields = %w(Jan Feb Mar);
# data_sales_02 = [12, 45, 21] # data_sales_02 = [12, 45, 21]
# #
# graph = SVG::Graph::Bar.new( # graph = SVG::Graph::Bar.new(
# :height => 500, # :height => 500,
# :width => 300, # :width => 300,
# :fields => fields # :fields => fields
# ) # )
# #
# graph.add_data( # graph.add_data(
# :data => data_sales_02, # :data => data_sales_02,
# :title => 'Sales 2002' # :title => 'Sales 2002'
# ) # )
# #
# print "Content-type: image/svg+xml\r\n\r\n" # print "Content-type: image/svg+xml\r\n\r\n"
# print graph.burn # print graph.burn
# #
# = Description # = Description
# #
# This object aims to allow you to easily create high quality # This object aims to allow you to easily create high quality
# SVG[http://www.w3c.org/tr/svg bar graphs. You can either use the default # SVG[http://www.w3c.org/tr/svg bar graphs. You can either use the default
# style sheet or supply your own. Either way there are many options which # style sheet or supply your own. Either way there are many options which
# can be configured to give you control over how the graph is generated - # can be configured to give you control over how the graph is generated -
# with or without a key, data elements at each point, title, subtitle etc. # with or without a key, data elements at each point, title, subtitle etc.
# #
# = Notes # = Notes
# #
# The default stylesheet handles upto 12 data sets, if you # The default stylesheet handles upto 12 data sets, if you
# use more you must create your own stylesheet and add the # use more you must create your own stylesheet and add the
# additional settings for the extra data sets. You will know # additional settings for the extra data sets. You will know
# if you go over 12 data sets as they will have no style and # if you go over 12 data sets as they will have no style and
# be in black. # be in black.
# #
# = Examples # = Examples
# #
# * http://germane-software.com/repositories/public/SVG/test/test.rb # * http://germane-software.com/repositories/public/SVG/test/test.rb
# #
# = See also # = See also
# #
# * SVG::Graph::Graph # * SVG::Graph::Graph
# * SVG::Graph::BarHorizontal # * SVG::Graph::BarHorizontal
# * SVG::Graph::Line # * SVG::Graph::Line
# * SVG::Graph::Pie # * SVG::Graph::Pie
# * SVG::Graph::Plot # * SVG::Graph::Plot
# * SVG::Graph::TimeSeries # * SVG::Graph::TimeSeries
class Bar < BarBase class Bar < BarBase
include REXML include REXML
# See Graph::initialize and BarBase::set_defaults # See Graph::initialize and BarBase::set_defaults
def set_defaults def set_defaults
super super
self.top_align = self.top_font = 1 self.top_align = self.top_font = 1
end end
protected protected
def get_x_labels def get_x_labels
@config[:fields] @config[:fields]
end end
def get_y_labels def get_y_labels
maxvalue = max_value maxvalue = max_value
minvalue = min_value minvalue = min_value
range = maxvalue - minvalue range = maxvalue - minvalue
top_pad = range == 0 ? 10 : range / 20.0 top_pad = range == 0 ? 10 : range / 20.0
scale_range = (maxvalue + top_pad) - minvalue scale_range = (maxvalue + top_pad) - minvalue
scale_division = scale_divisions || (scale_range / 10.0) scale_division = scale_divisions || (scale_range / 10.0)
if scale_integers if scale_integers
scale_division = scale_division < 1 ? 1 : scale_division.round scale_division = scale_division < 1 ? 1 : scale_division.round
end end
rv = [] rv = []
maxvalue = maxvalue%scale_division == 0 ? maxvalue = maxvalue%scale_division == 0 ?
maxvalue : maxvalue + scale_division maxvalue : maxvalue + scale_division
minvalue.step( maxvalue, scale_division ) {|v| rv << v} minvalue.step( maxvalue, scale_division ) {|v| rv << v}
return rv return rv
end end
def x_label_offset( width ) def x_label_offset( width )
width / 2.0 width / 2.0
end end
def draw_data def draw_data
minvalue = min_value minvalue = min_value
fieldwidth = field_width fieldwidth = field_width
unit_size = (@graph_height.to_f - font_size*2*top_font) / unit_size = (@graph_height.to_f - font_size*2*top_font) /
(get_y_labels.max - get_y_labels.min) (get_y_labels.max - get_y_labels.min)
bargap = bar_gap ? (fieldwidth < 10 ? fieldwidth / 2 : 10) : 0 bargap = bar_gap ? (fieldwidth < 10 ? fieldwidth / 2 : 10) : 0
bar_width = fieldwidth - bargap bar_width = fieldwidth - bargap
bar_width /= @data.length if stack == :side bar_width /= @data.length if stack == :side
x_mod = (@graph_width-bargap)/2 - (stack==:side ? bar_width/2 : 0) x_mod = (@graph_width-bargap)/2 - (stack==:side ? bar_width/2 : 0)
bottom = @graph_height bottom = @graph_height
field_count = 0 field_count = 0
@config[:fields].each_index { |i| @config[:fields].each_index { |i|
dataset_count = 0 dataset_count = 0
for dataset in @data for dataset in @data
# cases (assume 0 = +ve): # cases (assume 0 = +ve):
# value min length # value min length
# +ve +ve value - min # +ve +ve value - min
# +ve -ve value - 0 # +ve -ve value - 0
# -ve -ve value.abs - 0 # -ve -ve value.abs - 0
value = dataset[:data][i] value = dataset[:data][i]
left = (fieldwidth * field_count) left = (fieldwidth * field_count)
length = (value.abs - (minvalue > 0 ? minvalue : 0)) * unit_size length = (value.abs - (minvalue > 0 ? minvalue : 0)) * unit_size
# top is 0 if value is negative # top is 0 if value is negative
top = bottom - (((value < 0 ? 0 : value) - minvalue) * unit_size) top = bottom - (((value < 0 ? 0 : value) - minvalue) * unit_size)
left += bar_width * dataset_count if stack == :side left += bar_width * dataset_count if stack == :side
@graph.add_element( "rect", { @graph.add_element( "rect", {
"x" => left.to_s, "x" => left.to_s,
"y" => top.to_s, "y" => top.to_s,
"width" => bar_width.to_s, "width" => bar_width.to_s,
"height" => length.to_s, "height" => length.to_s,
"class" => "fill#{dataset_count+1}" "class" => "fill#{dataset_count+1}"
}) })
make_datapoint_text(left + bar_width/2.0, top - 6, value.to_s) make_datapoint_text(left + bar_width/2.0, top - 6, value.to_s)
dataset_count += 1 dataset_count += 1
end end
field_count += 1 field_count += 1
} }
end end
end end
end end
end end

View File

@ -1,139 +1,139 @@
require 'rexml/document' require 'rexml/document'
require 'SVG/Graph/Graph' require 'SVG/Graph/Graph'
module SVG module SVG
module Graph module Graph
# = Synopsis # = Synopsis
# #
# A superclass for bar-style graphs. Do not attempt to instantiate # A superclass for bar-style graphs. Do not attempt to instantiate
# directly; use one of the subclasses instead. # directly; use one of the subclasses instead.
# #
# = Author # = Author
# #
# Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom> # Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>
# #
# Copyright 2004 Sean E. Russell # Copyright 2004 Sean E. Russell
# This software is available under the Ruby license[LICENSE.txt] # This software is available under the Ruby license[LICENSE.txt]
# #
class BarBase < SVG::Graph::Graph class BarBase < SVG::Graph::Graph
# Ensures that :fields are provided in the configuration. # Ensures that :fields are provided in the configuration.
def initialize config def initialize config
raise "fields was not supplied or is empty" unless config[:fields] && raise "fields was not supplied or is empty" unless config[:fields] &&
config[:fields].kind_of?(Array) && config[:fields].kind_of?(Array) &&
config[:fields].length > 0 config[:fields].length > 0
super super
end end
# In addition to the defaults set in Graph::initialize, sets # In addition to the defaults set in Graph::initialize, sets
# [bar_gap] true # [bar_gap] true
# [stack] :overlap # [stack] :overlap
def set_defaults def set_defaults
init_with( :bar_gap => true, :stack => :overlap ) init_with( :bar_gap => true, :stack => :overlap )
end end
# Whether to have a gap between the bars or not, default # Whether to have a gap between the bars or not, default
# is true, set to false if you don't want gaps. # is true, set to false if you don't want gaps.
attr_accessor :bar_gap attr_accessor :bar_gap
# How to stack data sets. :overlap overlaps bars with # How to stack data sets. :overlap overlaps bars with
# transparent colors, :top stacks bars on top of one another, # transparent colors, :top stacks bars on top of one another,
# :side stacks the bars side-by-side. Defaults to :overlap. # :side stacks the bars side-by-side. Defaults to :overlap.
attr_accessor :stack attr_accessor :stack
protected protected
def max_value def max_value
@data.collect{|x| x[:data].max}.max @data.collect{|x| x[:data].max}.max
end end
def min_value def min_value
min = 0 min = 0
if min_scale_value.nil? if min_scale_value.nil?
min = @data.collect{|x| x[:data].min}.min min = @data.collect{|x| x[:data].min}.min
min = min > 0 ? 0 : min min = min > 0 ? 0 : min
else else
min = min_scale_value min = min_scale_value
end end
return min return min
end end
def get_css def get_css
return <<EOL return <<EOL
/* default fill styles for multiple datasets (probably only use a single dataset on this graph though) */ /* default fill styles for multiple datasets (probably only use a single dataset on this graph though) */
.key1,.fill1{ .key1,.fill1{
fill: #ff0000; fill: #ff0000;
fill-opacity: 0.5; fill-opacity: 0.5;
stroke: none; stroke: none;
stroke-width: 0.5px; stroke-width: 0.5px;
} }
.key2,.fill2{ .key2,.fill2{
fill: #0000ff; fill: #0000ff;
fill-opacity: 0.5; fill-opacity: 0.5;
stroke: none; stroke: none;
stroke-width: 1px; stroke-width: 1px;
} }
.key3,.fill3{ .key3,.fill3{
fill: #00ff00; fill: #00ff00;
fill-opacity: 0.5; fill-opacity: 0.5;
stroke: none; stroke: none;
stroke-width: 1px; stroke-width: 1px;
} }
.key4,.fill4{ .key4,.fill4{
fill: #ffcc00; fill: #ffcc00;
fill-opacity: 0.5; fill-opacity: 0.5;
stroke: none; stroke: none;
stroke-width: 1px; stroke-width: 1px;
} }
.key5,.fill5{ .key5,.fill5{
fill: #00ccff; fill: #00ccff;
fill-opacity: 0.5; fill-opacity: 0.5;
stroke: none; stroke: none;
stroke-width: 1px; stroke-width: 1px;
} }
.key6,.fill6{ .key6,.fill6{
fill: #ff00ff; fill: #ff00ff;
fill-opacity: 0.5; fill-opacity: 0.5;
stroke: none; stroke: none;
stroke-width: 1px; stroke-width: 1px;
} }
.key7,.fill7{ .key7,.fill7{
fill: #00ffff; fill: #00ffff;
fill-opacity: 0.5; fill-opacity: 0.5;
stroke: none; stroke: none;
stroke-width: 1px; stroke-width: 1px;
} }
.key8,.fill8{ .key8,.fill8{
fill: #ffff00; fill: #ffff00;
fill-opacity: 0.5; fill-opacity: 0.5;
stroke: none; stroke: none;
stroke-width: 1px; stroke-width: 1px;
} }
.key9,.fill9{ .key9,.fill9{
fill: #cc6666; fill: #cc6666;
fill-opacity: 0.5; fill-opacity: 0.5;
stroke: none; stroke: none;
stroke-width: 1px; stroke-width: 1px;
} }
.key10,.fill10{ .key10,.fill10{
fill: #663399; fill: #663399;
fill-opacity: 0.5; fill-opacity: 0.5;
stroke: none; stroke: none;
stroke-width: 1px; stroke-width: 1px;
} }
.key11,.fill11{ .key11,.fill11{
fill: #339900; fill: #339900;
fill-opacity: 0.5; fill-opacity: 0.5;
stroke: none; stroke: none;
stroke-width: 1px; stroke-width: 1px;
} }
.key12,.fill12{ .key12,.fill12{
fill: #9966FF; fill: #9966FF;
fill-opacity: 0.5; fill-opacity: 0.5;
stroke: none; stroke: none;
stroke-width: 1px; stroke-width: 1px;
} }
EOL EOL
end end
end end
end end
end end

View File

@ -1,149 +1,149 @@
require 'rexml/document' require 'rexml/document'
require 'SVG/Graph/BarBase' require 'SVG/Graph/BarBase'
module SVG module SVG
module Graph module Graph
# === Create presentation quality SVG horitonzal bar graphs easily # === Create presentation quality SVG horitonzal bar graphs easily
# #
# = Synopsis # = Synopsis
# #
# require 'SVG/Graph/BarHorizontal' # require 'SVG/Graph/BarHorizontal'
# #
# fields = %w(Jan Feb Mar) # fields = %w(Jan Feb Mar)
# data_sales_02 = [12, 45, 21] # data_sales_02 = [12, 45, 21]
# #
# graph = SVG::Graph::BarHorizontal.new({ # graph = SVG::Graph::BarHorizontal.new({
# :height => 500, # :height => 500,
# :width => 300, # :width => 300,
# :fields => fields, # :fields => fields,
# }) # })
# #
# graph.add_data({ # graph.add_data({
# :data => data_sales_02, # :data => data_sales_02,
# :title => 'Sales 2002', # :title => 'Sales 2002',
# }) # })
# #
# print "Content-type: image/svg+xml\r\n\r\n" # print "Content-type: image/svg+xml\r\n\r\n"
# print graph.burn # print graph.burn
# #
# = Description # = Description
# #
# This object aims to allow you to easily create high quality # This object aims to allow you to easily create high quality
# SVG horitonzal bar graphs. You can either use the default style sheet # SVG horitonzal bar graphs. You can either use the default style sheet
# or supply your own. Either way there are many options which can # or supply your own. Either way there are many options which can
# be configured to give you control over how the graph is # be configured to give you control over how the graph is
# generated - with or without a key, data elements at each point, # generated - with or without a key, data elements at each point,
# title, subtitle etc. # title, subtitle etc.
# #
# = Examples # = Examples
# #
# * http://germane-software.com/repositories/public/SVG/test/test.rb # * http://germane-software.com/repositories/public/SVG/test/test.rb
# #
# = See also # = See also
# #
# * SVG::Graph::Graph # * SVG::Graph::Graph
# * SVG::Graph::Bar # * SVG::Graph::Bar
# * SVG::Graph::Line # * SVG::Graph::Line
# * SVG::Graph::Pie # * SVG::Graph::Pie
# * SVG::Graph::Plot # * SVG::Graph::Plot
# * SVG::Graph::TimeSeries # * SVG::Graph::TimeSeries
# #
# == Author # == Author
# #
# Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom> # Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>
# #
# Copyright 2004 Sean E. Russell # Copyright 2004 Sean E. Russell
# This software is available under the Ruby license[LICENSE.txt] # This software is available under the Ruby license[LICENSE.txt]
# #
class BarHorizontal < BarBase class BarHorizontal < BarBase
# In addition to the defaults set in BarBase::set_defaults, sets # In addition to the defaults set in BarBase::set_defaults, sets
# [rotate_y_labels] true # [rotate_y_labels] true
# [show_x_guidelines] true # [show_x_guidelines] true
# [show_y_guidelines] false # [show_y_guidelines] false
def set_defaults def set_defaults
super super
init_with( init_with(
:rotate_y_labels => true, :rotate_y_labels => true,
:show_x_guidelines => true, :show_x_guidelines => true,
:show_y_guidelines => false :show_y_guidelines => false
) )
self.right_align = self.right_font = 1 self.right_align = self.right_font = 1
end end
protected protected
def get_x_labels def get_x_labels
maxvalue = max_value maxvalue = max_value
minvalue = min_value minvalue = min_value
range = maxvalue - minvalue range = maxvalue - minvalue
top_pad = range == 0 ? 10 : range / 20.0 top_pad = range == 0 ? 10 : range / 20.0
scale_range = (maxvalue + top_pad) - minvalue scale_range = (maxvalue + top_pad) - minvalue
scale_division = scale_divisions || (scale_range / 10.0) scale_division = scale_divisions || (scale_range / 10.0)
if scale_integers if scale_integers
scale_division = scale_division < 1 ? 1 : scale_division.round scale_division = scale_division < 1 ? 1 : scale_division.round
end end
rv = [] rv = []
maxvalue = maxvalue%scale_division == 0 ? maxvalue = maxvalue%scale_division == 0 ?
maxvalue : maxvalue + scale_division maxvalue : maxvalue + scale_division
minvalue.step( maxvalue, scale_division ) {|v| rv << v} minvalue.step( maxvalue, scale_division ) {|v| rv << v}
return rv return rv
end end
def get_y_labels def get_y_labels
@config[:fields] @config[:fields]
end end
def y_label_offset( height ) def y_label_offset( height )
height / -2.0 height / -2.0
end end
def draw_data def draw_data
minvalue = min_value minvalue = min_value
fieldheight = field_height fieldheight = field_height
unit_size = (@graph_width.to_f - font_size*2*right_font ) / unit_size = (@graph_width.to_f - font_size*2*right_font ) /
(get_x_labels.max - get_x_labels.min ) (get_x_labels.max - get_x_labels.min )
bargap = bar_gap ? (fieldheight < 10 ? fieldheight / 2 : 10) : 0 bargap = bar_gap ? (fieldheight < 10 ? fieldheight / 2 : 10) : 0
bar_height = fieldheight - bargap bar_height = fieldheight - bargap
bar_height /= @data.length if stack == :side bar_height /= @data.length if stack == :side
y_mod = (bar_height / 2) + (font_size / 2) y_mod = (bar_height / 2) + (font_size / 2)
field_count = 1 field_count = 1
@config[:fields].each_index { |i| @config[:fields].each_index { |i|
dataset_count = 0 dataset_count = 0
for dataset in @data for dataset in @data
value = dataset[:data][i] value = dataset[:data][i]
top = @graph_height - (fieldheight * field_count) top = @graph_height - (fieldheight * field_count)
top += (bar_height * dataset_count) if stack == :side top += (bar_height * dataset_count) if stack == :side
# cases (assume 0 = +ve): # cases (assume 0 = +ve):
# value min length left # value min length left
# +ve +ve value.abs - min minvalue.abs # +ve +ve value.abs - min minvalue.abs
# +ve -ve value.abs - 0 minvalue.abs # +ve -ve value.abs - 0 minvalue.abs
# -ve -ve value.abs - 0 minvalue.abs + value # -ve -ve value.abs - 0 minvalue.abs + value
length = (value.abs - (minvalue > 0 ? minvalue : 0)) * unit_size length = (value.abs - (minvalue > 0 ? minvalue : 0)) * unit_size
left = (minvalue.abs + (value < 0 ? value : 0)) * unit_size left = (minvalue.abs + (value < 0 ? value : 0)) * unit_size
@graph.add_element( "rect", { @graph.add_element( "rect", {
"x" => left.to_s, "x" => left.to_s,
"y" => top.to_s, "y" => top.to_s,
"width" => length.to_s, "width" => length.to_s,
"height" => bar_height.to_s, "height" => bar_height.to_s,
"class" => "fill#{dataset_count+1}" "class" => "fill#{dataset_count+1}"
}) })
make_datapoint_text( make_datapoint_text(
left+length+5, top+y_mod, value, "text-anchor: start; " left+length+5, top+y_mod, value, "text-anchor: start; "
) )
dataset_count += 1 dataset_count += 1
end end
field_count += 1 field_count += 1
} }
end end
end end
end end
end end

File diff suppressed because it is too large Load Diff

View File

@ -1,395 +1,395 @@
require 'SVG/Graph/Graph' require 'SVG/Graph/Graph'
module SVG module SVG
module Graph module Graph
# === Create presentation quality SVG pie graphs easily # === Create presentation quality SVG pie graphs easily
# #
# == Synopsis # == Synopsis
# #
# require 'SVG/Graph/Pie' # require 'SVG/Graph/Pie'
# #
# fields = %w(Jan Feb Mar) # fields = %w(Jan Feb Mar)
# data_sales_02 = [12, 45, 21] # data_sales_02 = [12, 45, 21]
# #
# graph = SVG::Graph::Pie.new({ # graph = SVG::Graph::Pie.new({
# :height => 500, # :height => 500,
# :width => 300, # :width => 300,
# :fields => fields, # :fields => fields,
# }) # })
# #
# graph.add_data({ # graph.add_data({
# :data => data_sales_02, # :data => data_sales_02,
# :title => 'Sales 2002', # :title => 'Sales 2002',
# }) # })
# #
# print "Content-type: image/svg+xml\r\n\r\n" # print "Content-type: image/svg+xml\r\n\r\n"
# print graph.burn(); # print graph.burn();
# #
# == Description # == Description
# #
# This object aims to allow you to easily create high quality # This object aims to allow you to easily create high quality
# SVG pie graphs. You can either use the default style sheet # SVG pie graphs. You can either use the default style sheet
# or supply your own. Either way there are many options which can # or supply your own. Either way there are many options which can
# be configured to give you control over how the graph is # be configured to give you control over how the graph is
# generated - with or without a key, display percent on pie chart, # generated - with or without a key, display percent on pie chart,
# title, subtitle etc. # title, subtitle etc.
# #
# = Examples # = Examples
# #
# http://www.germane-software/repositories/public/SVG/test/single.rb # http://www.germane-software/repositories/public/SVG/test/single.rb
# #
# == See also # == See also
# #
# * SVG::Graph::Graph # * SVG::Graph::Graph
# * SVG::Graph::BarHorizontal # * SVG::Graph::BarHorizontal
# * SVG::Graph::Bar # * SVG::Graph::Bar
# * SVG::Graph::Line # * SVG::Graph::Line
# * SVG::Graph::Plot # * SVG::Graph::Plot
# * SVG::Graph::TimeSeries # * SVG::Graph::TimeSeries
# #
# == Author # == Author
# #
# Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom> # Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>
# #
# Copyright 2004 Sean E. Russell # Copyright 2004 Sean E. Russell
# This software is available under the Ruby license[LICENSE.txt] # This software is available under the Ruby license[LICENSE.txt]
# #
class Pie < Graph class Pie < Graph
# Defaults are those set by Graph::initialize, and # Defaults are those set by Graph::initialize, and
# [show_shadow] true # [show_shadow] true
# [shadow_offset] 10 # [shadow_offset] 10
# [show_data_labels] false # [show_data_labels] false
# [show_actual_values] false # [show_actual_values] false
# [show_percent] true # [show_percent] true
# [show_key_data_labels] true # [show_key_data_labels] true
# [show_key_actual_values] true # [show_key_actual_values] true
# [show_key_percent] false # [show_key_percent] false
# [expanded] false # [expanded] false
# [expand_greatest] false # [expand_greatest] false
# [expand_gap] 10 # [expand_gap] 10
# [show_x_labels] false # [show_x_labels] false
# [show_y_labels] false # [show_y_labels] false
# [datapoint_font_size] 12 # [datapoint_font_size] 12
def set_defaults def set_defaults
init_with( init_with(
:show_shadow => true, :show_shadow => true,
:shadow_offset => 10, :shadow_offset => 10,
:show_data_labels => false, :show_data_labels => false,
:show_actual_values => false, :show_actual_values => false,
:show_percent => true, :show_percent => true,
:show_key_data_labels => true, :show_key_data_labels => true,
:show_key_actual_values => true, :show_key_actual_values => true,
:show_key_percent => false, :show_key_percent => false,
:expanded => false, :expanded => false,
:expand_greatest => false, :expand_greatest => false,
:expand_gap => 10, :expand_gap => 10,
:show_x_labels => false, :show_x_labels => false,
:show_y_labels => false, :show_y_labels => false,
:datapoint_font_size => 12 :datapoint_font_size => 12
) )
@data = [] @data = []
end end
# Adds a data set to the graph. # Adds a data set to the graph.
# #
# graph.add_data( { :data => [1,2,3,4] } ) # graph.add_data( { :data => [1,2,3,4] } )
# #
# Note that the :title is not necessary. If multiple # Note that the :title is not necessary. If multiple
# data sets are added to the graph, the pie chart will # data sets are added to the graph, the pie chart will
# display the +sums+ of the data. EG: # display the +sums+ of the data. EG:
# #
# graph.add_data( { :data => [1,2,3,4] } ) # graph.add_data( { :data => [1,2,3,4] } )
# graph.add_data( { :data => [2,3,5,9] } ) # graph.add_data( { :data => [2,3,5,9] } )
# #
# is the same as: # is the same as:
# #
# graph.add_data( { :data => [3,5,8,13] } ) # graph.add_data( { :data => [3,5,8,13] } )
def add_data arg def add_data arg
arg[:data].each_index {|idx| arg[:data].each_index {|idx|
@data[idx] = 0 unless @data[idx] @data[idx] = 0 unless @data[idx]
@data[idx] += arg[:data][idx] @data[idx] += arg[:data][idx]
} }
end end
# If true, displays a drop shadow for the chart # If true, displays a drop shadow for the chart
attr_accessor :show_shadow attr_accessor :show_shadow
# Sets the offset of the shadow from the pie chart # Sets the offset of the shadow from the pie chart
attr_accessor :shadow_offset attr_accessor :shadow_offset
# If true, display the data labels on the chart # If true, display the data labels on the chart
attr_accessor :show_data_labels attr_accessor :show_data_labels
# If true, display the actual field values in the data labels # If true, display the actual field values in the data labels
attr_accessor :show_actual_values attr_accessor :show_actual_values
# If true, display the percentage value of each pie wedge in the data # If true, display the percentage value of each pie wedge in the data
# labels # labels
attr_accessor :show_percent attr_accessor :show_percent
# If true, display the labels in the key # If true, display the labels in the key
attr_accessor :show_key_data_labels attr_accessor :show_key_data_labels
# If true, display the actual value of the field in the key # If true, display the actual value of the field in the key
attr_accessor :show_key_actual_values attr_accessor :show_key_actual_values
# If true, display the percentage value of the wedges in the key # If true, display the percentage value of the wedges in the key
attr_accessor :show_key_percent attr_accessor :show_key_percent
# If true, "explode" the pie (put space between the wedges) # If true, "explode" the pie (put space between the wedges)
attr_accessor :expanded attr_accessor :expanded
# If true, expand the largest pie wedge # If true, expand the largest pie wedge
attr_accessor :expand_greatest attr_accessor :expand_greatest
# The amount of space between expanded wedges # The amount of space between expanded wedges
attr_accessor :expand_gap attr_accessor :expand_gap
# The font size of the data point labels # The font size of the data point labels
attr_accessor :datapoint_font_size attr_accessor :datapoint_font_size
protected protected
def add_defs defs def add_defs defs
gradient = defs.add_element( "filter", { gradient = defs.add_element( "filter", {
"id"=>"dropshadow", "id"=>"dropshadow",
"width" => "1.2", "width" => "1.2",
"height" => "1.2", "height" => "1.2",
} ) } )
gradient.add_element( "feGaussianBlur", { gradient.add_element( "feGaussianBlur", {
"stdDeviation" => "4", "stdDeviation" => "4",
"result" => "blur" "result" => "blur"
}) })
end end
# We don't need the graph # We don't need the graph
def draw_graph def draw_graph
end end
def get_y_labels def get_y_labels
[""] [""]
end end
def get_x_labels def get_x_labels
[""] [""]
end end
def keys def keys
total = 0 total = 0
max_value = 0 max_value = 0
@data.each {|x| total += x } @data.each {|x| total += x }
percent_scale = 100.0 / total percent_scale = 100.0 / total
count = -1 count = -1
a = @config[:fields].collect{ |x| a = @config[:fields].collect{ |x|
count += 1 count += 1
v = @data[count] v = @data[count]
perc = show_key_percent ? " "+(v * percent_scale).round.to_s+"%" : "" perc = show_key_percent ? " "+(v * percent_scale).round.to_s+"%" : ""
x + " [" + v.to_s + "]" + perc x + " [" + v.to_s + "]" + perc
} }
end end
RADIANS = Math::PI/180 RADIANS = Math::PI/180
def draw_data def draw_data
@graph = @root.add_element( "g" ) @graph = @root.add_element( "g" )
background = @graph.add_element("g") background = @graph.add_element("g")
midground = @graph.add_element("g") midground = @graph.add_element("g")
diameter = @graph_height > @graph_width ? @graph_width : @graph_height diameter = @graph_height > @graph_width ? @graph_width : @graph_height
diameter -= expand_gap if expanded or expand_greatest diameter -= expand_gap if expanded or expand_greatest
diameter -= datapoint_font_size if show_data_labels diameter -= datapoint_font_size if show_data_labels
diameter -= 10 if show_shadow diameter -= 10 if show_shadow
radius = diameter / 2.0 radius = diameter / 2.0
xoff = (width - diameter) / 2 xoff = (width - diameter) / 2
yoff = (height - @border_bottom - diameter) yoff = (height - @border_bottom - diameter)
yoff -= 10 if show_shadow yoff -= 10 if show_shadow
@graph.attributes['transform'] = "translate( #{xoff} #{yoff} )" @graph.attributes['transform'] = "translate( #{xoff} #{yoff} )"
wedge_text_pad = 5 wedge_text_pad = 5
wedge_text_pad = 20 if show_percent and show_data_labels wedge_text_pad = 20 if show_percent and show_data_labels
total = 0 total = 0
max_value = 0 max_value = 0
@data.each {|x| @data.each {|x|
max_value = max_value < x ? x : max_value max_value = max_value < x ? x : max_value
total += x total += x
} }
percent_scale = 100.0 / total percent_scale = 100.0 / total
prev_percent = 0 prev_percent = 0
rad_mult = 3.6 * RADIANS rad_mult = 3.6 * RADIANS
@config[:fields].each_index { |count| @config[:fields].each_index { |count|
value = @data[count] value = @data[count]
percent = percent_scale * value percent = percent_scale * value
radians = prev_percent * rad_mult radians = prev_percent * rad_mult
x_start = radius+(Math.sin(radians) * radius) x_start = radius+(Math.sin(radians) * radius)
y_start = radius-(Math.cos(radians) * radius) y_start = radius-(Math.cos(radians) * radius)
radians = (prev_percent+percent) * rad_mult radians = (prev_percent+percent) * rad_mult
x_end = radius+(Math.sin(radians) * radius) x_end = radius+(Math.sin(radians) * radius)
x_end -= 0.00001 if @data.length == 1 x_end -= 0.00001 if @data.length == 1
y_end = radius-(Math.cos(radians) * radius) y_end = radius-(Math.cos(radians) * radius)
path = "M#{radius},#{radius} L#{x_start},#{y_start} "+ path = "M#{radius},#{radius} L#{x_start},#{y_start} "+
"A#{radius},#{radius} "+ "A#{radius},#{radius} "+
"0, #{percent >= 50 ? '1' : '0'},1, "+ "0, #{percent >= 50 ? '1' : '0'},1, "+
"#{x_end} #{y_end} Z" "#{x_end} #{y_end} Z"
wedge = @foreground.add_element( "path", { wedge = @foreground.add_element( "path", {
"d" => path, "d" => path,
"class" => "fill#{count+1}" "class" => "fill#{count+1}"
}) })
translate = nil translate = nil
tx = 0 tx = 0
ty = 0 ty = 0
half_percent = prev_percent + percent / 2 half_percent = prev_percent + percent / 2
radians = half_percent * rad_mult radians = half_percent * rad_mult
if show_shadow if show_shadow
shadow = background.add_element( "path", { shadow = background.add_element( "path", {
"d" => path, "d" => path,
"filter" => "url(#dropshadow)", "filter" => "url(#dropshadow)",
"style" => "fill: #ccc; stroke: none;" "style" => "fill: #ccc; stroke: none;"
}) })
clear = midground.add_element( "path", { clear = midground.add_element( "path", {
"d" => path, "d" => path,
"style" => "fill: #fff; stroke: none;" "style" => "fill: #fff; stroke: none;"
}) })
end end
if expanded or (expand_greatest && value == max_value) if expanded or (expand_greatest && value == max_value)
tx = (Math.sin(radians) * expand_gap) tx = (Math.sin(radians) * expand_gap)
ty = -(Math.cos(radians) * expand_gap) ty = -(Math.cos(radians) * expand_gap)
translate = "translate( #{tx} #{ty} )" translate = "translate( #{tx} #{ty} )"
wedge.attributes["transform"] = translate wedge.attributes["transform"] = translate
clear.attributes["transform"] = translate if clear clear.attributes["transform"] = translate if clear
end end
if show_shadow if show_shadow
shadow.attributes["transform"] = shadow.attributes["transform"] =
"translate( #{tx+shadow_offset} #{ty+shadow_offset} )" "translate( #{tx+shadow_offset} #{ty+shadow_offset} )"
end end
if show_data_labels and value != 0 if show_data_labels and value != 0
label = "" label = ""
label += @config[:fields][count] if show_key_data_labels label += @config[:fields][count] if show_key_data_labels
label += " ["+value.to_s+"]" if show_actual_values label += " ["+value.to_s+"]" if show_actual_values
label += " "+percent.round.to_s+"%" if show_percent label += " "+percent.round.to_s+"%" if show_percent
msr = Math.sin(radians) msr = Math.sin(radians)
mcr = Math.cos(radians) mcr = Math.cos(radians)
tx = radius + (msr * radius) tx = radius + (msr * radius)
ty = radius -(mcr * radius) ty = radius -(mcr * radius)
if expanded or (expand_greatest && value == max_value) if expanded or (expand_greatest && value == max_value)
tx += (msr * expand_gap) tx += (msr * expand_gap)
ty -= (mcr * expand_gap) ty -= (mcr * expand_gap)
end end
@foreground.add_element( "text", { @foreground.add_element( "text", {
"x" => tx.to_s, "x" => tx.to_s,
"y" => ty.to_s, "y" => ty.to_s,
"class" => "dataPointLabel", "class" => "dataPointLabel",
"style" => "stroke: #fff; stroke-width: 2;" "style" => "stroke: #fff; stroke-width: 2;"
}).text = label.to_s }).text = label.to_s
@foreground.add_element( "text", { @foreground.add_element( "text", {
"x" => tx.to_s, "x" => tx.to_s,
"y" => ty.to_s, "y" => ty.to_s,
"class" => "dataPointLabel", "class" => "dataPointLabel",
}).text = label.to_s }).text = label.to_s
end end
prev_percent += percent prev_percent += percent
} }
end end
def round val, to def round val, to
up = 10**to.to_f up = 10**to.to_f
(val * up).to_i / up (val * up).to_i / up
end end
def get_css def get_css
return <<EOL return <<EOL
.dataPointLabel{ .dataPointLabel{
fill: #000000; fill: #000000;
text-anchor:middle; text-anchor:middle;
font-size: #{datapoint_font_size}px; font-size: #{datapoint_font_size}px;
font-family: "Arial", sans-serif; font-family: "Arial", sans-serif;
font-weight: normal; font-weight: normal;
} }
/* key - MUST match fill styles */ /* key - MUST match fill styles */
.key1,.fill1{ .key1,.fill1{
fill: #ff0000; fill: #ff0000;
fill-opacity: 0.7; fill-opacity: 0.7;
stroke: none; stroke: none;
stroke-width: 1px; stroke-width: 1px;
} }
.key2,.fill2{ .key2,.fill2{
fill: #0000ff; fill: #0000ff;
fill-opacity: 0.7; fill-opacity: 0.7;
stroke: none; stroke: none;
stroke-width: 1px; stroke-width: 1px;
} }
.key3,.fill3{ .key3,.fill3{
fill-opacity: 0.7; fill-opacity: 0.7;
fill: #00ff00; fill: #00ff00;
stroke: none; stroke: none;
stroke-width: 1px; stroke-width: 1px;
} }
.key4,.fill4{ .key4,.fill4{
fill-opacity: 0.7; fill-opacity: 0.7;
fill: #ffcc00; fill: #ffcc00;
stroke: none; stroke: none;
stroke-width: 1px; stroke-width: 1px;
} }
.key5,.fill5{ .key5,.fill5{
fill-opacity: 0.7; fill-opacity: 0.7;
fill: #00ccff; fill: #00ccff;
stroke: none; stroke: none;
stroke-width: 1px; stroke-width: 1px;
} }
.key6,.fill6{ .key6,.fill6{
fill-opacity: 0.7; fill-opacity: 0.7;
fill: #ff00ff; fill: #ff00ff;
stroke: none; stroke: none;
stroke-width: 1px; stroke-width: 1px;
} }
.key7,.fill7{ .key7,.fill7{
fill-opacity: 0.7; fill-opacity: 0.7;
fill: #00ff99; fill: #00ff99;
stroke: none; stroke: none;
stroke-width: 1px; stroke-width: 1px;
} }
.key8,.fill8{ .key8,.fill8{
fill-opacity: 0.7; fill-opacity: 0.7;
fill: #ffff00; fill: #ffff00;
stroke: none; stroke: none;
stroke-width: 1px; stroke-width: 1px;
} }
.key9,.fill9{ .key9,.fill9{
fill-opacity: 0.7; fill-opacity: 0.7;
fill: #cc6666; fill: #cc6666;
stroke: none; stroke: none;
stroke-width: 1px; stroke-width: 1px;
} }
.key10,.fill10{ .key10,.fill10{
fill-opacity: 0.7; fill-opacity: 0.7;
fill: #663399; fill: #663399;
stroke: none; stroke: none;
stroke-width: 1px; stroke-width: 1px;
} }
.key11,.fill11{ .key11,.fill11{
fill-opacity: 0.7; fill-opacity: 0.7;
fill: #339900; fill: #339900;
stroke: none; stroke: none;
stroke-width: 1px; stroke-width: 1px;
} }
.key12,.fill12{ .key12,.fill12{
fill-opacity: 0.7; fill-opacity: 0.7;
fill: #9966FF; fill: #9966FF;
stroke: none; stroke: none;
stroke-width: 1px; stroke-width: 1px;
} }
EOL EOL
end end
end end
end end
end end

File diff suppressed because it is too large Load Diff

View File

@ -1,241 +1,241 @@
require 'SVG/Graph/Plot' require 'SVG/Graph/Plot'
require 'parsedate' require 'parsedate'
module SVG module SVG
module Graph module Graph
# === For creating SVG plots of scalar temporal data # === For creating SVG plots of scalar temporal data
# #
# = Synopsis # = Synopsis
# #
# require 'SVG/Graph/TimeSeriess' # require 'SVG/Graph/TimeSeriess'
# #
# # Data sets are x,y pairs # # Data sets are x,y pairs
# data1 = ["6/17/72", 11, "1/11/72", 7, "4/13/04 17:31", 11, # data1 = ["6/17/72", 11, "1/11/72", 7, "4/13/04 17:31", 11,
# "9/11/01", 9, "9/1/85", 2, "9/1/88", 1, "1/15/95", 13] # "9/11/01", 9, "9/1/85", 2, "9/1/88", 1, "1/15/95", 13]
# data2 = ["8/1/73", 18, "3/1/77", 15, "10/1/98", 4, # data2 = ["8/1/73", 18, "3/1/77", 15, "10/1/98", 4,
# "5/1/02", 14, "3/1/95", 6, "8/1/91", 12, "12/1/87", 6, # "5/1/02", 14, "3/1/95", 6, "8/1/91", 12, "12/1/87", 6,
# "5/1/84", 17, "10/1/80", 12] # "5/1/84", 17, "10/1/80", 12]
# #
# graph = SVG::Graph::TimeSeries.new( { # graph = SVG::Graph::TimeSeries.new( {
# :width => 640, # :width => 640,
# :height => 480, # :height => 480,
# :graph_title => title, # :graph_title => title,
# :show_graph_title => true, # :show_graph_title => true,
# :no_css => true, # :no_css => true,
# :key => true, # :key => true,
# :scale_x_integers => true, # :scale_x_integers => true,
# :scale_y_integers => true, # :scale_y_integers => true,
# :min_x_value => 0, # :min_x_value => 0,
# :min_y_value => 0, # :min_y_value => 0,
# :show_data_labels => true, # :show_data_labels => true,
# :show_x_guidelines => true, # :show_x_guidelines => true,
# :show_x_title => true, # :show_x_title => true,
# :x_title => "Time", # :x_title => "Time",
# :show_y_title => true, # :show_y_title => true,
# :y_title => "Ice Cream Cones", # :y_title => "Ice Cream Cones",
# :y_title_text_direction => :bt, # :y_title_text_direction => :bt,
# :stagger_x_labels => true, # :stagger_x_labels => true,
# :x_label_format => "%m/%d/%y", # :x_label_format => "%m/%d/%y",
# }) # })
# #
# graph.add_data({ # graph.add_data({
# :data => projection # :data => projection
# :title => 'Projected', # :title => 'Projected',
# }) # })
# #
# graph.add_data({ # graph.add_data({
# :data => actual, # :data => actual,
# :title => 'Actual', # :title => 'Actual',
# }) # })
# #
# print graph.burn() # print graph.burn()
# #
# = Description # = Description
# #
# Produces a graph of temporal scalar data. # Produces a graph of temporal scalar data.
# #
# = Examples # = Examples
# #
# http://www.germane-software/repositories/public/SVG/test/timeseries.rb # http://www.germane-software/repositories/public/SVG/test/timeseries.rb
# #
# = Notes # = Notes
# #
# The default stylesheet handles upto 10 data sets, if you # The default stylesheet handles upto 10 data sets, if you
# use more you must create your own stylesheet and add the # use more you must create your own stylesheet and add the
# additional settings for the extra data sets. You will know # additional settings for the extra data sets. You will know
# if you go over 10 data sets as they will have no style and # if you go over 10 data sets as they will have no style and
# be in black. # be in black.
# #
# Unlike the other types of charts, data sets must contain x,y pairs: # Unlike the other types of charts, data sets must contain x,y pairs:
# #
# [ "12:30", 2 ] # A data set with 1 point: ("12:30",2) # [ "12:30", 2 ] # A data set with 1 point: ("12:30",2)
# [ "01:00",2, "14:20",6] # A data set with 2 points: ("01:00",2) and # [ "01:00",2, "14:20",6] # A data set with 2 points: ("01:00",2) and
# # ("14:20",6) # # ("14:20",6)
# #
# Note that multiple data sets within the same chart can differ in length, # Note that multiple data sets within the same chart can differ in length,
# and that the data in the datasets needn't be in order; they will be ordered # and that the data in the datasets needn't be in order; they will be ordered
# by the plot along the X-axis. # by the plot along the X-axis.
# #
# The dates must be parseable by ParseDate, but otherwise can be # The dates must be parseable by ParseDate, but otherwise can be
# any order of magnitude (seconds within the hour, or years) # any order of magnitude (seconds within the hour, or years)
# #
# = See also # = See also
# #
# * SVG::Graph::Graph # * SVG::Graph::Graph
# * SVG::Graph::BarHorizontal # * SVG::Graph::BarHorizontal
# * SVG::Graph::Bar # * SVG::Graph::Bar
# * SVG::Graph::Line # * SVG::Graph::Line
# * SVG::Graph::Pie # * SVG::Graph::Pie
# * SVG::Graph::Plot # * SVG::Graph::Plot
# #
# == Author # == Author
# #
# Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom> # Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>
# #
# Copyright 2004 Sean E. Russell # Copyright 2004 Sean E. Russell
# This software is available under the Ruby license[LICENSE.txt] # This software is available under the Ruby license[LICENSE.txt]
# #
class TimeSeries < Plot class TimeSeries < Plot
# In addition to the defaults set by Graph::initialize and # In addition to the defaults set by Graph::initialize and
# Plot::set_defaults, sets: # Plot::set_defaults, sets:
# [x_label_format] '%Y-%m-%d %H:%M:%S' # [x_label_format] '%Y-%m-%d %H:%M:%S'
# [popup_format] '%Y-%m-%d %H:%M:%S' # [popup_format] '%Y-%m-%d %H:%M:%S'
def set_defaults def set_defaults
super super
init_with( init_with(
#:max_time_span => '', #:max_time_span => '',
:x_label_format => '%Y-%m-%d %H:%M:%S', :x_label_format => '%Y-%m-%d %H:%M:%S',
:popup_format => '%Y-%m-%d %H:%M:%S' :popup_format => '%Y-%m-%d %H:%M:%S'
) )
end end
# The format string use do format the X axis labels. # The format string use do format the X axis labels.
# See Time::strformat # See Time::strformat
attr_accessor :x_label_format attr_accessor :x_label_format
# Use this to set the spacing between dates on the axis. The value # Use this to set the spacing between dates on the axis. The value
# must be of the form # must be of the form
# "\d+ ?(days|weeks|months|years|hours|minutes|seconds)?" # "\d+ ?(days|weeks|months|years|hours|minutes|seconds)?"
# #
# EG: # EG:
# #
# graph.timescale_divisions = "2 weeks" # graph.timescale_divisions = "2 weeks"
# #
# will cause the chart to try to divide the X axis up into segments of # will cause the chart to try to divide the X axis up into segments of
# two week periods. # two week periods.
attr_accessor :timescale_divisions attr_accessor :timescale_divisions
# The formatting used for the popups. See x_label_format # The formatting used for the popups. See x_label_format
attr_accessor :popup_format attr_accessor :popup_format
# Add data to the plot. # Add data to the plot.
# #
# d1 = [ "12:30", 2 ] # A data set with 1 point: ("12:30",2) # d1 = [ "12:30", 2 ] # A data set with 1 point: ("12:30",2)
# d2 = [ "01:00",2, "14:20",6] # A data set with 2 points: ("01:00",2) and # d2 = [ "01:00",2, "14:20",6] # A data set with 2 points: ("01:00",2) and
# # ("14:20",6) # # ("14:20",6)
# graph.add_data( # graph.add_data(
# :data => d1, # :data => d1,
# :title => 'One' # :title => 'One'
# ) # )
# graph.add_data( # graph.add_data(
# :data => d2, # :data => d2,
# :title => 'Two' # :title => 'Two'
# ) # )
# #
# Note that the data must be in time,value pairs, and that the date format # Note that the data must be in time,value pairs, and that the date format
# may be any date that is parseable by ParseDate. # may be any date that is parseable by ParseDate.
def add_data data def add_data data
@data = [] unless @data @data = [] unless @data
raise "No data provided by #{@data.inspect}" unless data[:data] and raise "No data provided by #{@data.inspect}" unless data[:data] and
data[:data].kind_of? Array data[:data].kind_of? Array
raise "Data supplied must be x,y pairs! "+ raise "Data supplied must be x,y pairs! "+
"The data provided contained an odd set of "+ "The data provided contained an odd set of "+
"data points" unless data[:data].length % 2 == 0 "data points" unless data[:data].length % 2 == 0
return if data[:data].length == 0 return if data[:data].length == 0
x = [] x = []
y = [] y = []
data[:data].each_index {|i| data[:data].each_index {|i|
if i%2 == 0 if i%2 == 0
arr = ParseDate.parsedate( data[:data][i] ) arr = ParseDate.parsedate( data[:data][i] )
t = Time.local( *arr[0,6].compact ) t = Time.local( *arr[0,6].compact )
x << t.to_i x << t.to_i
else else
y << data[:data][i] y << data[:data][i]
end end
} }
sort( x, y ) sort( x, y )
data[:data] = [x,y] data[:data] = [x,y]
@data << data @data << data
end end
protected protected
def min_x_value=(value) def min_x_value=(value)
arr = ParseDate.parsedate( value ) arr = ParseDate.parsedate( value )
@min_x_value = Time.local( *arr[0,6].compact ).to_i @min_x_value = Time.local( *arr[0,6].compact ).to_i
end end
def format x, y def format x, y
Time.at( x ).strftime( popup_format ) Time.at( x ).strftime( popup_format )
end end
def get_x_labels def get_x_labels
get_x_values.collect { |v| Time.at(v).strftime( x_label_format ) } get_x_values.collect { |v| Time.at(v).strftime( x_label_format ) }
end end
private private
def get_x_values def get_x_values
rv = [] rv = []
min, max, scale_division = x_range min, max, scale_division = x_range
if timescale_divisions if timescale_divisions
timescale_divisions =~ /(\d+) ?(day|week|month|year|hour|minute|second)?/ timescale_divisions =~ /(\d+) ?(day|week|month|year|hour|minute|second)?/
division_units = $2 ? $2 : "day" division_units = $2 ? $2 : "day"
amount = $1.to_i amount = $1.to_i
if amount if amount
step = nil step = nil
case division_units case division_units
when "month" when "month"
cur = min cur = min
while cur < max while cur < max
rv << cur rv << cur
arr = Time.at( cur ).to_a arr = Time.at( cur ).to_a
arr[4] += amount arr[4] += amount
if arr[4] > 12 if arr[4] > 12
arr[5] += (arr[4] / 12).to_i arr[5] += (arr[4] / 12).to_i
arr[4] = (arr[4] % 12) arr[4] = (arr[4] % 12)
end end
cur = Time.local(*arr).to_i cur = Time.local(*arr).to_i
end end
when "year" when "year"
cur = min cur = min
while cur < max while cur < max
rv << cur rv << cur
arr = Time.at( cur ).to_a arr = Time.at( cur ).to_a
arr[5] += amount arr[5] += amount
cur = Time.local(*arr).to_i cur = Time.local(*arr).to_i
end end
when "week" when "week"
step = 7 * 24 * 60 * 60 * amount step = 7 * 24 * 60 * 60 * amount
when "day" when "day"
step = 24 * 60 * 60 * amount step = 24 * 60 * 60 * amount
when "hour" when "hour"
step = 60 * 60 * amount step = 60 * 60 * amount
when "minute" when "minute"
step = 60 * amount step = 60 * amount
when "second" when "second"
step = amount step = amount
end end
min.step( max, step ) {|v| rv << v} if step min.step( max, step ) {|v| rv << v} if step
return rv return rv
end end
end end
min.step( max, scale_division ) {|v| rv << v} min.step( max, scale_division ) {|v| rv << v}
return rv return rv
end end
end end
end end
end end

View File

@ -1,282 +1,282 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2010 Jean-Philippe Lang # Copyright (C) 2006-2010 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require 'redmine/scm/adapters/abstract_adapter' require 'redmine/scm/adapters/abstract_adapter'
require 'uri' require 'uri'
module Redmine module Redmine
module Scm module Scm
module Adapters module Adapters
class SubversionAdapter < AbstractAdapter class SubversionAdapter < AbstractAdapter
# SVN executable name # SVN executable name
SVN_BIN = Redmine::Configuration['scm_subversion_command'] || "svn" SVN_BIN = Redmine::Configuration['scm_subversion_command'] || "svn"
class << self class << self
def client_command def client_command
@@bin ||= SVN_BIN @@bin ||= SVN_BIN
end end
def sq_bin def sq_bin
@@sq_bin ||= shell_quote(SVN_BIN) @@sq_bin ||= shell_quote(SVN_BIN)
end end
def client_version def client_version
@@client_version ||= (svn_binary_version || []) @@client_version ||= (svn_binary_version || [])
end end
def client_available def client_available
!client_version.empty? !client_version.empty?
end end
def svn_binary_version def svn_binary_version
scm_version = scm_version_from_command_line.dup scm_version = scm_version_from_command_line.dup
if scm_version.respond_to?(:force_encoding) if scm_version.respond_to?(:force_encoding)
scm_version.force_encoding('ASCII-8BIT') scm_version.force_encoding('ASCII-8BIT')
end end
if m = scm_version.match(%r{\A(.*?)((\d+\.)+\d+)}) if m = scm_version.match(%r{\A(.*?)((\d+\.)+\d+)})
m[2].scan(%r{\d+}).collect(&:to_i) m[2].scan(%r{\d+}).collect(&:to_i)
end end
end end
def scm_version_from_command_line def scm_version_from_command_line
shellout("#{sq_bin} --version") { |io| io.read }.to_s shellout("#{sq_bin} --version") { |io| io.read }.to_s
end end
end end
# Get info about the svn repository # Get info about the svn repository
def info def info
cmd = "#{self.class.sq_bin} info --xml #{target}" cmd = "#{self.class.sq_bin} info --xml #{target}"
cmd << credentials_string cmd << credentials_string
info = nil info = nil
shellout(cmd) do |io| shellout(cmd) do |io|
output = io.read output = io.read
if output.respond_to?(:force_encoding) if output.respond_to?(:force_encoding)
output.force_encoding('UTF-8') output.force_encoding('UTF-8')
end end
begin begin
doc = ActiveSupport::XmlMini.parse(output) doc = ActiveSupport::XmlMini.parse(output)
#root_url = doc.elements["info/entry/repository/root"].text #root_url = doc.elements["info/entry/repository/root"].text
info = Info.new({:root_url => doc['info']['entry']['repository']['root']['__content__'], info = Info.new({:root_url => doc['info']['entry']['repository']['root']['__content__'],
:lastrev => Revision.new({ :lastrev => Revision.new({
:identifier => doc['info']['entry']['commit']['revision'], :identifier => doc['info']['entry']['commit']['revision'],
:time => Time.parse(doc['info']['entry']['commit']['date']['__content__']).localtime, :time => Time.parse(doc['info']['entry']['commit']['date']['__content__']).localtime,
:author => (doc['info']['entry']['commit']['author'] ? doc['info']['entry']['commit']['author']['__content__'] : "") :author => (doc['info']['entry']['commit']['author'] ? doc['info']['entry']['commit']['author']['__content__'] : "")
}) })
}) })
rescue rescue
end end
end end
return nil if $? && $?.exitstatus != 0 return nil if $? && $?.exitstatus != 0
info info
rescue CommandFailed rescue CommandFailed
return nil return nil
end end
# Returns an Entries collection # Returns an Entries collection
# or nil if the given path doesn't exist in the repository # or nil if the given path doesn't exist in the repository
def entries(path=nil, identifier=nil) def entries(path=nil, identifier=nil)
path ||= '' path ||= ''
identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD" identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
entries = Entries.new entries = Entries.new
cmd = "#{self.class.sq_bin} list --xml #{target(path)}@#{identifier}" cmd = "#{self.class.sq_bin} list --xml #{target(path)}@#{identifier}"
cmd << credentials_string cmd << credentials_string
shellout(cmd) do |io| shellout(cmd) do |io|
output = io.read output = io.read
if output.respond_to?(:force_encoding) if output.respond_to?(:force_encoding)
output.force_encoding('UTF-8') output.force_encoding('UTF-8')
end end
begin begin
doc = ActiveSupport::XmlMini.parse(output) doc = ActiveSupport::XmlMini.parse(output)
each_xml_element(doc['lists']['list'], 'entry') do |entry| each_xml_element(doc['lists']['list'], 'entry') do |entry|
commit = entry['commit'] commit = entry['commit']
commit_date = commit['date'] commit_date = commit['date']
# Skip directory if there is no commit date (usually that # Skip directory if there is no commit date (usually that
# means that we don't have read access to it) # means that we don't have read access to it)
next if entry['kind'] == 'dir' && commit_date.nil? next if entry['kind'] == 'dir' && commit_date.nil?
name = entry['name']['__content__'] name = entry['name']['__content__']
entries << Entry.new({:name => URI.unescape(name), entries << Entry.new({:name => URI.unescape(name),
:path => ((path.empty? ? "" : "#{path}/") + name), :path => ((path.empty? ? "" : "#{path}/") + name),
:kind => entry['kind'], :kind => entry['kind'],
:size => ((s = entry['size']) ? s['__content__'].to_i : nil), :size => ((s = entry['size']) ? s['__content__'].to_i : nil),
:lastrev => Revision.new({ :lastrev => Revision.new({
:identifier => commit['revision'], :identifier => commit['revision'],
:time => Time.parse(commit_date['__content__'].to_s).localtime, :time => Time.parse(commit_date['__content__'].to_s).localtime,
:author => ((a = commit['author']) ? a['__content__'] : nil) :author => ((a = commit['author']) ? a['__content__'] : nil)
}) })
}) })
end end
rescue Exception => e rescue Exception => e
logger.error("Error parsing svn output: #{e.message}") logger.error("Error parsing svn output: #{e.message}")
logger.error("Output was:\n #{output}") logger.error("Output was:\n #{output}")
end end
end end
return nil if $? && $?.exitstatus != 0 return nil if $? && $?.exitstatus != 0
logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug? logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug?
entries.sort_by_name entries.sort_by_name
end end
def properties(path, identifier=nil) def properties(path, identifier=nil)
# proplist xml output supported in svn 1.5.0 and higher # proplist xml output supported in svn 1.5.0 and higher
return nil unless self.class.client_version_above?([1, 5, 0]) return nil unless self.class.client_version_above?([1, 5, 0])
identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD" identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
cmd = "#{self.class.sq_bin} proplist --verbose --xml #{target(path)}@#{identifier}" cmd = "#{self.class.sq_bin} proplist --verbose --xml #{target(path)}@#{identifier}"
cmd << credentials_string cmd << credentials_string
properties = {} properties = {}
shellout(cmd) do |io| shellout(cmd) do |io|
output = io.read output = io.read
if output.respond_to?(:force_encoding) if output.respond_to?(:force_encoding)
output.force_encoding('UTF-8') output.force_encoding('UTF-8')
end end
begin begin
doc = ActiveSupport::XmlMini.parse(output) doc = ActiveSupport::XmlMini.parse(output)
each_xml_element(doc['properties']['target'], 'property') do |property| each_xml_element(doc['properties']['target'], 'property') do |property|
properties[ property['name'] ] = property['__content__'].to_s properties[ property['name'] ] = property['__content__'].to_s
end end
rescue rescue
end end
end end
return nil if $? && $?.exitstatus != 0 return nil if $? && $?.exitstatus != 0
properties properties
end end
def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
path ||= '' path ||= ''
identifier_from = (identifier_from && identifier_from.to_i > 0) ? identifier_from.to_i : "HEAD" identifier_from = (identifier_from && identifier_from.to_i > 0) ? identifier_from.to_i : "HEAD"
identifier_to = (identifier_to && identifier_to.to_i > 0) ? identifier_to.to_i : 1 identifier_to = (identifier_to && identifier_to.to_i > 0) ? identifier_to.to_i : 1
revisions = Revisions.new revisions = Revisions.new
cmd = "#{self.class.sq_bin} log --xml -r #{identifier_from}:#{identifier_to}" cmd = "#{self.class.sq_bin} log --xml -r #{identifier_from}:#{identifier_to}"
cmd << credentials_string cmd << credentials_string
cmd << " --verbose " if options[:with_paths] cmd << " --verbose " if options[:with_paths]
cmd << " --limit #{options[:limit].to_i}" if options[:limit] cmd << " --limit #{options[:limit].to_i}" if options[:limit]
cmd << ' ' + target(path) cmd << ' ' + target(path)
shellout(cmd) do |io| shellout(cmd) do |io|
output = io.read output = io.read
if output.respond_to?(:force_encoding) if output.respond_to?(:force_encoding)
output.force_encoding('UTF-8') output.force_encoding('UTF-8')
end end
begin begin
doc = ActiveSupport::XmlMini.parse(output) doc = ActiveSupport::XmlMini.parse(output)
each_xml_element(doc['log'], 'logentry') do |logentry| each_xml_element(doc['log'], 'logentry') do |logentry|
paths = [] paths = []
each_xml_element(logentry['paths'], 'path') do |path| each_xml_element(logentry['paths'], 'path') do |path|
paths << {:action => path['action'], paths << {:action => path['action'],
:path => path['__content__'], :path => path['__content__'],
:from_path => path['copyfrom-path'], :from_path => path['copyfrom-path'],
:from_revision => path['copyfrom-rev'] :from_revision => path['copyfrom-rev']
} }
end if logentry['paths'] && logentry['paths']['path'] end if logentry['paths'] && logentry['paths']['path']
paths.sort! { |x,y| x[:path] <=> y[:path] } paths.sort! { |x,y| x[:path] <=> y[:path] }
revisions << Revision.new({:identifier => logentry['revision'], revisions << Revision.new({:identifier => logentry['revision'],
:author => (logentry['author'] ? logentry['author']['__content__'] : ""), :author => (logentry['author'] ? logentry['author']['__content__'] : ""),
:time => Time.parse(logentry['date']['__content__'].to_s).localtime, :time => Time.parse(logentry['date']['__content__'].to_s).localtime,
:message => logentry['msg']['__content__'], :message => logentry['msg']['__content__'],
:paths => paths :paths => paths
}) })
end end
rescue rescue
end end
end end
return nil if $? && $?.exitstatus != 0 return nil if $? && $?.exitstatus != 0
revisions revisions
end end
def diff(path, identifier_from, identifier_to=nil, type="inline") def diff(path, identifier_from, identifier_to=nil, type="inline")
path ||= '' path ||= ''
identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : '' identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : ''
identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : (identifier_from.to_i - 1) identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : (identifier_from.to_i - 1)
cmd = "#{self.class.sq_bin} diff -r " cmd = "#{self.class.sq_bin} diff -r "
cmd << "#{identifier_to}:" cmd << "#{identifier_to}:"
cmd << "#{identifier_from}" cmd << "#{identifier_from}"
cmd << " #{target(path)}@#{identifier_from}" cmd << " #{target(path)}@#{identifier_from}"
cmd << credentials_string cmd << credentials_string
diff = [] diff = []
shellout(cmd) do |io| shellout(cmd) do |io|
io.each_line do |line| io.each_line do |line|
diff << line diff << line
end end
end end
return nil if $? && $?.exitstatus != 0 return nil if $? && $?.exitstatus != 0
diff diff
end end
def cat(path, identifier=nil) def cat(path, identifier=nil)
identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD" identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
cmd = "#{self.class.sq_bin} cat #{target(path)}@#{identifier}" cmd = "#{self.class.sq_bin} cat #{target(path)}@#{identifier}"
cmd << credentials_string cmd << credentials_string
cat = nil cat = nil
shellout(cmd) do |io| shellout(cmd) do |io|
io.binmode io.binmode
cat = io.read cat = io.read
end end
return nil if $? && $?.exitstatus != 0 return nil if $? && $?.exitstatus != 0
cat cat
end end
def annotate(path, identifier=nil) def annotate(path, identifier=nil)
identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD" identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
cmd = "#{self.class.sq_bin} blame #{target(path)}@#{identifier}" cmd = "#{self.class.sq_bin} blame #{target(path)}@#{identifier}"
cmd << credentials_string cmd << credentials_string
blame = Annotate.new blame = Annotate.new
shellout(cmd) do |io| shellout(cmd) do |io|
io.each_line do |line| io.each_line do |line|
next unless line =~ %r{^\s*(\d+)\s*(\S+)\s(.*)$} next unless line =~ %r{^\s*(\d+)\s*(\S+)\s(.*)$}
blame.add_line($3.rstrip, Revision.new(:identifier => $1.to_i, :author => $2.strip)) blame.add_line($3.rstrip, Revision.new(:identifier => $1.to_i, :author => $2.strip))
end end
end end
return nil if $? && $?.exitstatus != 0 return nil if $? && $?.exitstatus != 0
blame blame
end end
private private
def credentials_string def credentials_string
str = '' str = ''
str << " --username #{shell_quote(@login)}" unless @login.blank? str << " --username #{shell_quote(@login)}" unless @login.blank?
str << " --password #{shell_quote(@password)}" unless @login.blank? || @password.blank? str << " --password #{shell_quote(@password)}" unless @login.blank? || @password.blank?
str << " --no-auth-cache --non-interactive" str << " --no-auth-cache --non-interactive"
str str
end end
# Helper that iterates over the child elements of a xml node # Helper that iterates over the child elements of a xml node
# MiniXml returns a hash when a single child is found or an array of hashes for multiple children # MiniXml returns a hash when a single child is found or an array of hashes for multiple children
def each_xml_element(node, name) def each_xml_element(node, name)
if node && node[name] if node && node[name]
if node[name].is_a?(Hash) if node[name].is_a?(Hash)
yield node[name] yield node[name]
else else
node[name].each do |element| node[name].each do |element|
yield element yield element
end end
end end
end end
end end
def target(path = '') def target(path = '')
base = path.match(/^\//) ? root_url : url base = path.match(/^\//) ? root_url : url
uri = "#{base}/#{path}" uri = "#{base}/#{path}"
uri = URI.escape(URI.escape(uri), '[]') uri = URI.escape(URI.escape(uri), '[]')
shell_quote(uri.gsub(/[?<>\*]/, '')) shell_quote(uri.gsub(/[?<>\*]/, ''))
end end
end end
end end
end end
end end

View File

@ -1,186 +1,186 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2008 Jean-Philippe Lang # Copyright (C) 2006-2008 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
namespace :redmine do namespace :redmine do
namespace :email do namespace :email do
desc <<-END_DESC desc <<-END_DESC
Read an email from standard input. Read an email from standard input.
General options: General options:
unknown_user=ACTION how to handle emails from an unknown user unknown_user=ACTION how to handle emails from an unknown user
ACTION can be one of the following values: ACTION can be one of the following values:
ignore: email is ignored (default) ignore: email is ignored (default)
accept: accept as anonymous user accept: accept as anonymous user
create: create a user account create: create a user account
no_permission_check=1 disable permission checking when receiving no_permission_check=1 disable permission checking when receiving
the email the email
Issue attributes control options: Issue attributes control options:
project=PROJECT identifier of the target project project=PROJECT identifier of the target project
status=STATUS name of the target status status=STATUS name of the target status
tracker=TRACKER name of the target tracker tracker=TRACKER name of the target tracker
category=CATEGORY name of the target category category=CATEGORY name of the target category
priority=PRIORITY name of the target priority priority=PRIORITY name of the target priority
allow_override=ATTRS allow email content to override attributes allow_override=ATTRS allow email content to override attributes
specified by previous options specified by previous options
ATTRS is a comma separated list of attributes ATTRS is a comma separated list of attributes
Examples: Examples:
# No project specified. Emails MUST contain the 'Project' keyword: # No project specified. Emails MUST contain the 'Project' keyword:
rake redmine:email:read RAILS_ENV="production" < raw_email rake redmine:email:read RAILS_ENV="production" < raw_email
# Fixed project and default tracker specified, but emails can override # Fixed project and default tracker specified, but emails can override
# both tracker and priority attributes: # both tracker and priority attributes:
rake redmine:email:read RAILS_ENV="production" \\ rake redmine:email:read RAILS_ENV="production" \\
project=foo \\ project=foo \\
tracker=bug \\ tracker=bug \\
allow_override=tracker,priority < raw_email allow_override=tracker,priority < raw_email
END_DESC END_DESC
task :read => :environment do task :read => :environment do
options = { :issue => {} } options = { :issue => {} }
%w(project status tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] } %w(project status tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] }
options[:allow_override] = ENV['allow_override'] if ENV['allow_override'] options[:allow_override] = ENV['allow_override'] if ENV['allow_override']
options[:unknown_user] = ENV['unknown_user'] if ENV['unknown_user'] options[:unknown_user] = ENV['unknown_user'] if ENV['unknown_user']
options[:no_permission_check] = ENV['no_permission_check'] if ENV['no_permission_check'] options[:no_permission_check] = ENV['no_permission_check'] if ENV['no_permission_check']
MailHandler.receive(STDIN.read, options) MailHandler.receive(STDIN.read, options)
end end
desc <<-END_DESC desc <<-END_DESC
Read emails from an IMAP server. Read emails from an IMAP server.
General options: General options:
unknown_user=ACTION how to handle emails from an unknown user unknown_user=ACTION how to handle emails from an unknown user
ACTION can be one of the following values: ACTION can be one of the following values:
ignore: email is ignored (default) ignore: email is ignored (default)
accept: accept as anonymous user accept: accept as anonymous user
create: create a user account create: create a user account
no_permission_check=1 disable permission checking when receiving no_permission_check=1 disable permission checking when receiving
the email the email
Available IMAP options: Available IMAP options:
host=HOST IMAP server host (default: 127.0.0.1) host=HOST IMAP server host (default: 127.0.0.1)
port=PORT IMAP server port (default: 143) port=PORT IMAP server port (default: 143)
ssl=SSL Use SSL? (default: false) ssl=SSL Use SSL? (default: false)
username=USERNAME IMAP account username=USERNAME IMAP account
password=PASSWORD IMAP password password=PASSWORD IMAP password
folder=FOLDER IMAP folder to read (default: INBOX) folder=FOLDER IMAP folder to read (default: INBOX)
Issue attributes control options: Issue attributes control options:
project=PROJECT identifier of the target project project=PROJECT identifier of the target project
status=STATUS name of the target status status=STATUS name of the target status
tracker=TRACKER name of the target tracker tracker=TRACKER name of the target tracker
category=CATEGORY name of the target category category=CATEGORY name of the target category
priority=PRIORITY name of the target priority priority=PRIORITY name of the target priority
allow_override=ATTRS allow email content to override attributes allow_override=ATTRS allow email content to override attributes
specified by previous options specified by previous options
ATTRS is a comma separated list of attributes ATTRS is a comma separated list of attributes
Processed emails control options: Processed emails control options:
move_on_success=MAILBOX move emails that were successfully received move_on_success=MAILBOX move emails that were successfully received
to MAILBOX instead of deleting them to MAILBOX instead of deleting them
move_on_failure=MAILBOX move emails that were ignored to MAILBOX move_on_failure=MAILBOX move emails that were ignored to MAILBOX
Examples: Examples:
# No project specified. Emails MUST contain the 'Project' keyword: # No project specified. Emails MUST contain the 'Project' keyword:
rake redmine:email:receive_iamp RAILS_ENV="production" \\ rake redmine:email:receive_iamp RAILS_ENV="production" \\
host=imap.foo.bar username=redmine@example.net password=xxx host=imap.foo.bar username=redmine@example.net password=xxx
# Fixed project and default tracker specified, but emails can override # Fixed project and default tracker specified, but emails can override
# both tracker and priority attributes: # both tracker and priority attributes:
rake redmine:email:receive_iamp RAILS_ENV="production" \\ rake redmine:email:receive_iamp RAILS_ENV="production" \\
host=imap.foo.bar username=redmine@example.net password=xxx ssl=1 \\ host=imap.foo.bar username=redmine@example.net password=xxx ssl=1 \\
project=foo \\ project=foo \\
tracker=bug \\ tracker=bug \\
allow_override=tracker,priority allow_override=tracker,priority
END_DESC END_DESC
task :receive_imap => :environment do task :receive_imap => :environment do
imap_options = {:host => ENV['host'], imap_options = {:host => ENV['host'],
:port => ENV['port'], :port => ENV['port'],
:ssl => ENV['ssl'], :ssl => ENV['ssl'],
:username => ENV['username'], :username => ENV['username'],
:password => ENV['password'], :password => ENV['password'],
:folder => ENV['folder'], :folder => ENV['folder'],
:move_on_success => ENV['move_on_success'], :move_on_success => ENV['move_on_success'],
:move_on_failure => ENV['move_on_failure']} :move_on_failure => ENV['move_on_failure']}
options = { :issue => {} } options = { :issue => {} }
%w(project status tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] } %w(project status tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] }
options[:allow_override] = ENV['allow_override'] if ENV['allow_override'] options[:allow_override] = ENV['allow_override'] if ENV['allow_override']
options[:unknown_user] = ENV['unknown_user'] if ENV['unknown_user'] options[:unknown_user] = ENV['unknown_user'] if ENV['unknown_user']
options[:no_permission_check] = ENV['no_permission_check'] if ENV['no_permission_check'] options[:no_permission_check] = ENV['no_permission_check'] if ENV['no_permission_check']
Redmine::IMAP.check(imap_options, options) Redmine::IMAP.check(imap_options, options)
end end
desc <<-END_DESC desc <<-END_DESC
Read emails from an POP3 server. Read emails from an POP3 server.
Available POP3 options: Available POP3 options:
host=HOST POP3 server host (default: 127.0.0.1) host=HOST POP3 server host (default: 127.0.0.1)
port=PORT POP3 server port (default: 110) port=PORT POP3 server port (default: 110)
username=USERNAME POP3 account username=USERNAME POP3 account
password=PASSWORD POP3 password password=PASSWORD POP3 password
apop=1 use APOP authentication (default: false) apop=1 use APOP authentication (default: false)
delete_unprocessed=1 delete messages that could not be processed delete_unprocessed=1 delete messages that could not be processed
successfully from the server (default successfully from the server (default
behaviour is to leave them on the server) behaviour is to leave them on the server)
See redmine:email:receive_imap for more options and examples. See redmine:email:receive_imap for more options and examples.
END_DESC END_DESC
task :receive_pop3 => :environment do task :receive_pop3 => :environment do
pop_options = {:host => ENV['host'], pop_options = {:host => ENV['host'],
:port => ENV['port'], :port => ENV['port'],
:apop => ENV['apop'], :apop => ENV['apop'],
:username => ENV['username'], :username => ENV['username'],
:password => ENV['password'], :password => ENV['password'],
:delete_unprocessed => ENV['delete_unprocessed']} :delete_unprocessed => ENV['delete_unprocessed']}
options = { :issue => {} } options = { :issue => {} }
%w(project status tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] } %w(project status tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] }
options[:allow_override] = ENV['allow_override'] if ENV['allow_override'] options[:allow_override] = ENV['allow_override'] if ENV['allow_override']
options[:unknown_user] = ENV['unknown_user'] if ENV['unknown_user'] options[:unknown_user] = ENV['unknown_user'] if ENV['unknown_user']
options[:no_permission_check] = ENV['no_permission_check'] if ENV['no_permission_check'] options[:no_permission_check] = ENV['no_permission_check'] if ENV['no_permission_check']
Redmine::POP3.check(pop_options, options) Redmine::POP3.check(pop_options, options)
end end
desc "Send a test email to the user with the provided login name" desc "Send a test email to the user with the provided login name"
task :test, [:login] => :environment do |task, args| task :test, [:login] => :environment do |task, args|
include Redmine::I18n include Redmine::I18n
abort l(:notice_email_error, "Please include the user login to test with. Example: login=example-login") if args[:login].blank? abort l(:notice_email_error, "Please include the user login to test with. Example: login=example-login") if args[:login].blank?
user = User.find_by_login(args[:login]) user = User.find_by_login(args[:login])
abort l(:notice_email_error, "User #{args[:login]} not found") unless user && user.logged? abort l(:notice_email_error, "User #{args[:login]} not found") unless user && user.logged?
ActionMailer::Base.raise_delivery_errors = true ActionMailer::Base.raise_delivery_errors = true
begin begin
Mailer.deliver_test(User.current) Mailer.deliver_test(User.current)
puts l(:notice_email_sent, user.mail) puts l(:notice_email_sent, user.mail)
rescue Exception => e rescue Exception => e
abort l(:notice_email_error, e.message) abort l(:notice_email_error, e.message)
end end
end end
end end
end end

View File

@ -1,24 +1,24 @@
desc 'Create YAML test fixtures from data in an existing database. desc 'Create YAML test fixtures from data in an existing database.
Defaults to development database. Set RAILS_ENV to override.' Defaults to development database. Set RAILS_ENV to override.'
task :extract_fixtures => :environment do task :extract_fixtures => :environment do
sql = "SELECT * FROM %s" sql = "SELECT * FROM %s"
skip_tables = ["schema_info"] skip_tables = ["schema_info"]
ActiveRecord::Base.establish_connection ActiveRecord::Base.establish_connection
(ActiveRecord::Base.connection.tables - skip_tables).each do |table_name| (ActiveRecord::Base.connection.tables - skip_tables).each do |table_name|
i = "000" i = "000"
File.open("#{RAILS_ROOT}/#{table_name}.yml", 'w' ) do |file| File.open("#{RAILS_ROOT}/#{table_name}.yml", 'w' ) do |file|
data = ActiveRecord::Base.connection.select_all(sql % table_name) data = ActiveRecord::Base.connection.select_all(sql % table_name)
file.write data.inject({}) { |hash, record| file.write data.inject({}) { |hash, record|
# cast extracted values # cast extracted values
ActiveRecord::Base.connection.columns(table_name).each { |col| ActiveRecord::Base.connection.columns(table_name).each { |col|
record[col.name] = col.type_cast(record[col.name]) if record[col.name] record[col.name] = col.type_cast(record[col.name]) if record[col.name]
} }
hash["#{table_name}_#{i.succ!}"] = record hash["#{table_name}_#{i.succ!}"] = record
hash hash
}.to_yaml }.to_yaml
end end
end end
end end

View File

@ -1,24 +1,24 @@
# redMine - project management software # redMine - project management software
# Copyright (C) 2006-2008 Jean-Philippe Lang # Copyright (C) 2006-2008 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2 # as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version. # of the License, or (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
desc 'Fetch changesets from the repositories' desc 'Fetch changesets from the repositories'
namespace :redmine do namespace :redmine do
task :fetch_changesets => :environment do task :fetch_changesets => :environment do
Repository.fetch_changesets Repository.fetch_changesets
end end
end end

View File

@ -1,35 +1,35 @@
desc 'Load Redmine default configuration data. Language is chosen interactively or by setting REDMINE_LANG environment variable.' desc 'Load Redmine default configuration data. Language is chosen interactively or by setting REDMINE_LANG environment variable.'
namespace :redmine do namespace :redmine do
task :load_default_data => :environment do task :load_default_data => :environment do
include Redmine::I18n include Redmine::I18n
set_language_if_valid('en') set_language_if_valid('en')
envlang = ENV['REDMINE_LANG'] envlang = ENV['REDMINE_LANG']
if !envlang || !set_language_if_valid(envlang) if !envlang || !set_language_if_valid(envlang)
puts puts
while true while true
print "Select language: " print "Select language: "
print valid_languages.collect(&:to_s).sort.join(", ") print valid_languages.collect(&:to_s).sort.join(", ")
print " [#{current_language}] " print " [#{current_language}] "
STDOUT.flush STDOUT.flush
lang = STDIN.gets.chomp! lang = STDIN.gets.chomp!
break if lang.empty? break if lang.empty?
break if set_language_if_valid(lang) break if set_language_if_valid(lang)
puts "Unknown language!" puts "Unknown language!"
end end
STDOUT.flush STDOUT.flush
puts "====================================" puts "===================================="
end end
begin begin
Redmine::DefaultData::Loader.load(current_language) Redmine::DefaultData::Loader.load(current_language)
puts "Default configuration data loaded." puts "Default configuration data loaded."
rescue Redmine::DefaultData::DataAlreadyLoaded => error rescue Redmine::DefaultData::DataAlreadyLoaded => error
puts error puts error
rescue => error rescue => error
puts "Error: " + error puts "Error: " + error
puts "Default configuration data was not loaded." puts "Default configuration data was not loaded."
end end
end end
end end

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,15 @@
namespace :db do namespace :db do
desc 'Migrates installed plugins.' desc 'Migrates installed plugins.'
task :migrate_plugins => :environment do task :migrate_plugins => :environment do
if Rails.respond_to?('plugins') if Rails.respond_to?('plugins')
Rails.plugins.each do |plugin| Rails.plugins.each do |plugin|
next unless plugin.respond_to?('migrate') next unless plugin.respond_to?('migrate')
puts "Migrating #{plugin.name}..." puts "Migrating #{plugin.name}..."
plugin.migrate plugin.migrate
end end
else else
puts "Undefined method plugins for Rails!" puts "Undefined method plugins for Rails!"
puts "Make sure engines plugin is installed." puts "Make sure engines plugin is installed."
end end
end end
end end

View File

@ -1,91 +1,91 @@
### From http://svn.geekdaily.org/public/rails/plugins/generally_useful/tasks/coverage_via_rcov.rake ### From http://svn.geekdaily.org/public/rails/plugins/generally_useful/tasks/coverage_via_rcov.rake
namespace :test do namespace :test do
desc 'Measures test coverage' desc 'Measures test coverage'
task :coverage do task :coverage do
rm_f "coverage" rm_f "coverage"
rm_f "coverage.data" rm_f "coverage.data"
rcov = "rcov --rails --aggregate coverage.data --text-summary -Ilib --html" rcov = "rcov --rails --aggregate coverage.data --text-summary -Ilib --html"
files = Dir.glob("test/**/*_test.rb").join(" ") files = Dir.glob("test/**/*_test.rb").join(" ")
system("#{rcov} #{files}") system("#{rcov} #{files}")
system("open coverage/index.html") if PLATFORM['darwin'] system("open coverage/index.html") if PLATFORM['darwin']
end end
desc 'Run unit and functional scm tests' desc 'Run unit and functional scm tests'
task :scm do task :scm do
errors = %w(test:scm:units test:scm:functionals).collect do |task| errors = %w(test:scm:units test:scm:functionals).collect do |task|
begin begin
Rake::Task[task].invoke Rake::Task[task].invoke
nil nil
rescue => e rescue => e
task task
end end
end.compact end.compact
abort "Errors running #{errors.to_sentence(:locale => :en)}!" if errors.any? abort "Errors running #{errors.to_sentence(:locale => :en)}!" if errors.any?
end end
namespace :scm do namespace :scm do
namespace :setup do namespace :setup do
desc "Creates directory for test repositories" desc "Creates directory for test repositories"
task :create_dir do task :create_dir do
FileUtils.mkdir_p Rails.root + '/tmp/test' FileUtils.mkdir_p Rails.root + '/tmp/test'
end end
supported_scms = [:subversion, :cvs, :bazaar, :mercurial, :git, :darcs, :filesystem] supported_scms = [:subversion, :cvs, :bazaar, :mercurial, :git, :darcs, :filesystem]
desc "Creates a test subversion repository" desc "Creates a test subversion repository"
task :subversion => :create_dir do task :subversion => :create_dir do
repo_path = "tmp/test/subversion_repository" repo_path = "tmp/test/subversion_repository"
system "svnadmin create #{repo_path}" system "svnadmin create #{repo_path}"
system "gunzip < test/fixtures/repositories/subversion_repository.dump.gz | svnadmin load #{repo_path}" system "gunzip < test/fixtures/repositories/subversion_repository.dump.gz | svnadmin load #{repo_path}"
end end
desc "Creates a test mercurial repository" desc "Creates a test mercurial repository"
task :mercurial => :create_dir do task :mercurial => :create_dir do
repo_path = "tmp/test/mercurial_repository" repo_path = "tmp/test/mercurial_repository"
bundle_path = "test/fixtures/repositories/mercurial_repository.hg" bundle_path = "test/fixtures/repositories/mercurial_repository.hg"
system "hg init #{repo_path}" system "hg init #{repo_path}"
system "hg -R #{repo_path} pull #{bundle_path}" system "hg -R #{repo_path} pull #{bundle_path}"
end end
(supported_scms - [:subversion, :mercurial]).each do |scm| (supported_scms - [:subversion, :mercurial]).each do |scm|
desc "Creates a test #{scm} repository" desc "Creates a test #{scm} repository"
task scm => :create_dir do task scm => :create_dir do
# system "gunzip < test/fixtures/repositories/#{scm}_repository.tar.gz | tar -xv -C tmp/test" # system "gunzip < test/fixtures/repositories/#{scm}_repository.tar.gz | tar -xv -C tmp/test"
system "tar -xvz -C tmp/test -f test/fixtures/repositories/#{scm}_repository.tar.gz" system "tar -xvz -C tmp/test -f test/fixtures/repositories/#{scm}_repository.tar.gz"
end end
end end
desc "Creates all test repositories" desc "Creates all test repositories"
task :all => supported_scms task :all => supported_scms
end end
desc "Updates installed test repositories" desc "Updates installed test repositories"
task :update do task :update do
require 'fileutils' require 'fileutils'
Dir.glob("tmp/test/*_repository").each do |dir| Dir.glob("tmp/test/*_repository").each do |dir|
next unless File.basename(dir) =~ %r{^(.+)_repository$} && File.directory?(dir) next unless File.basename(dir) =~ %r{^(.+)_repository$} && File.directory?(dir)
scm = $1 scm = $1
next unless fixture = Dir.glob("test/fixtures/repositories/#{scm}_repository.*").first next unless fixture = Dir.glob("test/fixtures/repositories/#{scm}_repository.*").first
next if File.stat(dir).ctime > File.stat(fixture).mtime next if File.stat(dir).ctime > File.stat(fixture).mtime
FileUtils.rm_rf dir FileUtils.rm_rf dir
Rake::Task["test:scm:setup:#{scm}"].execute Rake::Task["test:scm:setup:#{scm}"].execute
end end
end end
Rake::TestTask.new(:units => "db:test:prepare") do |t| Rake::TestTask.new(:units => "db:test:prepare") do |t|
t.libs << "test" t.libs << "test"
t.verbose = true t.verbose = true
t.test_files = FileList['test/unit/repository*_test.rb'] + FileList['test/unit/lib/redmine/scm/**/*_test.rb'] t.test_files = FileList['test/unit/repository*_test.rb'] + FileList['test/unit/lib/redmine/scm/**/*_test.rb']
end end
Rake::Task['test:scm:units'].comment = "Run the scm unit tests" Rake::Task['test:scm:units'].comment = "Run the scm unit tests"
Rake::TestTask.new(:functionals => "db:test:prepare") do |t| Rake::TestTask.new(:functionals => "db:test:prepare") do |t|
t.libs << "test" t.libs << "test"
t.verbose = true t.verbose = true
t.test_files = FileList['test/functional/repositories*_test.rb'] t.test_files = FileList['test/functional/repositories*_test.rb']
end end
Rake::Task['test:scm:functionals'].comment = "Run the scm functional tests" Rake::Task['test:scm:functionals'].comment = "Run the scm functional tests"
end end
end end

View File

@ -1,11 +1,11 @@
class Version < ActiveRecord::Base class Version < ActiveRecord::Base
generator_for :name, :method => :next_name generator_for :name, :method => :next_name
generator_for :status => 'open' generator_for :status => 'open'
def self.next_name def self.next_name
@last_name ||= 'Version 1.0.0' @last_name ||= 'Version 1.0.0'
@last_name.succ! @last_name.succ!
@last_name @last_name
end end
end end

View File

@ -1,48 +1,48 @@
Return-Path: <JSmith@somenet.foo> Return-Path: <JSmith@somenet.foo>
Received: from osiris ([127.0.0.1]) Received: from osiris ([127.0.0.1])
by OSIRIS by OSIRIS
with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200 with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200
Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris> Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris>
In-Reply-To: <chiliproject.issue-2.20060719210421@osiris> In-Reply-To: <chiliproject.issue-2.20060719210421@osiris>
From: "John Smith" <JSmith@somenet.foo> From: "John Smith" <JSmith@somenet.foo>
To: <redmine@somenet.foo> To: <redmine@somenet.foo>
Subject: Re: update to issue 2 Subject: Re: update to issue 2
Date: Sun, 22 Jun 2008 12:28:07 +0200 Date: Sun, 22 Jun 2008 12:28:07 +0200
MIME-Version: 1.0 MIME-Version: 1.0
Content-Type: text/plain; Content-Type: text/plain;
format=flowed; format=flowed;
charset="iso-8859-1"; charset="iso-8859-1";
reply-type=original reply-type=original
Content-Transfer-Encoding: 7bit Content-Transfer-Encoding: 7bit
X-Priority: 3 X-Priority: 3
X-MSMail-Priority: Normal X-MSMail-Priority: Normal
X-Mailer: Microsoft Outlook Express 6.00.2900.2869 X-Mailer: Microsoft Outlook Express 6.00.2900.2869
X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869 X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869
An update to the issue by the sender. An update to the issue by the sender.
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas imperdiet Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas imperdiet
turpis et odio. Integer eget pede vel dolor euismod varius. Phasellus turpis et odio. Integer eget pede vel dolor euismod varius. Phasellus
blandit eleifend augue. Nulla facilisi. Duis id diam. Class aptent taciti blandit eleifend augue. Nulla facilisi. Duis id diam. Class aptent taciti
sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In
in urna sed tellus aliquet lobortis. Morbi scelerisque tortor in dolor. Cras in urna sed tellus aliquet lobortis. Morbi scelerisque tortor in dolor. Cras
sagittis odio eu lacus. Aliquam sem tortor, consequat sit amet, vestibulum sagittis odio eu lacus. Aliquam sem tortor, consequat sit amet, vestibulum
id, iaculis at, lectus. Fusce tortor libero, congue ut, euismod nec, luctus id, iaculis at, lectus. Fusce tortor libero, congue ut, euismod nec, luctus
eget, eros. Pellentesque tortor enim, feugiat in, dignissim eget, tristique eget, eros. Pellentesque tortor enim, feugiat in, dignissim eget, tristique
sed, mauris --- Pellentesque habitant morbi tristique senectus et netus et sed, mauris --- Pellentesque habitant morbi tristique senectus et netus et
malesuada fames ac turpis egestas. Quisque sit amet libero. In hac habitasse malesuada fames ac turpis egestas. Quisque sit amet libero. In hac habitasse
platea dictumst. platea dictumst.
>> > --- Reply above. Do not remove this line. --- >> > --- Reply above. Do not remove this line. ---
>> > >> >
>> > Issue #6779 has been updated by Eric Davis. >> > Issue #6779 has been updated by Eric Davis.
>> > >> >
>> > Subject changed from Projects with JSON to Project JSON API >> > Subject changed from Projects with JSON to Project JSON API
>> > Status changed from New to Assigned >> > Status changed from New to Assigned
>> > Assignee set to Eric Davis >> > Assignee set to Eric Davis
>> > Priority changed from Low to Normal >> > Priority changed from Low to Normal
>> > Estimated time deleted (1.00) >> > Estimated time deleted (1.00)
>> > >> >
>> > Looks like the JSON api for projects was missed. I'm going to be >> > Looks like the JSON api for projects was missed. I'm going to be
>> > reviewing the existing APIs and trying to clean them up over the next >> > reviewing the existing APIs and trying to clean them up over the next
>> > few weeks. >> > few weeks.

View File

@ -1,48 +1,48 @@
Return-Path: <JSmith@somenet.foo> Return-Path: <JSmith@somenet.foo>
Received: from osiris ([127.0.0.1]) Received: from osiris ([127.0.0.1])
by OSIRIS by OSIRIS
with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200 with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200
Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris> Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris>
In-Reply-To: <chiliproject.issue-2.20060719210421@osiris> In-Reply-To: <chiliproject.issue-2.20060719210421@osiris>
From: "John Smith" <JSmith@somenet.foo> From: "John Smith" <JSmith@somenet.foo>
To: <redmine@somenet.foo> To: <redmine@somenet.foo>
Subject: Re: update to issue 2 Subject: Re: update to issue 2
Date: Sun, 22 Jun 2008 12:28:07 +0200 Date: Sun, 22 Jun 2008 12:28:07 +0200
MIME-Version: 1.0 MIME-Version: 1.0
Content-Type: text/plain; Content-Type: text/plain;
format=flowed; format=flowed;
charset="iso-8859-1"; charset="iso-8859-1";
reply-type=original reply-type=original
Content-Transfer-Encoding: 7bit Content-Transfer-Encoding: 7bit
X-Priority: 3 X-Priority: 3
X-MSMail-Priority: Normal X-MSMail-Priority: Normal
X-Mailer: Microsoft Outlook Express 6.00.2900.2869 X-Mailer: Microsoft Outlook Express 6.00.2900.2869
X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869 X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869
An update to the issue by the sender. An update to the issue by the sender.
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas imperdiet Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas imperdiet
turpis et odio. Integer eget pede vel dolor euismod varius. Phasellus turpis et odio. Integer eget pede vel dolor euismod varius. Phasellus
blandit eleifend augue. Nulla facilisi. Duis id diam. Class aptent taciti blandit eleifend augue. Nulla facilisi. Duis id diam. Class aptent taciti
sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In
in urna sed tellus aliquet lobortis. Morbi scelerisque tortor in dolor. Cras in urna sed tellus aliquet lobortis. Morbi scelerisque tortor in dolor. Cras
sagittis odio eu lacus. Aliquam sem tortor, consequat sit amet, vestibulum sagittis odio eu lacus. Aliquam sem tortor, consequat sit amet, vestibulum
id, iaculis at, lectus. Fusce tortor libero, congue ut, euismod nec, luctus id, iaculis at, lectus. Fusce tortor libero, congue ut, euismod nec, luctus
eget, eros. Pellentesque tortor enim, feugiat in, dignissim eget, tristique eget, eros. Pellentesque tortor enim, feugiat in, dignissim eget, tristique
sed, mauris --- Pellentesque habitant morbi tristique senectus et netus et sed, mauris --- Pellentesque habitant morbi tristique senectus et netus et
malesuada fames ac turpis egestas. Quisque sit amet libero. In hac habitasse malesuada fames ac turpis egestas. Quisque sit amet libero. In hac habitasse
platea dictumst. platea dictumst.
> --- Reply above. Do not remove this line. --- > --- Reply above. Do not remove this line. ---
> >
> Issue #6779 has been updated by Eric Davis. > Issue #6779 has been updated by Eric Davis.
> >
> Subject changed from Projects with JSON to Project JSON API > Subject changed from Projects with JSON to Project JSON API
> Status changed from New to Assigned > Status changed from New to Assigned
> Assignee set to Eric Davis > Assignee set to Eric Davis
> Priority changed from Low to Normal > Priority changed from Low to Normal
> Estimated time deleted (1.00) > Estimated time deleted (1.00)
> >
> Looks like the JSON api for projects was missed. I'm going to be > Looks like the JSON api for projects was missed. I'm going to be
> reviewing the existing APIs and trying to clean them up over the next > reviewing the existing APIs and trying to clean them up over the next
> few weeks. > few weeks.

View File

@ -1,17 +1,17 @@
Return-Path: <john.doe@somenet.foo> Return-Path: <john.doe@somenet.foo>
Received: from osiris ([127.0.0.1]) Received: from osiris ([127.0.0.1])
by OSIRIS by OSIRIS
with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200 with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200
Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris> Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris>
To: <redmine@somenet.foo> To: <redmine@somenet.foo>
Subject: Ticket by unknown user Subject: Ticket by unknown user
Date: Sun, 22 Jun 2008 12:28:07 +0200 Date: Sun, 22 Jun 2008 12:28:07 +0200
MIME-Version: 1.0 MIME-Version: 1.0
Content-Type: text/plain; Content-Type: text/plain;
format=flowed; format=flowed;
charset="iso-8859-1"; charset="iso-8859-1";
reply-type=original reply-type=original
Content-Transfer-Encoding: 7bit Content-Transfer-Encoding: 7bit
This is a ticket submitted by an unknown user. This is a ticket submitted by an unknown user.

View File

@ -1,18 +1,18 @@
Return-Path: <john.doe@somenet.foo> Return-Path: <john.doe@somenet.foo>
Received: from osiris ([127.0.0.1]) Received: from osiris ([127.0.0.1])
by OSIRIS by OSIRIS
with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200 with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200
Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris> Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris>
From: "John Doe" <john.doe@somenet.foo> From: "John Doe" <john.doe@somenet.foo>
To: <redmine@somenet.foo> To: <redmine@somenet.foo>
Subject: Ticket by unknown user Subject: Ticket by unknown user
Date: Sun, 22 Jun 2008 12:28:07 +0200 Date: Sun, 22 Jun 2008 12:28:07 +0200
MIME-Version: 1.0 MIME-Version: 1.0
Content-Type: text/plain; Content-Type: text/plain;
format=flowed; format=flowed;
charset="iso-8859-1"; charset="iso-8859-1";
reply-type=original reply-type=original
Content-Transfer-Encoding: 7bit Content-Transfer-Encoding: 7bit
This is a ticket submitted by an unknown user. This is a ticket submitted by an unknown user.

View File

@ -1,60 +1,60 @@
Return-Path: <JSmith@somenet.foo> Return-Path: <JSmith@somenet.foo>
Received: from osiris ([127.0.0.1]) Received: from osiris ([127.0.0.1])
by OSIRIS by OSIRIS
with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200 with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200
Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris> Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris>
From: "John Smith" <JSmith@somenet.foo> From: "John Smith" <JSmith@somenet.foo>
To: <redmine@somenet.foo> To: <redmine@somenet.foo>
Subject: New ticket on a given project Subject: New ticket on a given project
Date: Sun, 22 Jun 2008 12:28:07 +0200 Date: Sun, 22 Jun 2008 12:28:07 +0200
MIME-Version: 1.0 MIME-Version: 1.0
Content-Type: text/plain; Content-Type: text/plain;
format=flowed; format=flowed;
charset="iso-8859-1"; charset="iso-8859-1";
reply-type=original reply-type=original
Content-Transfer-Encoding: 7bit Content-Transfer-Encoding: 7bit
X-Priority: 3 X-Priority: 3
X-MSMail-Priority: Normal X-MSMail-Priority: Normal
X-Mailer: Microsoft Outlook Express 6.00.2900.2869 X-Mailer: Microsoft Outlook Express 6.00.2900.2869
X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869 X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas imperdiet Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas imperdiet
turpis et odio. Integer eget pede vel dolor euismod varius. Phasellus turpis et odio. Integer eget pede vel dolor euismod varius. Phasellus
blandit eleifend augue. Nulla facilisi. Duis id diam. Class aptent taciti blandit eleifend augue. Nulla facilisi. Duis id diam. Class aptent taciti
sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In
in urna sed tellus aliquet lobortis. Morbi scelerisque tortor in dolor. Cras in urna sed tellus aliquet lobortis. Morbi scelerisque tortor in dolor. Cras
sagittis odio eu lacus. Aliquam sem tortor, consequat sit amet, vestibulum sagittis odio eu lacus. Aliquam sem tortor, consequat sit amet, vestibulum
id, iaculis at, lectus. Fusce tortor libero, congue ut, euismod nec, luctus id, iaculis at, lectus. Fusce tortor libero, congue ut, euismod nec, luctus
eget, eros. Pellentesque tortor enim, feugiat in, dignissim eget, tristique eget, eros. Pellentesque tortor enim, feugiat in, dignissim eget, tristique
sed, mauris --- Pellentesque habitant morbi tristique senectus et netus et sed, mauris --- Pellentesque habitant morbi tristique senectus et netus et
malesuada fames ac turpis egestas. Quisque sit amet libero. In hac habitasse malesuada fames ac turpis egestas. Quisque sit amet libero. In hac habitasse
platea dictumst. platea dictumst.
--- This line starts with a delimiter and should not be stripped --- This line starts with a delimiter and should not be stripped
This paragraph is before delimiters. This paragraph is before delimiters.
BREAK BREAK
This paragraph is between delimiters. This paragraph is between delimiters.
--- ---
This paragraph is after the delimiter so it shouldn't appear. This paragraph is after the delimiter so it shouldn't appear.
Nulla et nunc. Duis pede. Donec et ipsum. Nam ut dui tincidunt neque Nulla et nunc. Duis pede. Donec et ipsum. Nam ut dui tincidunt neque
sollicitudin iaculis. Duis vitae dolor. Vestibulum eget massa. Sed lorem. sollicitudin iaculis. Duis vitae dolor. Vestibulum eget massa. Sed lorem.
Nullam volutpat cursus erat. Cras felis dolor, lacinia quis, rutrum et, Nullam volutpat cursus erat. Cras felis dolor, lacinia quis, rutrum et,
dictum et, ligula. Sed erat nibh, gravida in, accumsan non, placerat sed, dictum et, ligula. Sed erat nibh, gravida in, accumsan non, placerat sed,
massa. Sed sodales, ante fermentum ultricies sollicitudin, massa leo massa. Sed sodales, ante fermentum ultricies sollicitudin, massa leo
pulvinar dui, a gravida orci mi eget odio. Nunc a lacus. pulvinar dui, a gravida orci mi eget odio. Nunc a lacus.
Project: onlinestore Project: onlinestore
Status: Resolved Status: Resolved
due date: 2010-12-31 due date: 2010-12-31
Start Date:2010-01-01 Start Date:2010-01-01
Assigned to: John Smith Assigned to: John Smith
fixed version: alpha fixed version: alpha
estimated hours: 2.5 estimated hours: 2.5
done ratio: 30 done ratio: 30

View File

@ -1,74 +1,74 @@
Return-Path: <jsmith@somenet.foo> Return-Path: <jsmith@somenet.foo>
Received: from osiris ([127.0.0.1]) Received: from osiris ([127.0.0.1])
by OSIRIS by OSIRIS
with hMailServer ; Sat, 21 Jun 2008 18:41:39 +0200 with hMailServer ; Sat, 21 Jun 2008 18:41:39 +0200
Message-ID: <006a01c8d3bd$ad9baec0$0a00a8c0@osiris> Message-ID: <006a01c8d3bd$ad9baec0$0a00a8c0@osiris>
In-Reply-To: <chiliproject.issue-2.20060719210421@osiris> In-Reply-To: <chiliproject.issue-2.20060719210421@osiris>
From: "John Smith" <jsmith@somenet.foo> From: "John Smith" <jsmith@somenet.foo>
To: <redmine@somenet.foo> To: <redmine@somenet.foo>
References: <485d0ad366c88_d7014663a025f@osiris.tmail> References: <485d0ad366c88_d7014663a025f@osiris.tmail>
Subject: Re: Add ingredients categories Subject: Re: Add ingredients categories
Date: Sat, 21 Jun 2008 18:41:39 +0200 Date: Sat, 21 Jun 2008 18:41:39 +0200
MIME-Version: 1.0 MIME-Version: 1.0
Content-Type: multipart/alternative; Content-Type: multipart/alternative;
boundary="----=_NextPart_000_0067_01C8D3CE.711F9CC0" boundary="----=_NextPart_000_0067_01C8D3CE.711F9CC0"
X-Priority: 3 X-Priority: 3
X-MSMail-Priority: Normal X-MSMail-Priority: Normal
X-Mailer: Microsoft Outlook Express 6.00.2900.2869 X-Mailer: Microsoft Outlook Express 6.00.2900.2869
X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869 X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869
This is a multi-part message in MIME format. This is a multi-part message in MIME format.
------=_NextPart_000_0067_01C8D3CE.711F9CC0 ------=_NextPart_000_0067_01C8D3CE.711F9CC0
Content-Type: text/plain; Content-Type: text/plain;
charset="utf-8" charset="utf-8"
Content-Transfer-Encoding: quoted-printable Content-Transfer-Encoding: quoted-printable
This is reply This is reply
------=_NextPart_000_0067_01C8D3CE.711F9CC0 ------=_NextPart_000_0067_01C8D3CE.711F9CC0
Content-Type: text/html; Content-Type: text/html;
charset="utf-8" charset="utf-8"
Content-Transfer-Encoding: quoted-printable Content-Transfer-Encoding: quoted-printable
=EF=BB=BF<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> =EF=BB=BF<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML><HEAD> <HTML><HEAD>
<META http-equiv=3DContent-Type content=3D"text/html; charset=3Dutf-8"> <META http-equiv=3DContent-Type content=3D"text/html; charset=3Dutf-8">
<STYLE>BODY { <STYLE>BODY {
FONT-SIZE: 0.8em; COLOR: #484848; FONT-FAMILY: Verdana, sans-serif FONT-SIZE: 0.8em; COLOR: #484848; FONT-FAMILY: Verdana, sans-serif
} }
BODY H1 { BODY H1 {
FONT-SIZE: 1.2em; MARGIN: 0px; FONT-FAMILY: "Trebuchet MS", Verdana, = FONT-SIZE: 1.2em; MARGIN: 0px; FONT-FAMILY: "Trebuchet MS", Verdana, =
sans-serif sans-serif
} }
A { A {
COLOR: #2a5685 COLOR: #2a5685
} }
A:link { A:link {
COLOR: #2a5685 COLOR: #2a5685
} }
A:visited { A:visited {
COLOR: #2a5685 COLOR: #2a5685
} }
A:hover { A:hover {
COLOR: #c61a1a COLOR: #c61a1a
} }
A:active { A:active {
COLOR: #c61a1a COLOR: #c61a1a
} }
HR { HR {
BORDER-RIGHT: 0px; BORDER-TOP: 0px; BACKGROUND: #ccc; BORDER-LEFT: 0px; = BORDER-RIGHT: 0px; BORDER-TOP: 0px; BACKGROUND: #ccc; BORDER-LEFT: 0px; =
WIDTH: 100%; BORDER-BOTTOM: 0px; HEIGHT: 1px WIDTH: 100%; BORDER-BOTTOM: 0px; HEIGHT: 1px
} }
.footer { .footer {
FONT-SIZE: 0.8em; FONT-STYLE: italic FONT-SIZE: 0.8em; FONT-STYLE: italic
} }
</STYLE> </STYLE>
<META content=3D"MSHTML 6.00.2900.2883" name=3DGENERATOR></HEAD> <META content=3D"MSHTML 6.00.2900.2883" name=3DGENERATOR></HEAD>
<BODY bgColor=3D#ffffff> <BODY bgColor=3D#ffffff>
<DIV><SPAN class=3Dfooter><FONT face=3DArial color=3D#000000 = <DIV><SPAN class=3Dfooter><FONT face=3DArial color=3D#000000 =
size=3D2>This is=20 size=3D2>This is=20
reply</FONT></DIV></SPAN></BODY></HTML> reply</FONT></DIV></SPAN></BODY></HTML>
------=_NextPart_000_0067_01C8D3CE.711F9CC0-- ------=_NextPart_000_0067_01C8D3CE.711F9CC0--

View File

@ -1,248 +1,248 @@
Return-Path: <jsmith@somenet.foo> Return-Path: <jsmith@somenet.foo>
Received: from osiris ([127.0.0.1]) Received: from osiris ([127.0.0.1])
by OSIRIS by OSIRIS
with hMailServer ; Sat, 21 Jun 2008 15:53:25 +0200 with hMailServer ; Sat, 21 Jun 2008 15:53:25 +0200
Message-ID: <002301c8d3a6$2cdf6950$0a00a8c0@osiris> Message-ID: <002301c8d3a6$2cdf6950$0a00a8c0@osiris>
From: "John Smith" <jsmith@somenet.foo> From: "John Smith" <jsmith@somenet.foo>
To: <redmine@somenet.foo> To: <redmine@somenet.foo>
Subject: Ticket created by email with attachment Subject: Ticket created by email with attachment
Date: Sat, 21 Jun 2008 15:53:25 +0200 Date: Sat, 21 Jun 2008 15:53:25 +0200
MIME-Version: 1.0 MIME-Version: 1.0
Content-Type: multipart/mixed; Content-Type: multipart/mixed;
boundary="----=_NextPart_000_001F_01C8D3B6.F05C5270" boundary="----=_NextPart_000_001F_01C8D3B6.F05C5270"
X-Priority: 3 X-Priority: 3
X-MSMail-Priority: Normal X-MSMail-Priority: Normal
X-Mailer: Microsoft Outlook Express 6.00.2900.2869 X-Mailer: Microsoft Outlook Express 6.00.2900.2869
X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869 X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869
This is a multi-part message in MIME format. This is a multi-part message in MIME format.
------=_NextPart_000_001F_01C8D3B6.F05C5270 ------=_NextPart_000_001F_01C8D3B6.F05C5270
Content-Type: multipart/alternative; Content-Type: multipart/alternative;
boundary="----=_NextPart_001_0020_01C8D3B6.F05C5270" boundary="----=_NextPart_001_0020_01C8D3B6.F05C5270"
------=_NextPart_001_0020_01C8D3B6.F05C5270 ------=_NextPart_001_0020_01C8D3B6.F05C5270
Content-Type: text/plain; Content-Type: text/plain;
charset="iso-8859-1" charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable Content-Transfer-Encoding: quoted-printable
This is a new ticket with attachments This is a new ticket with attachments
------=_NextPart_001_0020_01C8D3B6.F05C5270 ------=_NextPart_001_0020_01C8D3B6.F05C5270
Content-Type: text/html; Content-Type: text/html;
charset="iso-8859-1" charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable Content-Transfer-Encoding: quoted-printable
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML><HEAD> <HTML><HEAD>
<META http-equiv=3DContent-Type content=3D"text/html; = <META http-equiv=3DContent-Type content=3D"text/html; =
charset=3Diso-8859-1"> charset=3Diso-8859-1">
<META content=3D"MSHTML 6.00.2900.2883" name=3DGENERATOR> <META content=3D"MSHTML 6.00.2900.2883" name=3DGENERATOR>
<STYLE></STYLE> <STYLE></STYLE>
</HEAD> </HEAD>
<BODY bgColor=3D#ffffff> <BODY bgColor=3D#ffffff>
<DIV><FONT face=3DArial size=3D2>This is&nbsp; a new ticket with=20 <DIV><FONT face=3DArial size=3D2>This is&nbsp; a new ticket with=20
attachments</FONT></DIV></BODY></HTML> attachments</FONT></DIV></BODY></HTML>
------=_NextPart_001_0020_01C8D3B6.F05C5270-- ------=_NextPart_001_0020_01C8D3B6.F05C5270--
------=_NextPart_000_001F_01C8D3B6.F05C5270 ------=_NextPart_000_001F_01C8D3B6.F05C5270
Content-Type: image/jpeg; Content-Type: image/jpeg;
name="Paella.jpg" name="Paella.jpg"
Content-Transfer-Encoding: base64 Content-Transfer-Encoding: base64
Content-Disposition: attachment; Content-Disposition: attachment;
filename="Paella.jpg" filename="Paella.jpg"
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
cbis+/WpUqUcMZKdF44n/9k= cbis+/WpUqUcMZKdF44n/9k=
------=_NextPart_000_001F_01C8D3B6.F05C5270-- ------=_NextPart_000_001F_01C8D3B6.F05C5270--

View File

@ -1,47 +1,47 @@
Return-Path: <jsmith@somenet.foo> Return-Path: <jsmith@somenet.foo>
Received: from osiris ([127.0.0.1]) Received: from osiris ([127.0.0.1])
by OSIRIS by OSIRIS
with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200 with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200
Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris> Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris>
From: "John Smith" <jsmith@somenet.foo> From: "John Smith" <jsmith@somenet.foo>
To: <redmine@somenet.foo> To: <redmine@somenet.foo>
Subject: New ticket on a given project Subject: New ticket on a given project
Date: Sun, 22 Jun 2008 12:28:07 +0200 Date: Sun, 22 Jun 2008 12:28:07 +0200
MIME-Version: 1.0 MIME-Version: 1.0
Content-Type: text/plain; Content-Type: text/plain;
format=flowed; format=flowed;
charset="iso-8859-1"; charset="iso-8859-1";
reply-type=original reply-type=original
Content-Transfer-Encoding: 7bit Content-Transfer-Encoding: 7bit
X-Priority: 3 X-Priority: 3
X-MSMail-Priority: Normal X-MSMail-Priority: Normal
X-Mailer: Microsoft Outlook Express 6.00.2900.2869 X-Mailer: Microsoft Outlook Express 6.00.2900.2869
X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869 X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas imperdiet Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas imperdiet
turpis et odio. Integer eget pede vel dolor euismod varius. Phasellus turpis et odio. Integer eget pede vel dolor euismod varius. Phasellus
blandit eleifend augue. Nulla facilisi. Duis id diam. Class aptent taciti blandit eleifend augue. Nulla facilisi. Duis id diam. Class aptent taciti
sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In
in urna sed tellus aliquet lobortis. Morbi scelerisque tortor in dolor. Cras in urna sed tellus aliquet lobortis. Morbi scelerisque tortor in dolor. Cras
sagittis odio eu lacus. Aliquam sem tortor, consequat sit amet, vestibulum sagittis odio eu lacus. Aliquam sem tortor, consequat sit amet, vestibulum
id, iaculis at, lectus. Fusce tortor libero, congue ut, euismod nec, luctus id, iaculis at, lectus. Fusce tortor libero, congue ut, euismod nec, luctus
eget, eros. Pellentesque tortor enim, feugiat in, dignissim eget, tristique eget, eros. Pellentesque tortor enim, feugiat in, dignissim eget, tristique
sed, mauris. Pellentesque habitant morbi tristique senectus et netus et sed, mauris. Pellentesque habitant morbi tristique senectus et netus et
malesuada fames ac turpis egestas. Quisque sit amet libero. In hac habitasse malesuada fames ac turpis egestas. Quisque sit amet libero. In hac habitasse
platea dictumst. platea dictumst.
Nulla et nunc. Duis pede. Donec et ipsum. Nam ut dui tincidunt neque Nulla et nunc. Duis pede. Donec et ipsum. Nam ut dui tincidunt neque
sollicitudin iaculis. Duis vitae dolor. Vestibulum eget massa. Sed lorem. sollicitudin iaculis. Duis vitae dolor. Vestibulum eget massa. Sed lorem.
Nullam volutpat cursus erat. Cras felis dolor, lacinia quis, rutrum et, Nullam volutpat cursus erat. Cras felis dolor, lacinia quis, rutrum et,
dictum et, ligula. Sed erat nibh, gravida in, accumsan non, placerat sed, dictum et, ligula. Sed erat nibh, gravida in, accumsan non, placerat sed,
massa. Sed sodales, ante fermentum ultricies sollicitudin, massa leo massa. Sed sodales, ante fermentum ultricies sollicitudin, massa leo
pulvinar dui, a gravida orci mi eget odio. Nunc a lacus. pulvinar dui, a gravida orci mi eget odio. Nunc a lacus.
Project: onlinestore Project: onlinestore
Tracker: Feature request Tracker: Feature request
category: Stock management category: Stock management
assigned to: miscuser9@foo.bar assigned to: miscuser9@foo.bar
priority: foo priority: foo
done ratio: x done ratio: x
start date: some day start date: some day
due date: never due date: never

View File

@ -1,43 +1,43 @@
Return-Path: <jsmith@somenet.foo> Return-Path: <jsmith@somenet.foo>
Received: from osiris ([127.0.0.1]) Received: from osiris ([127.0.0.1])
by OSIRIS by OSIRIS
with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200 with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200
Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris> Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris>
From: "John Smith" <jsmith@somenet.foo> From: "John Smith" <jsmith@somenet.foo>
To: <redmine@somenet.foo> To: <redmine@somenet.foo>
Subject: New ticket on a given project Subject: New ticket on a given project
Date: Sun, 22 Jun 2008 12:28:07 +0200 Date: Sun, 22 Jun 2008 12:28:07 +0200
MIME-Version: 1.0 MIME-Version: 1.0
Content-Type: text/plain; Content-Type: text/plain;
format=flowed; format=flowed;
charset="iso-8859-1"; charset="iso-8859-1";
reply-type=original reply-type=original
Content-Transfer-Encoding: 7bit Content-Transfer-Encoding: 7bit
X-Priority: 3 X-Priority: 3
X-MSMail-Priority: Normal X-MSMail-Priority: Normal
X-Mailer: Microsoft Outlook Express 6.00.2900.2869 X-Mailer: Microsoft Outlook Express 6.00.2900.2869
X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869 X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas imperdiet Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas imperdiet
turpis et odio. Integer eget pede vel dolor euismod varius. Phasellus turpis et odio. Integer eget pede vel dolor euismod varius. Phasellus
blandit eleifend augue. Nulla facilisi. Duis id diam. Class aptent taciti blandit eleifend augue. Nulla facilisi. Duis id diam. Class aptent taciti
sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In
in urna sed tellus aliquet lobortis. Morbi scelerisque tortor in dolor. Cras in urna sed tellus aliquet lobortis. Morbi scelerisque tortor in dolor. Cras
sagittis odio eu lacus. Aliquam sem tortor, consequat sit amet, vestibulum sagittis odio eu lacus. Aliquam sem tortor, consequat sit amet, vestibulum
id, iaculis at, lectus. Fusce tortor libero, congue ut, euismod nec, luctus id, iaculis at, lectus. Fusce tortor libero, congue ut, euismod nec, luctus
eget, eros. Pellentesque tortor enim, feugiat in, dignissim eget, tristique eget, eros. Pellentesque tortor enim, feugiat in, dignissim eget, tristique
sed, mauris. Pellentesque habitant morbi tristique senectus et netus et sed, mauris. Pellentesque habitant morbi tristique senectus et netus et
malesuada fames ac turpis egestas. Quisque sit amet libero. In hac habitasse malesuada fames ac turpis egestas. Quisque sit amet libero. In hac habitasse
platea dictumst. platea dictumst.
Nulla et nunc. Duis pede. Donec et ipsum. Nam ut dui tincidunt neque Nulla et nunc. Duis pede. Donec et ipsum. Nam ut dui tincidunt neque
sollicitudin iaculis. Duis vitae dolor. Vestibulum eget massa. Sed lorem. sollicitudin iaculis. Duis vitae dolor. Vestibulum eget massa. Sed lorem.
Nullam volutpat cursus erat. Cras felis dolor, lacinia quis, rutrum et, Nullam volutpat cursus erat. Cras felis dolor, lacinia quis, rutrum et,
dictum et, ligula. Sed erat nibh, gravida in, accumsan non, placerat sed, dictum et, ligula. Sed erat nibh, gravida in, accumsan non, placerat sed,
massa. Sed sodales, ante fermentum ultricies sollicitudin, massa leo massa. Sed sodales, ante fermentum ultricies sollicitudin, massa leo
pulvinar dui, a gravida orci mi eget odio. Nunc a lacus. pulvinar dui, a gravida orci mi eget odio. Nunc a lacus.
Projet: onlinestore Projet: onlinestore
Tracker: Feature request Tracker: Feature request
catégorie: Stock management catégorie: Stock management
priorité: Urgent priorité: Urgent

View File

@ -1,57 +1,57 @@
Return-Path: <JSmith@somenet.foo> Return-Path: <JSmith@somenet.foo>
Received: from osiris ([127.0.0.1]) Received: from osiris ([127.0.0.1])
by OSIRIS by OSIRIS
with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200 with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200
Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris> Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris>
From: "John Smith" <JSmith@somenet.foo> From: "John Smith" <JSmith@somenet.foo>
To: <redmine@somenet.foo> To: <redmine@somenet.foo>
Subject: New ticket on a given project with a very long subject line which exceeds 255 chars and should not be ignored but chopped off. And if the subject line is still not long enough, we just add more text. And more text. Wow, this is really annoying. Especially, if you have nothing to say... Subject: New ticket on a given project with a very long subject line which exceeds 255 chars and should not be ignored but chopped off. And if the subject line is still not long enough, we just add more text. And more text. Wow, this is really annoying. Especially, if you have nothing to say...
Date: Sun, 22 Jun 2008 12:28:07 +0200 Date: Sun, 22 Jun 2008 12:28:07 +0200
MIME-Version: 1.0 MIME-Version: 1.0
Content-Type: text/plain; Content-Type: text/plain;
format=flowed; format=flowed;
charset="iso-8859-1"; charset="iso-8859-1";
reply-type=original reply-type=original
Content-Transfer-Encoding: 7bit Content-Transfer-Encoding: 7bit
X-Priority: 3 X-Priority: 3
X-MSMail-Priority: Normal X-MSMail-Priority: Normal
X-Mailer: Microsoft Outlook Express 6.00.2900.2869 X-Mailer: Microsoft Outlook Express 6.00.2900.2869
X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869 X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas imperdiet Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas imperdiet
turpis et odio. Integer eget pede vel dolor euismod varius. Phasellus turpis et odio. Integer eget pede vel dolor euismod varius. Phasellus
blandit eleifend augue. Nulla facilisi. Duis id diam. Class aptent taciti blandit eleifend augue. Nulla facilisi. Duis id diam. Class aptent taciti
sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In
in urna sed tellus aliquet lobortis. Morbi scelerisque tortor in dolor. Cras in urna sed tellus aliquet lobortis. Morbi scelerisque tortor in dolor. Cras
sagittis odio eu lacus. Aliquam sem tortor, consequat sit amet, vestibulum sagittis odio eu lacus. Aliquam sem tortor, consequat sit amet, vestibulum
id, iaculis at, lectus. Fusce tortor libero, congue ut, euismod nec, luctus id, iaculis at, lectus. Fusce tortor libero, congue ut, euismod nec, luctus
eget, eros. Pellentesque tortor enim, feugiat in, dignissim eget, tristique eget, eros. Pellentesque tortor enim, feugiat in, dignissim eget, tristique
sed, mauris --- Pellentesque habitant morbi tristique senectus et netus et sed, mauris --- Pellentesque habitant morbi tristique senectus et netus et
malesuada fames ac turpis egestas. Quisque sit amet libero. In hac habitasse malesuada fames ac turpis egestas. Quisque sit amet libero. In hac habitasse
platea dictumst. platea dictumst.
--- This line starts with a delimiter and should not be stripped --- This line starts with a delimiter and should not be stripped
This paragraph is before delimiters. This paragraph is before delimiters.
BREAK BREAK
This paragraph is between delimiters. This paragraph is between delimiters.
--- ---
This paragraph is after the delimiter so it shouldn't appear. This paragraph is after the delimiter so it shouldn't appear.
Nulla et nunc. Duis pede. Donec et ipsum. Nam ut dui tincidunt neque Nulla et nunc. Duis pede. Donec et ipsum. Nam ut dui tincidunt neque
sollicitudin iaculis. Duis vitae dolor. Vestibulum eget massa. Sed lorem. sollicitudin iaculis. Duis vitae dolor. Vestibulum eget massa. Sed lorem.
Nullam volutpat cursus erat. Cras felis dolor, lacinia quis, rutrum et, Nullam volutpat cursus erat. Cras felis dolor, lacinia quis, rutrum et,
dictum et, ligula. Sed erat nibh, gravida in, accumsan non, placerat sed, dictum et, ligula. Sed erat nibh, gravida in, accumsan non, placerat sed,
massa. Sed sodales, ante fermentum ultricies sollicitudin, massa leo massa. Sed sodales, ante fermentum ultricies sollicitudin, massa leo
pulvinar dui, a gravida orci mi eget odio. Nunc a lacus. pulvinar dui, a gravida orci mi eget odio. Nunc a lacus.
Project: onlinestore Project: onlinestore
Status: Resolved Status: Resolved
due date: 2010-12-31 due date: 2010-12-31
Start Date:2010-01-01 Start Date:2010-01-01
Assigned to: John Smith Assigned to: John Smith