watch_tower 0.0.0.1 → 0.0.1.beta1

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.
Files changed (106) hide show
  1. data/.todo +3 -0
  2. data/.travis.yml +4 -0
  3. data/Gemfile +0 -4
  4. data/README.md +48 -2
  5. data/TODO +3 -0
  6. data/ci/travis.rb +4 -1
  7. data/lib/watch_tower/appscript.rb +65 -7
  8. data/lib/watch_tower/cli/install.rb +27 -3
  9. data/lib/watch_tower/cli/open.rb +2 -0
  10. data/lib/watch_tower/cli/start.rb +3 -1
  11. data/lib/watch_tower/cli.rb +2 -0
  12. data/lib/watch_tower/config.rb +2 -0
  13. data/lib/watch_tower/core_ext.rb +2 -0
  14. data/lib/watch_tower/editor/base_appscript.rb +28 -1
  15. data/lib/watch_tower/editor/base_ps.rb +25 -0
  16. data/lib/watch_tower/editor/textmate.rb +2 -0
  17. data/lib/watch_tower/editor/xcode.rb +2 -0
  18. data/lib/watch_tower/editor.rb +2 -0
  19. data/lib/watch_tower/errors.rb +2 -0
  20. data/lib/watch_tower/eye.rb +13 -2
  21. data/lib/watch_tower/file_tree.rb +78 -0
  22. data/lib/watch_tower/project/any_based.rb +2 -0
  23. data/lib/watch_tower/project/git_based.rb +2 -0
  24. data/lib/watch_tower/project/init.rb +2 -0
  25. data/lib/watch_tower/project/path_based.rb +2 -0
  26. data/lib/watch_tower/project.rb +2 -0
  27. data/lib/watch_tower/server/app.rb +37 -7
  28. data/lib/watch_tower/server/assets/javascripts/application.js +4 -0
  29. data/lib/watch_tower/server/assets/javascripts/datepicker.coffee +21 -0
  30. data/lib/watch_tower/server/assets/javascripts/file_tree.coffee +30 -0
  31. data/lib/watch_tower/server/assets/javascripts/percentage.coffee +15 -5
  32. data/lib/watch_tower/server/assets/stylesheets/application.css +1 -0
  33. data/lib/watch_tower/server/assets/stylesheets/date.sass +17 -0
  34. data/lib/watch_tower/server/assets/stylesheets/file_tree.sass +42 -0
  35. data/lib/watch_tower/server/assets/stylesheets/global.sass +7 -4
  36. data/lib/watch_tower/server/configurations/asset.rb +2 -0
  37. data/lib/watch_tower/server/configurations.rb +2 -0
  38. data/lib/watch_tower/server/database.rb +2 -0
  39. data/lib/watch_tower/server/db/migrate/001_create_projects.rb +2 -0
  40. data/lib/watch_tower/server/db/migrate/002_create_files.rb +2 -0
  41. data/lib/watch_tower/server/db/migrate/003_create_time_entries.rb +2 -0
  42. data/lib/watch_tower/server/db/migrate/004_create_durations.rb +2 -0
  43. data/lib/watch_tower/server/db/migrate/005_add_hash_to_time_entries.rb +2 -0
  44. data/lib/watch_tower/server/db/migrate/006_add_hash_to_files.rb +2 -0
  45. data/lib/watch_tower/server/db/migrate/007_add_editor_to_times_entries.rb +8 -0
  46. data/lib/watch_tower/server/db/migrate/008_rename_editor_to_editor_name_in_times_entries.rb +5 -0
  47. data/lib/watch_tower/server/db/migrate/009_remove_editor_index_from_time_entries.rb +7 -0
  48. data/lib/watch_tower/server/db/migrate/010_add_editor_version_to_times_entries.rb +7 -0
  49. data/lib/watch_tower/server/helpers/asset.rb +2 -0
  50. data/lib/watch_tower/server/helpers/improved_partials.rb +2 -0
  51. data/lib/watch_tower/server/helpers/presenters.rb +33 -0
  52. data/lib/watch_tower/server/helpers.rb +3 -0
  53. data/lib/watch_tower/server/models/duration.rb +9 -0
  54. data/lib/watch_tower/server/models/file.rb +17 -0
  55. data/lib/watch_tower/server/models/project.rb +30 -0
  56. data/lib/watch_tower/server/models/time_entry.rb +5 -0
  57. data/lib/watch_tower/server/presenters/application_presenter.rb +165 -0
  58. data/lib/watch_tower/server/presenters/file_presenter.rb +10 -0
  59. data/lib/watch_tower/server/presenters/project_presenter.rb +20 -0
  60. data/lib/watch_tower/server/presenters.rb +13 -0
  61. data/lib/watch_tower/server/public/assets/{WatchTower-4d6de11e1bd34165ad91ac46fb711bf3.jpg → WatchTower-58eff0713efffbc6054defddc879e0b1.jpg} +0 -0
  62. data/lib/watch_tower/server/public/assets/application-4e6971066e06aa53b0c8e52c764044d1.css +389 -0
  63. data/lib/watch_tower/server/public/assets/application-6a1be75d4fd6a545faceb638e47a2486.js +23778 -0
  64. data/lib/watch_tower/server/public/assets/calendar-379834cd6e6321a940b662ace47f3032.gif +0 -0
  65. data/lib/watch_tower/server/public/assets/calendar-blue-d6aa74feef7ee4287532761db99a6c0a.gif +0 -0
  66. data/lib/watch_tower/server/public/assets/calendar-green-3752fe2996091379c8d321f759039385.gif +0 -0
  67. data/lib/watch_tower/server/public/assets/jquery.datepick-9c8dfe3a4d40bcafc7b182e194c13836.css +227 -0
  68. data/lib/watch_tower/server/public/assets/{percentage-d8589e21a5fc85d32a445f531ff8ab95.png → percentage-d0176e99520c95e93eee63738ef5d487.png} +0 -0
  69. data/lib/watch_tower/server/vendor/assets/images/calendar-blue.gif +0 -0
  70. data/lib/watch_tower/server/vendor/assets/images/calendar-green.gif +0 -0
  71. data/lib/watch_tower/server/vendor/assets/images/calendar.gif +0 -0
  72. data/lib/watch_tower/server/vendor/assets/javascripts/jquery-datepick-ext.js +266 -0
  73. data/lib/watch_tower/server/vendor/assets/javascripts/jquery-datepick-validation.js +232 -0
  74. data/lib/watch_tower/server/vendor/assets/javascripts/jquery-datepick.js +2092 -0
  75. data/lib/watch_tower/server/vendor/assets/stylesheets/jquery.datepick.css +226 -0
  76. data/lib/watch_tower/server/views/_project.haml +10 -13
  77. data/lib/watch_tower/server/views/index.haml +9 -6
  78. data/lib/watch_tower/server/views/layout.haml +7 -4
  79. data/lib/watch_tower/server/views/project.haml +9 -11
  80. data/lib/watch_tower/server.rb +8 -1
  81. data/lib/watch_tower/templates/{watchtower.plist → watchtower.plist.erb} +5 -6
  82. data/lib/watch_tower/version.rb +12 -3
  83. data/lib/watch_tower.rb +20 -1
  84. data/spec/factories.rb +2 -0
  85. data/spec/watch_tower/appscript_spec.rb +36 -2
  86. data/spec/watch_tower/editor/base_appscript_spec.rb +83 -0
  87. data/spec/watch_tower/editor/textmate_spec.rb +37 -0
  88. data/spec/watch_tower/editor/xcode_spec.rb +6 -0
  89. data/spec/watch_tower/eye_spec.rb +21 -0
  90. data/spec/watch_tower/file_tree_spec.rb +156 -0
  91. data/spec/watch_tower/server/app_spec.rb +64 -20
  92. data/spec/watch_tower/server/models/file_spec.rb +93 -31
  93. data/spec/watch_tower/server/models/project_spec.rb +147 -18
  94. data/spec/watch_tower/server/models/time_entry_spec.rb +10 -0
  95. data/spec/watch_tower/server/{decorator/project_decorator_spec.rb → presenters/application_presenter_spec.rb} +24 -13
  96. data/spec/watch_tower/server/presenters/file_presenter_spec.rb +8 -0
  97. data/spec/watch_tower/server/presenters/project_presenter_spec.rb +130 -0
  98. data/watch_tower.gemspec +10 -4
  99. metadata +114 -74
  100. data/lib/watch_tower/server/decorator/application_decorator.rb +0 -91
  101. data/lib/watch_tower/server/decorator/file_decorator.rb +0 -38
  102. data/lib/watch_tower/server/decorator/project_decorator.rb +0 -51
  103. data/lib/watch_tower/server/decorator.rb +0 -21
  104. data/lib/watch_tower/server/public/assets/application-7829b53b5ece1a16d22dc3d00f329023.css +0 -107
  105. data/lib/watch_tower/server/public/assets/application-e0e6b7731aade460f680331e65cf0682.js +0 -9359
  106. data/lib/watch_tower/server/views/_file.haml +0 -9
@@ -0,0 +1,21 @@
1
+ window.date_filtering =
2
+ handleResponse: (response, textStatus, jqXHR) ->
3
+ ($ '.page').html(response)
4
+ percentage.applyPercentage()
5
+
6
+ updateMetaTags: (dates) ->
7
+ ($ 'meta[name="date_filtering_from"]').attr 'content', $.datepick.formatDate(dates[0])
8
+ ($ 'meta[name="date_filtering_to"]').attr 'content', $.datepick.formatDate(dates[1])
9
+
10
+ handleDatePicker: (dates) ->
11
+ options = { from_date: $.datepick.formatDate(dates[0]), to_date: $.datepick.formatDate(dates[1]) }
12
+ url = window.location.pathname
13
+ date_filtering.updateMetaTags dates
14
+ $.get url, options, date_filtering.handleResponse
15
+
16
+ jQuery ->
17
+ ($ '#date input').datepick
18
+ rangeSelect: true,
19
+ monthsToShow: 2,
20
+ alignment: 'bottomRight',
21
+ onClose: date_filtering.handleDatePicker
@@ -0,0 +1,30 @@
1
+ window.file_tree =
2
+ handle_button: (domId) ->
3
+ if ($ domId).hasClass "collapsed"
4
+ ($ domId).removeClass "collapsed"
5
+ ($ domId).addClass "expanded"
6
+ ($ domId).text '-'
7
+ file_tree.show_folder domId
8
+ else
9
+ ($ domId).removeClass "expanded"
10
+ ($ domId).addClass "collapsed"
11
+ ($ domId).text '+'
12
+ file_tree.hide_folder domId
13
+
14
+ show_folder: (button_domId) ->
15
+ ($ button_domId).parent().parent().children('.nested_folder').each (index, element) ->
16
+ ($ element).show()
17
+ ($ button_domId).parent().parent().children('.files').each (index, element) ->
18
+ ($ element).show()
19
+
20
+
21
+ hide_folder: (button_domId) ->
22
+ ($ button_domId).parent().parent().children('.nested_folder').each (index, element) ->
23
+ ($ element).hide()
24
+ ($ button_domId).parent().parent().children('.files').each (index, element) ->
25
+ ($ element).hide()
26
+
27
+ jQuery ($) ->
28
+ ($ '.collapsed').each (index, element) ->
29
+ ($ element).bind 'click', ->
30
+ file_tree.handle_button element
@@ -1,8 +1,18 @@
1
- animateGrowth = (domId, width) ->
2
- ($ domId).attr('width', width)
1
+ window.percentage =
2
+ applyPercentage: ->
3
+ ($ '.percentage_img').each (index, element) ->
4
+ width = ($ element).attr('data-width')
5
+ percentage.animateGrowth(element, width * 3)
6
+
7
+ animateGrowth: (domId, width, overGrow = false) ->
8
+ overGrowth = width + 50
9
+ overGrowth = 300 if overGrowth > 300
10
+ if overGrow
11
+ ($ domId).effect 'size', { to: { width: width } }, 1000
12
+ else
13
+ ($ domId).effect 'size', { to: { width: overGrowth } }, 1000, ->
14
+ percentage.animateGrowth domId, width, true
3
15
 
4
16
 
5
17
  jQuery ->
6
- ($ '.percentage_img').each (index, element) ->
7
- width = ($ element).attr('data-width')
8
- animateGrowth(element, width * 3)
18
+ percentage.applyPercentage()
@@ -3,5 +3,6 @@
3
3
  * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
4
4
  * the top of the compiled file, but it's generally better to create a new file per style scope.
5
5
  *= require_self
6
+ *= require jquery.datepick
6
7
  *= require_tree .
7
8
  */
@@ -0,0 +1,17 @@
1
+ aside#date
2
+ float: right
3
+ width: 200px
4
+ height: 30px
5
+ padding: 1px
6
+ border: 1px solid #CDCDCD
7
+ -webkit-border-radius: 5px
8
+ -moz-border-radius: 5px
9
+ border-radius: 5px
10
+
11
+ &:hover
12
+ border: 1px solid #AAAAAA
13
+
14
+ input
15
+ border: none
16
+ width: 201px
17
+ height: 21px
@@ -0,0 +1,42 @@
1
+ .file_tree
2
+ background: #f7f7f7
3
+ padding: 10px
4
+ min-height: 16px
5
+ border: 1px solid #cdcdcd
6
+ -webkit-border-radius: 5px
7
+ -moz-border-radius: 5px
8
+ border-radius: 5px
9
+
10
+ .collapsed, .expanded
11
+ float: left
12
+ text-align: center
13
+ width: 10px
14
+ height: 9px
15
+ margin-right: 10px
16
+ background: #fff
17
+ padding: 0 3px 7px 3px
18
+ -webkit-border-radius: 29px
19
+ -moz-border-radius: 29px
20
+ border-radius: 29px
21
+
22
+ &:hover
23
+ background: #5D4BFF
24
+ color: #fff
25
+ cursor: pointer
26
+
27
+ .name
28
+ float: left
29
+ .path
30
+ float: left
31
+ width: auto !important
32
+ .elapsed_time
33
+ float: right
34
+ .files
35
+ display: none
36
+ list-style: none
37
+ margin: 0
38
+
39
+ .nested_folder
40
+ display: none
41
+ &
42
+ margin-left: 15px
@@ -15,7 +15,7 @@ h2
15
15
  // Background stylesheet
16
16
  display: block
17
17
  // TODO: Very bad bad bad idea to hardcode assets, how can we reference an asset from sass ?
18
- background: url('WatchTower-4d6de11e1bd34165ad91ac46fb711bf3.jpg') no-repeat
18
+ background: url('WatchTower-58eff0713efffbc6054defddc879e0b1.jpg') no-repeat
19
19
  width: 390px
20
20
  height: 94px
21
21
 
@@ -48,13 +48,16 @@ body
48
48
 
49
49
  #main
50
50
  border: 1px solid #cdcdcd
51
- -webkit-border-radius: 5px
52
- -moz-border-radius: 5px
53
- border-radius: 5px
51
+ -webkit-border-radius: 5px 5px 0 0
52
+ -moz-border-radius: 5px 5px 0 0
53
+ border-radius: 5px 5px 0 0
54
54
  padding: 10px
55
55
  min-height: 200px
56
56
  background: #fff
57
57
 
58
+ .page
59
+ padding: 40px 0 0
60
+
58
61
  #footer
59
62
  width: 960px
60
63
  text-align: center
@@ -1,3 +1,5 @@
1
+ # -*- encoding: utf-8 -*-
2
+
1
3
  # Asset Pipeline
2
4
  require 'coffee-script'
3
5
  require 'uglifier'
@@ -1,3 +1,5 @@
1
+ # -*- encoding: utf-8 -*-
2
+
1
3
  module WatchTower
2
4
  module Server
3
5
  module Configurations
@@ -1,3 +1,5 @@
1
+ # -*- encoding: utf-8 -*-
2
+
1
3
  require 'active_record'
2
4
 
3
5
  module WatchTower
@@ -1,3 +1,5 @@
1
+ # -*- encoding: utf-8 -*-
2
+
1
3
  class CreateProjects < ActiveRecord::Migration
2
4
  def change
3
5
  create_table :projects do |t|
@@ -1,3 +1,5 @@
1
+ # -*- encoding: utf-8 -*-
2
+
1
3
  class CreateFiles < ActiveRecord::Migration
2
4
  def change
3
5
  create_table :files do |t|
@@ -1,3 +1,5 @@
1
+ # -*- encoding: utf-8 -*-
2
+
1
3
  class CreateTimeEntries < ActiveRecord::Migration
2
4
  def change
3
5
  create_table :time_entries do |t|
@@ -1,3 +1,5 @@
1
+ # -*- encoding: utf-8 -*-
2
+
1
3
  class CreateDurations < ActiveRecord::Migration
2
4
  def change
3
5
  create_table :durations do |t|
@@ -1,3 +1,5 @@
1
+ # -*- encoding: utf-8 -*-
2
+
1
3
  class AddHashToTimeEntries < ActiveRecord::Migration
2
4
  def change
3
5
  add_column :time_entries, :file_hash, :string, null: false, default: ""
@@ -1,3 +1,5 @@
1
+ # -*- encoding: utf-8 -*-
2
+
1
3
  class AddHashToFiles < ActiveRecord::Migration
2
4
  def change
3
5
  add_column :files, :file_hash, :string, null: false, default: ""
@@ -0,0 +1,8 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ class AddEditorToTimesEntries < ActiveRecord::Migration
4
+ def change
5
+ add_column :time_entries, :editor, :string, null: false, default: ""
6
+ add_index :time_entries, :editor
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ class RenameEditorToEditorNameInTimesEntries < ActiveRecord::Migration
4
+ rename_column :time_entries, :editor, :editor_name
5
+ end
@@ -0,0 +1,7 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ class RemoveEditorIndexFromTimeEntries < ActiveRecord::Migration
4
+ def change
5
+ remove_index :time_entries, :editor
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ class AddEditorVersionToTimesEntries < ActiveRecord::Migration
4
+ def change
5
+ add_column :time_entries, :editor_version, :string, null: false, default: ""
6
+ end
7
+ end
@@ -1,3 +1,5 @@
1
+ # -*- encoding: utf-8 -*-
2
+
1
3
  module WatchTower
2
4
  module Server
3
5
  module Helpers
@@ -1,3 +1,5 @@
1
+ # -*- encoding: utf-8 -*-
2
+
1
3
  module WatchTower
2
4
  module Server
3
5
  module Helpers
@@ -0,0 +1,33 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module WatchTower
4
+ module Server
5
+ module Helpers
6
+ module Presenters
7
+
8
+ def self.included(base)
9
+ base.send :include, InstanceMethods
10
+ end
11
+
12
+ module InstanceMethods
13
+
14
+ # Define partial as a helper
15
+ helpers do
16
+ # Present an object
17
+ # Usually called with a block, the method yields the presenter into
18
+ # the block
19
+ #
20
+ # @param [ActiveRecord::Base] Object: The model to present
21
+ # @param [Nil | Object] klass: The klass to present
22
+ def present(object, klass = nil)
23
+ klass ||= "::WatchTower::Server::Presenters::#{object.class.to_s.split('::').last}Presenter".constantize
24
+ presenter = klass.new(object, self)
25
+ yield presenter if block_given?
26
+ presenter
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,3 +1,5 @@
1
+ # -*- encoding: utf-8 -*-
2
+
1
3
  require 'sinatra-snap'
2
4
 
3
5
  module WatchTower
@@ -8,6 +10,7 @@ module WatchTower
8
10
  # Sinatra helpers
9
11
  autoload :ImprovedPartials
10
12
  autoload :Asset
13
+ autoload :Presenters
11
14
  end
12
15
  end
13
16
  end
@@ -1,10 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+
1
3
  module WatchTower
2
4
  module Server
3
5
  class Duration < ::ActiveRecord::Base
6
+ # Scopes
7
+ scope :before_date, lambda { |date| where('date <= ?', Date.strptime(date, '%m/%d/%Y')) }
8
+ scope :after_date, lambda { |date| where('date >= ?', Date.strptime(date, '%m/%d/%Y')) }
9
+ scope :date_range, lambda { |from, to| after_date(from).before_date(to) }
10
+
11
+ # Validations
4
12
  validates :file_id, presence: true
5
13
  validates :date, presence: true
6
14
  validates :duration, presence: true
7
15
 
16
+ # Associations
8
17
  belongs_to :file, counter_cache: true
9
18
  end
10
19
  end
@@ -1,3 +1,5 @@
1
+ # -*- encoding: utf-8 -*-
2
+
1
3
  module WatchTower
2
4
  module Server
3
5
  class File < ::ActiveRecord::Base
@@ -20,6 +22,21 @@ module WatchTower
20
22
  (elapsed_time * 100) / project.files.sum_elapsed_time
21
23
  end
22
24
 
25
+ # Return an ActiveRelation limited to a date range
26
+ #
27
+ # @param [String] From date
28
+ # @param [String] To date
29
+ # @return [ActiveRelation]
30
+ def self.date_range(from, to)
31
+ from = Date.strptime(from, '%m/%d/%Y')
32
+ to = Date.strptime(to, '%m/%d/%Y')
33
+
34
+ joins(:durations => :file).
35
+ where('durations.date >= ?', from).
36
+ where('durations.date <= ?', to).
37
+ select('DISTINCT files.*')
38
+ end
39
+
23
40
  # Returns the sum of all elapsed time
24
41
  #
25
42
  # @return [Integer]
@@ -1,3 +1,5 @@
1
+ # -*- encoding: utf-8 -*-
2
+
1
3
  module WatchTower
2
4
  module Server
3
5
  class Project < ::ActiveRecord::Base
@@ -12,6 +14,34 @@ module WatchTower
12
14
  # Associations
13
15
  has_many :files, dependent: :destroy
14
16
  has_many :time_entries, through: :files
17
+ has_many :durations, through: :files
18
+
19
+ # Return the percent of this file
20
+ def percent
21
+ (elapsed_time * 100) / self.class.sum_elapsed_time
22
+ end
23
+
24
+ # Return an ActiveRelation limited to a date range
25
+ #
26
+ # @param [String] From date
27
+ # @param [String] To date
28
+ # @return [ActiveRelation]
29
+ def self.date_range(from, to)
30
+ from = Date.strptime(from, '%m/%d/%Y')
31
+ to = Date.strptime(to, '%m/%d/%Y')
32
+
33
+ joins(:durations => :file).
34
+ where('durations.date >= ?', from).
35
+ where('durations.date <= ?', to).
36
+ select('DISTINCT projects.*')
37
+ end
38
+
39
+ # Returns the sum of all elapsed time
40
+ #
41
+ # @return [Integer]
42
+ def self.sum_elapsed_time
43
+ sum(:elapsed_time)
44
+ end
15
45
  end
16
46
  end
17
47
  end
@@ -1,3 +1,5 @@
1
+ # -*- encoding: utf-8 -*-
2
+
1
3
  module WatchTower
2
4
  module Server
3
5
  class TimeEntry < ::ActiveRecord::Base
@@ -10,10 +12,13 @@ module WatchTower
10
12
  validates :mtime, presence: true
11
13
  validates_uniqueness_of :mtime, scope: :file_id
12
14
  validates :file_hash, presence: true
15
+ validates :editor_name, presence: true
16
+ validates :editor_version, presence: true
13
17
 
14
18
 
15
19
  # Associations
16
20
  belongs_to :file, counter_cache: true
21
+ has_many :durations
17
22
 
18
23
  # Callbacks
19
24
  after_create :calculate_elapsed_time
@@ -0,0 +1,165 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module WatchTower
4
+ module Server
5
+ module Presenters
6
+ class ApplicationPresenter
7
+
8
+ attr_reader :model, :template
9
+
10
+ # Presents a model
11
+ #
12
+ # @param [Symbol] name
13
+ def self.presents(name)
14
+ define_method name do
15
+ @model
16
+ end
17
+ end
18
+
19
+ # Initialise the presenter
20
+ #
21
+ # @param [ActiveRecord::Base] Model
22
+ # @param [Object] Template
23
+ def initialize(model, template)
24
+ @model = model
25
+ @template = template
26
+ end
27
+
28
+ # Overwrite Kernel#method_missing to invoke either the model or the
29
+ # template's method if present, if not Kernel#method_missing will be
30
+ # called
31
+ #
32
+ # @param [Symbol] method: The method name
33
+ # @param [Array] arguments
34
+ # @param [Block]
35
+ def method_missing(method, *args, &block)
36
+ if model.respond_to?(method)
37
+ model.send(method, *args, &block)
38
+ elsif template.respond_to?(method)
39
+ template.send(method, *args, &block)
40
+ else
41
+ super
42
+ end
43
+ end
44
+
45
+ # Returns a human formatted time
46
+ #
47
+ # @param [Integer] elapsed_time
48
+ # @return [String] The elapsed time formatted
49
+ def elapsed(elapsed_time = nil)
50
+ return "" if elapsed_time.nil? && !model.respond_to?(:elapsed_time)
51
+ elapsed_time ||= model.elapsed_time
52
+ humanize_time elapsed_time
53
+ end
54
+
55
+ protected
56
+ def pluralize(num, word)
57
+ if num > 1
58
+ "#{num} #{word.pluralize}"
59
+ else
60
+ "#{num} #{word}"
61
+ end
62
+ end
63
+
64
+ # Humanize time
65
+ #
66
+ # @param [Integer] The number of seconds
67
+ # @return [String]
68
+ def humanize_time(time)
69
+ case
70
+ when time >= 1.day
71
+ humanize_day(time)
72
+ when time >= 1.hour
73
+ humanize_hour(time)
74
+ when time >= 1.minute
75
+ humanize_minute(time)
76
+ else
77
+ pluralize time, "second"
78
+ end
79
+ end
80
+
81
+ [:day, :hour, :minute].each do |t|
82
+ class_eval <<-END, __FILE__, __LINE__ + 1
83
+ protected
84
+ def humanize_#{t}(time)
85
+ seconds = 1.#{t}
86
+ num = (time / seconds).to_i
87
+ rest = time % seconds
88
+
89
+ time_str = pluralize num, "#{t}"
90
+
91
+ unless rest == 0
92
+ "\#{time_str}#{t == :minute ? ' and' : ','} \#{humanize_time(rest)}"
93
+ else
94
+ time_str
95
+ end
96
+ end
97
+ END
98
+ end
99
+
100
+ # Parse a file tree
101
+ #
102
+ # @param [FileTree] tree
103
+ # @param [Boolean] root
104
+ # @return [String] HTML of the file tree
105
+ def parse_file_tree(tree, root = false)
106
+ # Create the root element
107
+ if root
108
+ html = '<article class="file_tree">'
109
+ html << '<div id="root" class="folder">'
110
+ else
111
+ folder_name = ::File.basename(tree.base_path)
112
+ html = %(<div id="nested_#{folder_name}" class="nested_folder">)
113
+ end
114
+ # Open the wrapper
115
+ html << '<div>'
116
+ # Add the collapsed span
117
+ html << '<span class="collapsed">+</span>'
118
+ # Add the name
119
+ if root
120
+ html << '<span class="name">Project</span>'
121
+ else
122
+ html << %(<span class="name">#{folder_name}</span>)
123
+ end
124
+ # Add the elapsed time
125
+ html << %(<span class="elapsed_time">#{elapsed(tree.elapsed_time)}</span>)
126
+ # End with a clearfix element
127
+ html << '<div class="clearfix"></div>'
128
+ # Close the wrapper
129
+ html << '</div>'
130
+ # Add the nested_tree if available
131
+ if tree.nested_tree.any?
132
+ tree.nested_tree.each_pair do |folder, nested_tree|
133
+ html << parse_file_tree(nested_tree)
134
+ end
135
+ end
136
+ # Add the files
137
+ if tree.files.any?
138
+ # Open the files 's ul
139
+ html << '<ul class="files">'
140
+ tree.files.each do |file|
141
+ # Open the file's li
142
+ html << '<li class="file">'
143
+ # Add the path
144
+ html << %(<span class="path">#{file[:path]}</span>)
145
+ # Add the elapsed time
146
+ html << %(<span class="elapsed_time">#{elapsed(file[:elapsed_time])}</span>)
147
+ # End with a clearfix element
148
+ html << '<div class="clearfix"></div>'
149
+ # Close the file's li
150
+ html << '</li>'
151
+ end
152
+ # Close the files 's ul
153
+ html << '</ul>'
154
+ end
155
+ # Close the root div
156
+ html << "</div>"
157
+ # Clode the article if it is the root element
158
+ html << "</article>" if root
159
+ # Finally return the whole thing
160
+ html
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,10 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module WatchTower
4
+ module Server
5
+ module Presenters
6
+ class FilePresenter < ApplicationPresenter
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module WatchTower
4
+ module Server
5
+ module Presenters
6
+ class ProjectPresenter < ApplicationPresenter
7
+
8
+ # Return a file tree representation of a bunch of files
9
+ #
10
+ # @return [String] HTML representation of a file tree
11
+ def file_tree(files)
12
+ # Create a FileTree
13
+ file_tree = FileTree.new(files.first.project.path, files)
14
+ # Parse and return the tree
15
+ parse_file_tree(file_tree, true)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,13 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module WatchTower
4
+ module Server
5
+ module Presenters
6
+ extend ::ActiveSupport::Autoload
7
+
8
+ autoload :ApplicationPresenter
9
+ autoload :ProjectPresenter
10
+ autoload :FilePresenter
11
+ end
12
+ end
13
+ end