omniboard 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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