watch_tower 0.0.0.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.
- data/.gitignore +10 -0
- data/.todo +33 -0
- data/.travis.yml +14 -0
- data/Gemfile +43 -0
- data/Guardfile +25 -0
- data/MIT-LICENSE +20 -0
- data/README.md +38 -0
- data/Rakefile +8 -0
- data/TODO +17 -0
- data/bin/watchtower +10 -0
- data/ci/adapters/jruby-mysql.yml +8 -0
- data/ci/adapters/jruby-postgresql.yml +6 -0
- data/ci/adapters/jruby-sqlite.yml +6 -0
- data/ci/adapters/ruby-mysql.yml +8 -0
- data/ci/adapters/ruby-postgresql.yml +6 -0
- data/ci/adapters/ruby-sqlite.yml +6 -0
- data/ci/travis.rb +102 -0
- data/lib/watch_tower.rb +60 -0
- data/lib/watch_tower/appscript.rb +22 -0
- data/lib/watch_tower/cli.rb +15 -0
- data/lib/watch_tower/cli/.gitkeep +0 -0
- data/lib/watch_tower/cli/install.rb +63 -0
- data/lib/watch_tower/cli/open.rb +24 -0
- data/lib/watch_tower/cli/start.rb +140 -0
- data/lib/watch_tower/config.rb +38 -0
- data/lib/watch_tower/core_ext.rb +4 -0
- data/lib/watch_tower/core_ext/.gitkeep +0 -0
- data/lib/watch_tower/editor.rb +17 -0
- data/lib/watch_tower/editor/.gitkeep +0 -0
- data/lib/watch_tower/editor/base_appscript.rb +34 -0
- data/lib/watch_tower/editor/base_ps.rb +6 -0
- data/lib/watch_tower/editor/textmate.rb +17 -0
- data/lib/watch_tower/editor/xcode.rb +22 -0
- data/lib/watch_tower/errors.rb +25 -0
- data/lib/watch_tower/eye.rb +79 -0
- data/lib/watch_tower/project.rb +14 -0
- data/lib/watch_tower/project/.gitkeep +0 -0
- data/lib/watch_tower/project/any_based.rb +22 -0
- data/lib/watch_tower/project/git_based.rb +86 -0
- data/lib/watch_tower/project/init.rb +38 -0
- data/lib/watch_tower/project/path_based.rb +144 -0
- data/lib/watch_tower/server.rb +62 -0
- data/lib/watch_tower/server/.gitkeep +0 -0
- data/lib/watch_tower/server/app.rb +37 -0
- data/lib/watch_tower/server/assets/images/WatchTower.jpg +0 -0
- data/lib/watch_tower/server/assets/images/percentage.png +0 -0
- data/lib/watch_tower/server/assets/javascripts/application.js +3 -0
- data/lib/watch_tower/server/assets/javascripts/percentage.coffee +8 -0
- data/lib/watch_tower/server/assets/stylesheets/application.css +7 -0
- data/lib/watch_tower/server/assets/stylesheets/global.sass +71 -0
- data/lib/watch_tower/server/assets/stylesheets/project.sass +59 -0
- data/lib/watch_tower/server/configurations.rb +10 -0
- data/lib/watch_tower/server/configurations/asset.rb +43 -0
- data/lib/watch_tower/server/database.rb +105 -0
- data/lib/watch_tower/server/db/migrate/001_create_projects.rb +15 -0
- data/lib/watch_tower/server/db/migrate/002_create_files.rb +16 -0
- data/lib/watch_tower/server/db/migrate/003_create_time_entries.rb +12 -0
- data/lib/watch_tower/server/db/migrate/004_create_durations.rb +14 -0
- data/lib/watch_tower/server/db/migrate/005_add_hash_to_time_entries.rb +6 -0
- data/lib/watch_tower/server/db/migrate/006_add_hash_to_files.rb +6 -0
- data/lib/watch_tower/server/decorator.rb +21 -0
- data/lib/watch_tower/server/decorator/application_decorator.rb +91 -0
- data/lib/watch_tower/server/decorator/file_decorator.rb +38 -0
- data/lib/watch_tower/server/decorator/project_decorator.rb +51 -0
- data/lib/watch_tower/server/helpers.rb +13 -0
- data/lib/watch_tower/server/helpers/asset.rb +29 -0
- data/lib/watch_tower/server/helpers/improved_partials.rb +41 -0
- data/lib/watch_tower/server/models/duration.rb +11 -0
- data/lib/watch_tower/server/models/file.rb +31 -0
- data/lib/watch_tower/server/models/project.rb +17 -0
- data/lib/watch_tower/server/models/time_entry.rb +64 -0
- data/lib/watch_tower/server/public/assets/WatchTower-4d6de11e1bd34165ad91ac46fb711bf3.jpg +0 -0
- data/lib/watch_tower/server/public/assets/application-7829b53b5ece1a16d22dc3d00f329023.css +107 -0
- data/lib/watch_tower/server/public/assets/application-e0e6b7731aade460f680331e65cf0682.js +9359 -0
- data/lib/watch_tower/server/public/assets/percentage-d8589e21a5fc85d32a445f531ff8ab95.png +0 -0
- data/lib/watch_tower/server/vendor/assets/javascripts/jquery-ui.js +11729 -0
- data/lib/watch_tower/server/vendor/assets/javascripts/jquery.js +8981 -0
- data/lib/watch_tower/server/vendor/assets/javascripts/jquery_ujs.js +363 -0
- data/lib/watch_tower/server/views/.gitkeep +0 -0
- data/lib/watch_tower/server/views/_file.haml +9 -0
- data/lib/watch_tower/server/views/_project.haml +13 -0
- data/lib/watch_tower/server/views/index.haml +7 -0
- data/lib/watch_tower/server/views/layout.haml +32 -0
- data/lib/watch_tower/server/views/project.haml +12 -0
- data/lib/watch_tower/templates/config.yml +146 -0
- data/lib/watch_tower/templates/watchtower.plist +23 -0
- data/lib/watch_tower/version.rb +8 -0
- data/spec/factories.rb +45 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/active_record.rb +44 -0
- data/spec/support/factory_girl.rb +6 -0
- data/spec/support/launchy.rb +3 -0
- data/spec/support/sinatra.rb +10 -0
- data/spec/support/timecop.rb +7 -0
- data/spec/watch_tower/appscript_spec.rb +6 -0
- data/spec/watch_tower/cli/install_spec.rb +16 -0
- data/spec/watch_tower/cli/open_spec.rb +14 -0
- data/spec/watch_tower/cli/start_spec.rb +17 -0
- data/spec/watch_tower/cli_spec.rb +15 -0
- data/spec/watch_tower/config_spec.rb +25 -0
- data/spec/watch_tower/editor/textmate_spec.rb +43 -0
- data/spec/watch_tower/editor/xcode_spec.rb +43 -0
- data/spec/watch_tower/editor_spec.rb +19 -0
- data/spec/watch_tower/eye_spec.rb +130 -0
- data/spec/watch_tower/project/git_based_spec.rb +131 -0
- data/spec/watch_tower/project/path_based_spec.rb +111 -0
- data/spec/watch_tower/project_spec.rb +82 -0
- data/spec/watch_tower/server/app_spec.rb +186 -0
- data/spec/watch_tower/server/decorator/project_decorator_spec.rb +60 -0
- data/spec/watch_tower/server/models/file_spec.rb +284 -0
- data/spec/watch_tower/server/models/project_spec.rb +165 -0
- data/spec/watch_tower/server/models/time_entry_spec.rb +37 -0
- data/spec/watch_tower/server_spec.rb +4 -0
- data/watch_tower.gemspec +80 -0
- metadata +450 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
require 'digest/sha1'
|
|
2
|
+
|
|
3
|
+
module WatchTower
|
|
4
|
+
module Eye
|
|
5
|
+
extend self
|
|
6
|
+
|
|
7
|
+
# Start the watch loop
|
|
8
|
+
#
|
|
9
|
+
# @param [Hash] options
|
|
10
|
+
# @raise [EyeError]
|
|
11
|
+
def start(options = {})
|
|
12
|
+
LOG.debug("#{__FILE__}:#{__LINE__}: The Eye loop has just started")
|
|
13
|
+
loop do
|
|
14
|
+
# Try getting the mtime of the document opened by each editor in the
|
|
15
|
+
# editors list.
|
|
16
|
+
Editor.editors.each do |editor|
|
|
17
|
+
# Create an instance of the editor
|
|
18
|
+
# TODO: Should be used as a class instead
|
|
19
|
+
editor = editor.new
|
|
20
|
+
# Check if the editor is running
|
|
21
|
+
if editor.is_running?
|
|
22
|
+
LOG.debug("#{__FILE__}:#{__LINE__}: #{editor.to_s} is running")
|
|
23
|
+
# Get the currently being edited file from the editor
|
|
24
|
+
files_paths = editor.current_paths
|
|
25
|
+
files_paths.each do |file_path|
|
|
26
|
+
begin
|
|
27
|
+
# Get the file_hash of the file
|
|
28
|
+
file_hash = Digest::SHA1.file(file_path).hexdigest
|
|
29
|
+
# Create a project from the file_path
|
|
30
|
+
project = Project.new_from_path(file_path)
|
|
31
|
+
rescue PathNotUnderCodePath
|
|
32
|
+
LOG.debug("#{__FILE__}:#{__LINE__ - 2}: The file '#{file_path}' is not located under '#{Config[:code_path]}', it has been ignored")
|
|
33
|
+
next
|
|
34
|
+
rescue FileNotFound
|
|
35
|
+
LOG.debug "#{__FILE__}:#{__LINE__ - 5}: The file '#{file_path}' does not exist, it has been ignored"
|
|
36
|
+
next
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
begin
|
|
40
|
+
# Create (or fetch) a project
|
|
41
|
+
project_model = Server::Project.find_or_create_by_name_and_path(project.name, project.path)
|
|
42
|
+
|
|
43
|
+
# Create (or fetch) a file
|
|
44
|
+
file_model = project_model.files.find_or_create_by_path(file_path)
|
|
45
|
+
begin
|
|
46
|
+
# Create a time entry
|
|
47
|
+
file_model.time_entries.create!(mtime: File.stat(file_path).mtime, file_hash: file_hash)
|
|
48
|
+
rescue ActiveRecord::RecordInvalid => e
|
|
49
|
+
# This should happen if the mtime is already present
|
|
50
|
+
end
|
|
51
|
+
rescue ActiveRecord::RecordInvalid => e
|
|
52
|
+
# This should not happen
|
|
53
|
+
LOG.fatal("#{__FILE__}:#{__LINE__}: #{e}")
|
|
54
|
+
$close_eye = true
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
else
|
|
58
|
+
LOG.debug("#{__FILE__}:#{__LINE__}: #{editor.to_s} is not running")
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# If $stop global is set, please stop, otherwise sleep for 30 seconds.
|
|
63
|
+
if $close_eye
|
|
64
|
+
LOG.debug("#{__FILE__}:#{__LINE__}: Closing eye has been requested, end the loop")
|
|
65
|
+
break
|
|
66
|
+
else
|
|
67
|
+
sleep 10
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Start the Eye, a method invoked from the Watch Tower command line interface
|
|
73
|
+
#
|
|
74
|
+
# @param [Hash] options
|
|
75
|
+
def start!(options = {})
|
|
76
|
+
start(options)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
File without changes
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module WatchTower
|
|
2
|
+
class Project
|
|
3
|
+
module AnyBased
|
|
4
|
+
|
|
5
|
+
def self.included(base)
|
|
6
|
+
base.send :include, InstanceMethods
|
|
7
|
+
base.extend ClassMethods
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module InstanceMethods
|
|
11
|
+
attr_reader :name, :path
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module ClassMethods
|
|
15
|
+
protected
|
|
16
|
+
def expand_path(path)
|
|
17
|
+
File.expand_path(path)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
require 'git'
|
|
2
|
+
|
|
3
|
+
module WatchTower
|
|
4
|
+
class Project
|
|
5
|
+
module GitBased
|
|
6
|
+
extend self
|
|
7
|
+
include AnyBased
|
|
8
|
+
|
|
9
|
+
# Cache for working_directory by path
|
|
10
|
+
# The key is the path to a file, the value is the working directory of
|
|
11
|
+
# this path
|
|
12
|
+
@@working_cache = Hash.new
|
|
13
|
+
|
|
14
|
+
# Cache for project_name by path
|
|
15
|
+
# The key is the path to a file, the value is the project's name
|
|
16
|
+
@@project_name_cache = Hash.new
|
|
17
|
+
|
|
18
|
+
# Cache for project git path
|
|
19
|
+
# The key is the path to a file, the value is the project's parts
|
|
20
|
+
@@project_git_folder_path = Hash.new
|
|
21
|
+
|
|
22
|
+
# Check if the path is under Git
|
|
23
|
+
#
|
|
24
|
+
# @param path The path we should check if it's under Git control
|
|
25
|
+
# @param [Hash] options A hash of options
|
|
26
|
+
# @return boolean
|
|
27
|
+
def active_for_path?(path, options = {})
|
|
28
|
+
path = expand_path path
|
|
29
|
+
project_git_folder_path(path).present?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Return the working directory (the project's path if you will) from a path
|
|
33
|
+
# to any file inside the project
|
|
34
|
+
#
|
|
35
|
+
# @param path The path to look the project path from
|
|
36
|
+
# @param [Hash] options A hash of options
|
|
37
|
+
# @return [String] the project's folder
|
|
38
|
+
def working_directory(path, options = {})
|
|
39
|
+
path = expand_path path
|
|
40
|
+
return @@working_cache[path] if @@working_cache.key?(path)
|
|
41
|
+
|
|
42
|
+
@@working_cache[path] = File.dirname(project_git_folder_path(path))
|
|
43
|
+
@@working_cache[path]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Return the project's name from a path to any file inside the project
|
|
47
|
+
#
|
|
48
|
+
# @param path The path to look the project path from
|
|
49
|
+
# @param [Hash] options A hash of options
|
|
50
|
+
# @return [String] the project's name
|
|
51
|
+
def project_name(path, options = {})
|
|
52
|
+
path = expand_path path
|
|
53
|
+
return @@project_name_cache[path] if @@project_name_cache.key?(path)
|
|
54
|
+
|
|
55
|
+
@@project_name_cache[path] = File.basename working_directory(path, options)
|
|
56
|
+
@@project_name_cache[path]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def head(path)
|
|
60
|
+
log(path)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def log(path)
|
|
64
|
+
g = ::Git.open path
|
|
65
|
+
g.log.first
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
protected
|
|
69
|
+
def project_git_folder_path(path)
|
|
70
|
+
return @@project_git_folder_path[path] if @@project_git_folder_path.key?(path)
|
|
71
|
+
|
|
72
|
+
# Define the start
|
|
73
|
+
n = 0
|
|
74
|
+
# Define the maximum search folder
|
|
75
|
+
max_n = path.split('/').size
|
|
76
|
+
|
|
77
|
+
until File.exists?(File.expand_path File.join(path, (%w{..} * n).flatten, '.git')) || n > max_n
|
|
78
|
+
n = n + 1
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
@@project_git_folder_path[path] = n <= max_n ? File.expand_path(File.join(path, (%w{..} * n).flatten, '.git')) : nil
|
|
82
|
+
@@project_git_folder_path[path]
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module WatchTower
|
|
2
|
+
class Project
|
|
3
|
+
module Init
|
|
4
|
+
def self.included(base)
|
|
5
|
+
base.extend ClassMethods
|
|
6
|
+
base.send :include, InstanceMethods
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
module ClassMethods
|
|
10
|
+
# Create a new project from a path (to a file or a folder)
|
|
11
|
+
#
|
|
12
|
+
# @param [String] path, the path to the file
|
|
13
|
+
# @return [Project] a new initialized project
|
|
14
|
+
def new_from_path(path)
|
|
15
|
+
raise FileNotFound unless path && File.exists?(path)
|
|
16
|
+
LOG.debug("#{__FILE__}:#{__LINE__}: Creating a project from #{path}")
|
|
17
|
+
if GitBased.active_for_path?(path)
|
|
18
|
+
Project.new GitBased.project_name(path), GitBased.working_directory(path)
|
|
19
|
+
else
|
|
20
|
+
Project.new PathBased.project_name(path), PathBased.working_directory(path)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
module InstanceMethods
|
|
26
|
+
# Initialize a project using a name and a path
|
|
27
|
+
#
|
|
28
|
+
# @param [String] name: the name of the project
|
|
29
|
+
# @param [String] path: The path of the project
|
|
30
|
+
def initialize(name, path)
|
|
31
|
+
LOG.debug("#{__FILE__}:#{__LINE__}: Created project #{name} located at #{path}")
|
|
32
|
+
@name = name
|
|
33
|
+
@path = path
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
module WatchTower
|
|
2
|
+
class Project
|
|
3
|
+
# The module contains Path specific project methods, methods like name, path
|
|
4
|
+
# The module can be included into another module/class or used on it's own,
|
|
5
|
+
# it does extend itself so any methods defined here is available to both class
|
|
6
|
+
# and instance level
|
|
7
|
+
module PathBased
|
|
8
|
+
include AnyBased
|
|
9
|
+
extend self
|
|
10
|
+
|
|
11
|
+
# Cache for working_directory by path
|
|
12
|
+
# The key is the path to a file, the value is the working directory of
|
|
13
|
+
# this path
|
|
14
|
+
@@working_cache = Hash.new
|
|
15
|
+
|
|
16
|
+
# Cache for project_name by path
|
|
17
|
+
# The key is the path to a file, the value is the project's name
|
|
18
|
+
@@project_name_cache = Hash.new
|
|
19
|
+
|
|
20
|
+
# Cache for project path parts
|
|
21
|
+
# The key is the path to a file, the value is the project's parts
|
|
22
|
+
@@project_path_part_cache = Hash.new
|
|
23
|
+
|
|
24
|
+
# Return the working directory (the project's path if you will) from a path
|
|
25
|
+
# to any file inside the project
|
|
26
|
+
#
|
|
27
|
+
# @param [String] path The path to look the project path from
|
|
28
|
+
# @param [Hash] options A hash of options
|
|
29
|
+
# @return [String] the project's folder
|
|
30
|
+
def working_directory(path, options = {})
|
|
31
|
+
return @@working_cache[path] if @@working_cache.key?(path)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@@working_cache[path] = project_path_from_nested_path(code_path(options),
|
|
35
|
+
path, nested_project_layers(options))
|
|
36
|
+
@@working_cache[path]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Return the project's name from a path to any file inside the project
|
|
40
|
+
#
|
|
41
|
+
# @param path The path to look the project path from
|
|
42
|
+
# @param [Hash] options A hash of options
|
|
43
|
+
# @return [String] the project's name
|
|
44
|
+
def project_name(path, options = {})
|
|
45
|
+
return @@project_name_cache[path] if @@project_name_cache.key?(path)
|
|
46
|
+
|
|
47
|
+
@@project_name_cache[path] = project_name_from_nested_path(code_path(options),
|
|
48
|
+
path, nested_project_layers(options))
|
|
49
|
+
@@project_name_cache[path]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
protected
|
|
53
|
+
|
|
54
|
+
# Get the code path from the options, if not found use the one from the
|
|
55
|
+
# configurations
|
|
56
|
+
#
|
|
57
|
+
# @param [Hash] options
|
|
58
|
+
# @return [String] The Code path
|
|
59
|
+
def code_path(options = {})
|
|
60
|
+
options[:code_path] || Config[:code_path]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Get the nested_project_layers from the options, if not found use the
|
|
64
|
+
# one from the configurations
|
|
65
|
+
#
|
|
66
|
+
# @param [Hash] options
|
|
67
|
+
# @return [String] The nested_project_layers
|
|
68
|
+
def nested_project_layers(options = {})
|
|
69
|
+
options[:nested_project_layers] || Config[:nested_project_layers]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Taken from timetap
|
|
73
|
+
# https://github.com/elia/timetap/blob/master/lib/time_tap/project.rb#L40
|
|
74
|
+
#
|
|
75
|
+
# Find out the path parts of the project that's currently being worked on,
|
|
76
|
+
# under the code path, it uses the param nested_project_layers to determine
|
|
77
|
+
# the project name from the entire expanded path to any file under the
|
|
78
|
+
# project
|
|
79
|
+
#
|
|
80
|
+
# nested project layers works "how many folders inside your code folder
|
|
81
|
+
# do you keep projects.
|
|
82
|
+
#
|
|
83
|
+
# For example, if your directory structure looks like:
|
|
84
|
+
# ~/Code/
|
|
85
|
+
# Clients/
|
|
86
|
+
# AcmeCorp/
|
|
87
|
+
# website/
|
|
88
|
+
# intranet
|
|
89
|
+
# BetaCorp/
|
|
90
|
+
# skunkworks/
|
|
91
|
+
# OpenSource/
|
|
92
|
+
# project_one/
|
|
93
|
+
# timetap/
|
|
94
|
+
#
|
|
95
|
+
# A nested_project_layers setting of 2 would mean we track "AcmeCorp", "BetaCorp", and everything
|
|
96
|
+
# under OpenSource, as their own projects
|
|
97
|
+
#
|
|
98
|
+
# @param code The path you store all the projects under
|
|
99
|
+
# @param path The path to look the project name from
|
|
100
|
+
# @param nested_project_layers How many folders, defaults to 2
|
|
101
|
+
# @return [Array] The project path's parts
|
|
102
|
+
# @raise [WatchTower::PathNotUnderCodePath] if the path is not nested under code
|
|
103
|
+
def project_path_part(code, path, nested_project_layers = 2)
|
|
104
|
+
return @@project_path_part_cache[path] if @@project_path_part_cache.key?(path)
|
|
105
|
+
|
|
106
|
+
# Expand pathes
|
|
107
|
+
code = expand_path code
|
|
108
|
+
path = expand_path path
|
|
109
|
+
|
|
110
|
+
regex_suffix = "([^/]+)"
|
|
111
|
+
regex_suffix = [regex_suffix] * nested_project_layers
|
|
112
|
+
regex_suffix = regex_suffix.join("/")
|
|
113
|
+
|
|
114
|
+
path.scan(%r{(#{code})/#{regex_suffix}}).flatten.collect(&:chomp).
|
|
115
|
+
tap { |r| raise PathNotUnderCodePath unless r.any? }.
|
|
116
|
+
tap { |ppp| @@project_path_part_cache[path] = ppp }
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Find out the project's name
|
|
120
|
+
# See #project_path_part
|
|
121
|
+
#
|
|
122
|
+
# @param code The path you store all the projects under
|
|
123
|
+
# @param path The path to look the project name from
|
|
124
|
+
# @param nested_project_layers How many folders, defaults to 2
|
|
125
|
+
# @return [String] The project's name
|
|
126
|
+
# @raise [WatchTower::PathNotUnderCodePath] if the path is not nested under code
|
|
127
|
+
def project_name_from_nested_path(code, path, nested_project_layers = 2)
|
|
128
|
+
project_path_part(code, path, nested_project_layers)[nested_project_layers]
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Find out the project's path
|
|
132
|
+
# See #project_path_part
|
|
133
|
+
#
|
|
134
|
+
# @param code The path you store all the projects under
|
|
135
|
+
# @param path The path to look the project name from
|
|
136
|
+
# @param nested_project_layers How many folders, defaults to 2
|
|
137
|
+
# @return [String] The project's path
|
|
138
|
+
# @raise [WatchTower::PathNotUnderCodePath] if the path is not nested under code
|
|
139
|
+
def project_path_from_nested_path(code, path, nested_project_layers = 2)
|
|
140
|
+
project_path_part(code, path, nested_project_layers)[0..nested_project_layers].join('/')
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module WatchTower
|
|
2
|
+
module Server
|
|
3
|
+
extend ::ActiveSupport::Autoload
|
|
4
|
+
|
|
5
|
+
autoload :Database
|
|
6
|
+
autoload :Duration, ::File.join(MODELS_PATH, 'duration.rb')
|
|
7
|
+
autoload :Project, ::File.join(MODELS_PATH, 'project.rb')
|
|
8
|
+
autoload :File, ::File.join(MODELS_PATH, 'file.rb')
|
|
9
|
+
autoload :TimeEntry, ::File.join(MODELS_PATH, 'time_entry.rb')
|
|
10
|
+
autoload :Helpers
|
|
11
|
+
autoload :Configurations
|
|
12
|
+
autoload :Decorator
|
|
13
|
+
autoload :App
|
|
14
|
+
|
|
15
|
+
# Start the server
|
|
16
|
+
# This method starts the database and then starts the server
|
|
17
|
+
#
|
|
18
|
+
# @param [Hash] options
|
|
19
|
+
def self.start(options = {})
|
|
20
|
+
# Start the Database
|
|
21
|
+
Database.start!(options)
|
|
22
|
+
|
|
23
|
+
# Start the Sinatra application
|
|
24
|
+
start_web_server(options)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Start the Server, a method invoked from the Watch Tower command line interface
|
|
28
|
+
#
|
|
29
|
+
# @param [Hash] options
|
|
30
|
+
def self.start!(options = {})
|
|
31
|
+
start(options)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
protected
|
|
35
|
+
# Start the web_server
|
|
36
|
+
# This method starts the web server (The Sinatra app)
|
|
37
|
+
#
|
|
38
|
+
# @param [Hash] options
|
|
39
|
+
def self.start_web_server(options = {})
|
|
40
|
+
LOG.debug("#{__FILE__}:#{__LINE__}: Starting the Sinatra App")
|
|
41
|
+
|
|
42
|
+
# Abort execution if the Thread raised an error.
|
|
43
|
+
Thread.abort_on_exception = true
|
|
44
|
+
|
|
45
|
+
WatchTower.threads[:web_server] = Thread.new do
|
|
46
|
+
LOG.debug("#{__FILE__}:#{__LINE__}: Starting a new Thread for the web server.")
|
|
47
|
+
|
|
48
|
+
begin
|
|
49
|
+
LOG.debug("#{__FILE__}:#{__LINE__}: Starting the web server in the new Thread.")
|
|
50
|
+
|
|
51
|
+
# Start the server
|
|
52
|
+
App.run!(options)
|
|
53
|
+
|
|
54
|
+
LOG.debug("#{__FILE__}:#{__LINE__}: The server has stopped.")
|
|
55
|
+
rescue Exception => e
|
|
56
|
+
LOG.fatal "#{__FILE__}:#{__LINE__ - 4}: #{e}"
|
|
57
|
+
raise e
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|