omniboard 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ require "rspec/core/rake_task"
2
+
3
+ RSpec::Core::RakeTask.new(:spec)
@@ -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
@@ -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,15 @@
1
+ class Omniboard::Colour
2
+ attr_accessor :hue
3
+
4
+ def initialize(hue)
5
+ @hue = hue
6
+ end
7
+
8
+ def standard
9
+ "hsl(#{hue},100%,80%)"
10
+ end
11
+
12
+ def light
13
+ "hsl(#{hue},50%,90%)"
14
+ end
15
+ end
@@ -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