omniboard 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/HISTORY.md +71 -0
- data/README.md +404 -0
- data/Rakefile +3 -0
- data/bin/omniboard +43 -0
- data/lib/omniboard.rb +25 -0
- data/lib/omniboard/colour.rb +15 -0
- data/lib/omniboard/column.rb +368 -0
- data/lib/omniboard/columns/active.rb +17 -0
- data/lib/omniboard/columns/backburner.rb +5 -0
- data/lib/omniboard/columns/completed.rb +5 -0
- data/lib/omniboard/columns/config.rb +11 -0
- data/lib/omniboard/core_ext.rb +22 -0
- data/lib/omniboard/group.rb +104 -0
- data/lib/omniboard/omniboard.rb +149 -0
- data/lib/omniboard/project_wrapper.rb +119 -0
- data/lib/omniboard/property.rb +53 -0
- data/lib/omniboard/renderer.rb +50 -0
- data/lib/omniboard/styled_text.rb +63 -0
- data/lib/omniboard/styled_text_element.rb +40 -0
- data/lib/omniboard/templates/column.erb +25 -0
- data/lib/omniboard/templates/postamble.erb +226 -0
- data/lib/omniboard/templates/preamble.erb +346 -0
- data/lib/omniboard/templates/project.erb +15 -0
- data/omniboard.gemspec +23 -0
- data/version.txt +1 -0
- metadata +100 -0
data/Rakefile
ADDED
data/bin/omniboard
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "omniboard"
|
4
|
+
|
5
|
+
# Command line options
|
6
|
+
require "trollop"
|
7
|
+
|
8
|
+
opts = Trollop::options do
|
9
|
+
opt :configure, "Location of configuration files. (Default: #{File.join(ENV["HOME"], ".omniboard")})", type: String
|
10
|
+
opt :output, "Where should the html go? If not specified, outputs to STDOUT.", type: String
|
11
|
+
opt :reset, "Wipe cache and start again from the source."
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
# Set configuration location
|
16
|
+
Omniboard::config_location = opts[:configure] if opts[:configure]
|
17
|
+
|
18
|
+
# If this is a new location, populate appropriately
|
19
|
+
Omniboard::populate if !Omniboard::config_exists?
|
20
|
+
|
21
|
+
# Create or load + update
|
22
|
+
if Omniboard::document_exists? && !opts[:reset]
|
23
|
+
Omniboard::load_document
|
24
|
+
else
|
25
|
+
Omniboard::create_document
|
26
|
+
end
|
27
|
+
|
28
|
+
Omniboard::update_document
|
29
|
+
|
30
|
+
renderer = Omniboard::Renderer.new
|
31
|
+
|
32
|
+
Omniboard::columns.each do |column|
|
33
|
+
column.add Omniboard::projects
|
34
|
+
renderer.add_column(column)
|
35
|
+
end
|
36
|
+
|
37
|
+
if opts[:output]
|
38
|
+
File.open(opts[:output], "w"){ |io| io.puts renderer.to_s }
|
39
|
+
else
|
40
|
+
puts renderer.to_s
|
41
|
+
end
|
42
|
+
|
43
|
+
Omniboard::save_document
|
data/lib/omniboard.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# Helps iron out some problems
|
2
|
+
Encoding.default_external = "UTF-8"
|
3
|
+
|
4
|
+
module Omniboard; end
|
5
|
+
|
6
|
+
# Rubyfocus library
|
7
|
+
require "rubyfocus"
|
8
|
+
|
9
|
+
# FileUtils for making directories
|
10
|
+
require "fileutils"
|
11
|
+
|
12
|
+
# ERB for rendering web
|
13
|
+
require "erb"
|
14
|
+
|
15
|
+
# Require library files
|
16
|
+
require "omniboard/core_ext"
|
17
|
+
require "omniboard/project_wrapper"
|
18
|
+
require "omniboard/property"
|
19
|
+
require "omniboard/omniboard"
|
20
|
+
require "omniboard/column"
|
21
|
+
require "omniboard/colour"
|
22
|
+
require "omniboard/group"
|
23
|
+
require "omniboard/renderer"
|
24
|
+
require "omniboard/styled_text_element"
|
25
|
+
require "omniboard/styled_text"
|
@@ -0,0 +1,368 @@
|
|
1
|
+
# The column. Each column represents either:
|
2
|
+
# * A grouped column, if Column#group_by or Column.group_by are set
|
3
|
+
# * An ungrouped column, if they aren't
|
4
|
+
class Omniboard::Column
|
5
|
+
include Omniboard::Property
|
6
|
+
|
7
|
+
# Column name, used to display
|
8
|
+
attr_accessor :name
|
9
|
+
def to_s; @name; end
|
10
|
+
|
11
|
+
# All projects contained by the column
|
12
|
+
attr_accessor :projects
|
13
|
+
|
14
|
+
# Blocks governing which projects are let in and how they are displayed
|
15
|
+
block_property :conditions
|
16
|
+
block_property :sort
|
17
|
+
block_property :mark_when
|
18
|
+
block_property :dim_when
|
19
|
+
block_property :icon
|
20
|
+
|
21
|
+
# Blocks governing how to group and how groups are displayed
|
22
|
+
block_property :group_by
|
23
|
+
block_property :sort_groups
|
24
|
+
block_property :group_name
|
25
|
+
|
26
|
+
INHERITED_PROPERTIES = %i(sort mark_when dim_when icon group_by sort_groups group_name hide_dimmed display_project_counts)
|
27
|
+
ALLOWED_PROJECT_COUNTS = %i(all active marked inherit)
|
28
|
+
ALLOWED_PROJECT_DISPLAYS = %i(full compact)
|
29
|
+
|
30
|
+
# Order in the kanban board. Lower numbers are further left. Default 0
|
31
|
+
property :order
|
32
|
+
|
33
|
+
# Relative width of the column. Defaults to 1.
|
34
|
+
property :width
|
35
|
+
|
36
|
+
# Display - compact or full? Full includes task info.
|
37
|
+
property :display, allowed_values: ALLOWED_PROJECT_DISPLAYS
|
38
|
+
|
39
|
+
# Display a heading with the total number of projects in this column?
|
40
|
+
# Allowed values: "all", "active", "marked", nil
|
41
|
+
property :display_project_counts, allowed_values: ALLOWED_PROJECT_COUNTS
|
42
|
+
|
43
|
+
# Display total projects in red if this project limit is breached
|
44
|
+
property :project_limit
|
45
|
+
|
46
|
+
# Do we show a button allowing us to filter our dimmed projects?
|
47
|
+
property :filter_button
|
48
|
+
|
49
|
+
# Do we automatically hide dimmed projects?
|
50
|
+
property :hide_dimmed
|
51
|
+
|
52
|
+
# How many projects to show per line? Defaults to 1.
|
53
|
+
property :columns
|
54
|
+
|
55
|
+
# Intializer. Provide name and block for instance evaluation (optional)
|
56
|
+
def initialize(name, &blck)
|
57
|
+
# Set defaults
|
58
|
+
self.name = name
|
59
|
+
self.projects = []
|
60
|
+
self.order(0)
|
61
|
+
self.width(1)
|
62
|
+
self.columns(1)
|
63
|
+
self.display(:full)
|
64
|
+
self.display_project_counts(nil)
|
65
|
+
self.project_limit(nil)
|
66
|
+
self.filter_button(false)
|
67
|
+
|
68
|
+
INHERITED_PROPERTIES.each{ |s| self.send(s, :inherit) }
|
69
|
+
|
70
|
+
instance_exec(&blck) if blck
|
71
|
+
Omniboard::Column.add(self)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Fetch the appropriate property value. If the value is :inherit, will fetch the appropriate value from Omniboard::Column
|
75
|
+
def property(sym)
|
76
|
+
raise(ArgumentError, "Unrecognised property #{sym}: allowed values are #{INHERITED_PROPERTIES.join(", ")}.") unless INHERITED_PROPERTIES.include?(sym)
|
77
|
+
v = self.send(sym)
|
78
|
+
v = Omniboard::Column.send(sym) if v == :inherit
|
79
|
+
v
|
80
|
+
end
|
81
|
+
|
82
|
+
# Add an array of projects to the column. If +conditions+ is set, each project will be run through it.
|
83
|
+
# Only projects that return +true+ will be allowed in.
|
84
|
+
def add(arr)
|
85
|
+
# Reset cache
|
86
|
+
@grouped_projects = nil
|
87
|
+
|
88
|
+
# Make sure it's an array
|
89
|
+
arr = [arr] unless arr.is_a?(Array) || arr.is_a?(Rubyfocus::SearchableArray)
|
90
|
+
|
91
|
+
# Run through individual conditions block
|
92
|
+
arr = arr.select{ |p| self.conditions[p] } if self.conditions
|
93
|
+
|
94
|
+
# Run through global conditions block
|
95
|
+
arr = arr.select{ |p| self.class.conditions[p] } if self.class.conditions
|
96
|
+
|
97
|
+
# Wrap in ProjectWrappers
|
98
|
+
arr = arr.map{ |p| p.is_a?(Omniboard::ProjectWrapper) ? p : Omniboard::ProjectWrapper.new(p, column: self) }
|
99
|
+
|
100
|
+
# Tasks performed upon adding project to column. Add to group, mark & dim appropriately
|
101
|
+
arr.each do |pw|
|
102
|
+
pw.column = self
|
103
|
+
|
104
|
+
pw.group = self.group_for(pw)
|
105
|
+
pw.marked = self.should_mark(pw)
|
106
|
+
pw.dimmed = self.should_dim(pw)
|
107
|
+
|
108
|
+
# Icon methods
|
109
|
+
icon_attrs = self.icon_for(pw)
|
110
|
+
if icon_attrs.is_a?(Array)
|
111
|
+
pw.icon, pw.icon_alt = *icon_attrs
|
112
|
+
else
|
113
|
+
pw.icon = icon_attrs
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
@projects += arr
|
118
|
+
end
|
119
|
+
alias_method :<<, :add
|
120
|
+
|
121
|
+
|
122
|
+
# Return an array of projects, sorted according to the sort block (or not, if no sort block supplied).
|
123
|
+
# If group string is provided, only fetched projects for that group
|
124
|
+
def projects(group=nil)
|
125
|
+
p = if group
|
126
|
+
group = Omniboard::Group[group] unless group.is_a?(Omniboard::Group)
|
127
|
+
grouped_projects[group]
|
128
|
+
else
|
129
|
+
@projects
|
130
|
+
end
|
131
|
+
|
132
|
+
sort_block = property(:sort)
|
133
|
+
|
134
|
+
if sort_block.nil?
|
135
|
+
p.sort_by(&:to_s)
|
136
|
+
elsif sort_block.arity == 1
|
137
|
+
p.sort_by(&self.sort)
|
138
|
+
elsif sort_block.arity == 2
|
139
|
+
p.sort(&self.sort)
|
140
|
+
else
|
141
|
+
raise ArgumentError, "Omniboard::Column.sort has an arity of #{sort.arity}, must take either 1 or 2 arguments."
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Returns true if column or global group_by supplied
|
146
|
+
def can_be_grouped?
|
147
|
+
!!property(:group_by)
|
148
|
+
end
|
149
|
+
|
150
|
+
# Returns a sorted array of groups. Returned as strings
|
151
|
+
def groups
|
152
|
+
keys = grouped_projects.keys.map(&:identifier)
|
153
|
+
|
154
|
+
group_sort_block = property(:sort_groups)
|
155
|
+
if group_sort_block.nil?
|
156
|
+
keys.sort
|
157
|
+
elsif group_sort_block.arity == 1
|
158
|
+
keys.sort_by(&group_sort_block)
|
159
|
+
elsif group_sort_block.arity == 2
|
160
|
+
keys.sort(&group_sort_block)
|
161
|
+
else
|
162
|
+
raise ArgumentError, "Omniboard::Column.group_sort has an arity of #{group_sort_block.arity}, must take either 1 or 2 arguments."
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# Return a hash of arrays of sorted projects, grouped using the group_by lambda.
|
167
|
+
# Note: Unsorted
|
168
|
+
def grouped_projects
|
169
|
+
raise(RuntimeError, "Attempted to return grouped projects from column #{self.name}, but no group_by method defined.") unless can_be_grouped?
|
170
|
+
@grouped_projects ||= self.projects.group_by(&:group)
|
171
|
+
end
|
172
|
+
|
173
|
+
# Return the group a project should fall into
|
174
|
+
def group_for(project)
|
175
|
+
gby = property(:group_by)
|
176
|
+
if gby
|
177
|
+
Omniboard::Group[gby[project]]
|
178
|
+
else
|
179
|
+
nil
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# Return the group name for a given group
|
184
|
+
def group_name_for(group)
|
185
|
+
gname = property(:group_name)
|
186
|
+
gname ? gname[group] : group.to_s
|
187
|
+
end
|
188
|
+
|
189
|
+
# Return the marked status of a given project, based on mark_when blocks
|
190
|
+
def should_mark(project)
|
191
|
+
mark = property(:mark_when)
|
192
|
+
if mark
|
193
|
+
mark[project]
|
194
|
+
else
|
195
|
+
false
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# Return the dimmed status of a given project, based on mark_when blocks
|
200
|
+
def should_dim(project)
|
201
|
+
dim = property(:dim_when)
|
202
|
+
if dim
|
203
|
+
dim[project]
|
204
|
+
else
|
205
|
+
false
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
# Return the icon for a given project, based on icon blocks
|
210
|
+
def icon_for(project)
|
211
|
+
ic = property(:icon)
|
212
|
+
if ic
|
213
|
+
ic[project]
|
214
|
+
else
|
215
|
+
nil
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
#---------------------------------------
|
220
|
+
# Presentation methods
|
221
|
+
def count_div
|
222
|
+
total = case property(:display_project_counts)
|
223
|
+
when :all
|
224
|
+
self.projects.count
|
225
|
+
when :active
|
226
|
+
self.projects.select{ |p| !p.dimmed? }.count
|
227
|
+
when :marked
|
228
|
+
self.projects.select{ |p| p.marked? }.count
|
229
|
+
else
|
230
|
+
0
|
231
|
+
end
|
232
|
+
css_class = "column-total"
|
233
|
+
css_class << " limit-breached" if project_limit && project_limit < total
|
234
|
+
%|<div class="#{css_class}">#{total}</div>|
|
235
|
+
end
|
236
|
+
|
237
|
+
|
238
|
+
#---------------------------------------
|
239
|
+
# Class-level methods
|
240
|
+
|
241
|
+
# Columns
|
242
|
+
@columns = []
|
243
|
+
|
244
|
+
# Any group colour assignments
|
245
|
+
@colour_groups = []
|
246
|
+
|
247
|
+
# Default values for global config
|
248
|
+
@hide_dimmed = false
|
249
|
+
|
250
|
+
class << self
|
251
|
+
include Omniboard::Property
|
252
|
+
|
253
|
+
attr_reader :columns
|
254
|
+
|
255
|
+
# Add a column to the global columns register
|
256
|
+
def add(c)
|
257
|
+
@columns << c
|
258
|
+
@columns = @columns.sort_by(&:order)
|
259
|
+
end
|
260
|
+
|
261
|
+
# Wipe the global register of columns. Useful when resetting Omniboard to
|
262
|
+
# a default state
|
263
|
+
def reset_columns
|
264
|
+
@columns = []
|
265
|
+
end
|
266
|
+
|
267
|
+
# Configuration
|
268
|
+
|
269
|
+
#---------------------------------------
|
270
|
+
# Font config values
|
271
|
+
property :heading_font
|
272
|
+
property :body_font
|
273
|
+
|
274
|
+
# If set, will provide a link in the heading allowing you to refresh the page
|
275
|
+
property :refresh_link
|
276
|
+
|
277
|
+
# Fallback default for displaying project counts
|
278
|
+
# Allowed values: "all", "active", "marked", nil
|
279
|
+
property :display_project_counts, allowed_values: ALLOWED_PROJECT_COUNTS
|
280
|
+
|
281
|
+
# Global conditions, apply to all columns
|
282
|
+
block_property :conditions
|
283
|
+
|
284
|
+
# Fallback sort method
|
285
|
+
block_property :sort
|
286
|
+
|
287
|
+
# Fallback mark method, apply only if individual mark method is blank
|
288
|
+
block_property :mark_when
|
289
|
+
|
290
|
+
# Fallback mark method, apply only if individual dim method is blank
|
291
|
+
block_property :dim_when
|
292
|
+
|
293
|
+
# Fallback property for hiding dimmed project
|
294
|
+
property :hide_dimmed
|
295
|
+
|
296
|
+
# Fallback group method, apply only if individual column group is blank
|
297
|
+
block_property :group_by
|
298
|
+
|
299
|
+
# Fallback group name method, apply only if individual column group name is blank
|
300
|
+
block_property :group_name
|
301
|
+
|
302
|
+
# Fallback group sort method
|
303
|
+
block_property :sort_groups
|
304
|
+
|
305
|
+
# Fallback icon method
|
306
|
+
block_property :icon
|
307
|
+
|
308
|
+
# Assign a colour to a group, given it fits a block
|
309
|
+
def colour_group(hue, &blck)
|
310
|
+
@colour_groups << {hue: hue, block: blck}
|
311
|
+
end
|
312
|
+
|
313
|
+
# Returns the appropriate hue for a given group, if it matches any of the colour groups provided by @colour_groups
|
314
|
+
def colour_for_group(group)
|
315
|
+
colour_group = @colour_groups.find{ |cgp| cgp[:block][group] }
|
316
|
+
return colour_group && colour_group[:hue]
|
317
|
+
end
|
318
|
+
|
319
|
+
# Config method
|
320
|
+
def config &blck
|
321
|
+
self.instance_exec(&blck)
|
322
|
+
end
|
323
|
+
|
324
|
+
# Clear configuration option. You can always pass :all to clear all configuration options
|
325
|
+
def clear_config config
|
326
|
+
case config
|
327
|
+
when :conditions
|
328
|
+
@conditions = nil
|
329
|
+
when :sort
|
330
|
+
@sort = nil
|
331
|
+
when :group_by
|
332
|
+
@group_by = nil
|
333
|
+
when :mark_when
|
334
|
+
@mark_when = nil
|
335
|
+
when :dim_when
|
336
|
+
@dim_when = nil
|
337
|
+
when :icon
|
338
|
+
@icon = nil
|
339
|
+
when :sort_groups
|
340
|
+
@sort_groups = nil
|
341
|
+
when :group_name
|
342
|
+
@group_name = nil
|
343
|
+
when :hide_dimmed
|
344
|
+
@hide_dimmed = false
|
345
|
+
when :display_project_counts
|
346
|
+
@display_project_counts = nil
|
347
|
+
when :all
|
348
|
+
@conditions = nil
|
349
|
+
@sort = nil
|
350
|
+
@group_by = nil
|
351
|
+
@mark_when = nil
|
352
|
+
@dim_when = nil
|
353
|
+
@icon = nil
|
354
|
+
@sort_groups = nil
|
355
|
+
@group_name = nil
|
356
|
+
@hide_dimmed = false
|
357
|
+
@display_project_counts = nil
|
358
|
+
else
|
359
|
+
raise ArgumentError, "Do not know how to clear config: #{config}"
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
#---------------------------------------
|
365
|
+
# Default values
|
366
|
+
heading_font "Helvetica, Arial, sans-serif"
|
367
|
+
body_font "Helvetica, Arial, sans-serif"
|
368
|
+
end
|