nutella_framework 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +4 -3
- data/VERSION +1 -1
- data/actors/main_interface/main_interface_bot.rb +148 -0
- data/actors/main_interface/public/index.html +47 -0
- data/actors/main_interface/startup +5 -0
- data/actors/main_interface/views/index.erb +45 -0
- data/actors/main_interface/views/not_found_404.erb +37 -0
- data/lib/cli/nutella_cli.rb +24 -15
- data/lib/config/config.rb +5 -67
- data/lib/config/current_project.rb +58 -0
- data/lib/config/persisted_hash.rb +114 -0
- data/lib/config/runlist.rb +19 -83
- data/lib/core/command.rb +2 -2
- data/lib/core/commands/broker.rb +23 -24
- data/lib/core/commands/checkup.rb +36 -37
- data/lib/core/commands/help.rb +3 -3
- data/lib/core/commands/install.rb +3 -4
- data/lib/core/commands/new.rb +26 -28
- data/lib/core/commands/runs.rb +22 -31
- data/lib/core/commands/start.rb +134 -121
- data/lib/core/commands/stop.rb +62 -48
- data/lib/core/nutella_core.rb +23 -12
- data/lib/core/run_command.rb +44 -0
- data/lib/core/tmux.rb +20 -18
- data/lib/logging/nutella_logging.rb +5 -5
- data/lib/nutella_framework.rb +8 -6
- data/nutella_framework.gemspec +15 -12
- data/test/config/test_config.rb +23 -22
- data/test/config/test_project.rb +13 -12
- data/test/config/test_runlist.rb +30 -22
- metadata +15 -12
- data/lib/config/project.rb +0 -53
- data/lib/extra/config.ru +0 -2
- data/lib/extra/interfaces_list.erubis +0 -14
- data/lib/extra/main_interface.rb +0 -9
- data/test/test_nutella_framework.rb +0 -31
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2e2c581262282b014fb5dc0911616b442534da8c
|
4
|
+
data.tar.gz: 3908173709fc9ee02ba4cd339615dde180e4776c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5715373d4edeac20b922d507261b496bb961bc71c3d812e3d5ffd74ddf5ed0904cf096b8929e10788bdae78750e39b40427af224ca65bf46386e3956c8aa0872
|
7
|
+
data.tar.gz: d7e8d4b278eb8df7275f209cc21828bad2e678ee0116c3d45556245713b8d835256285df2522ded4487607ec0cbf14f22ff95409b2270c4b04bb343861728d11
|
data/Gemfile
CHANGED
@@ -6,11 +6,12 @@ gem 'logging', '~> 1.8', '>=1.8.2'
|
|
6
6
|
gem 'git', '~> 1.2', '>=1.2.8'
|
7
7
|
gem 'thin', '~> 1.6.3', '>= 1.6.3'
|
8
8
|
gem 'sinatra', '~>1.4.5', '>=1.4.5'
|
9
|
-
gem '
|
9
|
+
gem 'nokogiri', '~>1.6.3', '>=1.6.3'
|
10
|
+
|
10
11
|
|
11
12
|
group :development do
|
12
13
|
gem 'shoulda', '~> 3', '>= 3'
|
13
|
-
gem
|
14
|
+
gem 'yard', '~> 0.8', '>= 0.8.7'
|
14
15
|
gem 'rdoc', '~> 4.0', '>= 4.0'
|
15
16
|
gem 'bundler', '~> 1.0', '>= 1.0'
|
16
17
|
gem 'jeweler', '~> 2.0.1', '>= 2.0.1'
|
@@ -18,5 +19,5 @@ group :development do
|
|
18
19
|
end
|
19
20
|
|
20
21
|
group :test do
|
21
|
-
gem
|
22
|
+
gem 'rake'
|
22
23
|
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
@@ -0,0 +1,148 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'sinatra'
|
3
|
+
require 'nokogiri'
|
4
|
+
|
5
|
+
# Configuration file and runlist (runlist as global)
|
6
|
+
config_file = ARGV[0]
|
7
|
+
$runlist_file = ARGV[1]
|
8
|
+
|
9
|
+
# Try to parse the both config file and runlist and terminate if we can't
|
10
|
+
begin
|
11
|
+
config_h = JSON.parse(IO.read(config_file))
|
12
|
+
JSON.parse(IO.read($runlist_file))
|
13
|
+
rescue
|
14
|
+
# something went wrong
|
15
|
+
abort 'Impossible to parse configuration and/or runlist files!'
|
16
|
+
end
|
17
|
+
|
18
|
+
# Set Sinatra's port to nutella's main_interface_port
|
19
|
+
set :port, config_h['main_interface_port']
|
20
|
+
|
21
|
+
|
22
|
+
# Display the form to input the run_id
|
23
|
+
get '/' do
|
24
|
+
send_file File.join("#{config_h['nutella_home']}/actors/main_interface/public", 'index.html')
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
# Renders the run template
|
29
|
+
get '/:run_id' do
|
30
|
+
|
31
|
+
# Parse the run_id from URL and extract the run path from runlist.json
|
32
|
+
@run_id = params[:run_id]
|
33
|
+
@run_path = get_run_path @run_id
|
34
|
+
@project_name = @run_path[@run_path.rindex('/')+1..@run_path.length] unless @run_path.nil?
|
35
|
+
|
36
|
+
# If there is no run with this name, render error page
|
37
|
+
return erb( :not_found_404, :locals => {:not_found_type => 'run'} ) if @run_path.nil?
|
38
|
+
|
39
|
+
# If there is an 'index.erb' file inside the 'interfaces' folder, render it
|
40
|
+
custom_index_file = "#{@run_path}/interfaces/index.erb"
|
41
|
+
return erb(File.read( custom_index_file )) if File.exist? custom_index_file
|
42
|
+
|
43
|
+
# If no 'index.erb' is provided we need to generate one
|
44
|
+
# In order to do so we need to load a bunch of details
|
45
|
+
# (folder, title/name, description) for each interface
|
46
|
+
@interfaces = load_interfaces_details
|
47
|
+
|
48
|
+
# Finally render the interfaces summary page
|
49
|
+
erb :index
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
# Serves the index.html file for each individual interface augmented with nutella query string parameters
|
54
|
+
get '/:run_id/:interface' do
|
55
|
+
|
56
|
+
# Parse the run_id and the interface name from URL
|
57
|
+
run_id = params[:run_id]
|
58
|
+
interface = params[:interface]
|
59
|
+
|
60
|
+
# Extract the run path from runlist.json
|
61
|
+
run_path = get_run_path run_id
|
62
|
+
|
63
|
+
# Compose the path of interface index file
|
64
|
+
index_file_path = "#{run_path}/interfaces/#{interface}/index.html"
|
65
|
+
|
66
|
+
# If the index file doesn't exist, render error page
|
67
|
+
return erb( :not_found_404, :locals => {:not_found_type => 'idx'} ) unless File.exist? index_file_path
|
68
|
+
|
69
|
+
# If the index file exists, compose query string and redirect
|
70
|
+
index_with_query_url = "#{request.path}/index.html?run_id=#{run_id}&broker=#{config_h['broker']}"
|
71
|
+
redirect index_with_query_url
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
# Serves the files contained in each interface folder
|
76
|
+
get '/:run_id/:interface/*' do
|
77
|
+
|
78
|
+
# Parse the run_id, the interface name and the file_path from URL
|
79
|
+
run_id = params[:run_id]
|
80
|
+
interface = params[:interface]
|
81
|
+
relative_file_path = params[:splat][0]
|
82
|
+
|
83
|
+
# Extract the run path from runlist.json
|
84
|
+
run_path = get_run_path run_id
|
85
|
+
|
86
|
+
# Compose the path of the file we are trying to serve
|
87
|
+
file_path = "#{run_path}/interfaces/#{interface}/#{relative_file_path}"
|
88
|
+
|
89
|
+
# If the file we are trying to serve doesn't exist, render error page
|
90
|
+
return erb( :not_found_404, :locals => {:not_found_type => 'file'} ) unless File.exist? file_path
|
91
|
+
|
92
|
+
# If the file exists, render it
|
93
|
+
send_file file_path
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
# Utility function:
|
98
|
+
# Gets the path associated with a certain run
|
99
|
+
def get_run_path (run_id)
|
100
|
+
begin
|
101
|
+
runs_h = JSON.parse(IO.read($runlist_file))
|
102
|
+
runs_h[run_id]
|
103
|
+
rescue
|
104
|
+
nil
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Utility function:
|
109
|
+
# Loads all the details for all interfaces and stores them into an array of hashes
|
110
|
+
def load_interfaces_details
|
111
|
+
interfaces = Array.new
|
112
|
+
interfaces_path = "#{@run_path}/interfaces/"
|
113
|
+
Dir.entries(interfaces_path).select {|entry| File.directory?(File.join(interfaces_path, entry)) && !(entry =='.' || entry == '..') }.each do |iface_dir|
|
114
|
+
interfaces.push extract_interface_info( interfaces_path, iface_dir )
|
115
|
+
end
|
116
|
+
interfaces
|
117
|
+
end
|
118
|
+
|
119
|
+
# Utility function:
|
120
|
+
# Extracts name, description and folder for a single interface
|
121
|
+
def extract_interface_info( interfaces_path, iface_dir )
|
122
|
+
iface_props = Hash.new
|
123
|
+
|
124
|
+
index_path = "#{interfaces_path}#{iface_dir}/index.html"
|
125
|
+
|
126
|
+
unless File.exist? index_path
|
127
|
+
iface_props[:name] = iface_dir
|
128
|
+
iface_props[:description] = 'My designer was a lazy ass and didn\'t include an index.html file in the main interface directory'
|
129
|
+
return iface_props
|
130
|
+
end
|
131
|
+
|
132
|
+
# If file exists, parse it and extract info
|
133
|
+
f = File.open index_path
|
134
|
+
doc = Nokogiri::HTML f
|
135
|
+
f.close
|
136
|
+
iface_props[:name] = doc.css('title').empty? ? iface_dir : doc.css('title').text
|
137
|
+
if doc.css("meta[name='description']").empty?
|
138
|
+
iface_props[:description] = 'My designer was a lazy ass and didn\'t include a <meta name="description" content="Description of this interface"> tag in the index.html file'
|
139
|
+
else
|
140
|
+
if doc.css("meta[name='description']").attribute('content').nil?
|
141
|
+
iface_props[:description] = 'There was no attribute content in <meta name="description" content="Description of this interface"> tag in the index.html file'
|
142
|
+
else
|
143
|
+
iface_props[:description] = doc.css("meta[name='description']").attribute('content').text
|
144
|
+
end
|
145
|
+
end
|
146
|
+
iface_props[:url] = "#{@run_id}/#{iface_dir}"
|
147
|
+
iface_props
|
148
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8">
|
5
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
7
|
+
<title>Welcome to nutella</title>
|
8
|
+
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css">
|
9
|
+
|
10
|
+
<style type="text/css">
|
11
|
+
div {
|
12
|
+
margin-top: 4em;
|
13
|
+
text-align: center;
|
14
|
+
font-size: 3em;
|
15
|
+
}
|
16
|
+
.form-control {
|
17
|
+
height: 50px;
|
18
|
+
width: 40%;
|
19
|
+
text-align: center;
|
20
|
+
font-size: .7em;
|
21
|
+
display: inline-block;
|
22
|
+
}
|
23
|
+
.center {
|
24
|
+
text-align:center;
|
25
|
+
}
|
26
|
+
.center form {
|
27
|
+
display:inline-block;
|
28
|
+
}
|
29
|
+
</style>
|
30
|
+
</head>
|
31
|
+
<body>
|
32
|
+
|
33
|
+
<!-- Content -->
|
34
|
+
<div>
|
35
|
+
<p>What is your run id?</p>
|
36
|
+
<input id="run_id_input" type="text" value="" class="form-control" autofocus>
|
37
|
+
</div>
|
38
|
+
|
39
|
+
<!-- Scripts -->
|
40
|
+
<script>
|
41
|
+
el = document.getElementById('run_id_input');
|
42
|
+
el.addEventListener('change', function() {
|
43
|
+
window.location = '/' + this.value
|
44
|
+
});
|
45
|
+
</script>
|
46
|
+
</body>
|
47
|
+
</html>
|
@@ -0,0 +1,45 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8">
|
5
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
7
|
+
<title><%= @run_id %> interfaces</title>
|
8
|
+
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css">
|
9
|
+
|
10
|
+
<style type="text/css">
|
11
|
+
.row {
|
12
|
+
margin-bottom: 20px;
|
13
|
+
}
|
14
|
+
</style>
|
15
|
+
</head>
|
16
|
+
<body>
|
17
|
+
|
18
|
+
<!-- Content -->
|
19
|
+
<div class="container">
|
20
|
+
<div class="page-header">
|
21
|
+
<% if @run_id == @project_name %>
|
22
|
+
<h1>Welcome to <em><%= @project_name %></em></h1>
|
23
|
+
<% else %>
|
24
|
+
<h1>Welcome to <em><%= @project_name %></em>, run <em><%= @run_id %></em></h1>
|
25
|
+
<% end %>
|
26
|
+
<p class="lead">Click on the buttons to launch the intereface</p>
|
27
|
+
</div>
|
28
|
+
|
29
|
+
<% @interfaces.each do |i| %>
|
30
|
+
<div class="row">
|
31
|
+
<div class="col-xs-1 col-sm-1 col-lg-1"><a href="<%= i[:url] %>" target="_blank" type="button" class="btn btn-danger">Launch!</a></div>
|
32
|
+
<div class="col-xs-2 col-sm-2 col-lg-2 text-center"><strong><%= i[:name] %></strong></div>
|
33
|
+
<div class="col-xs-9 col-sm-9 col-lg-9"><%= i[:description] %></div>
|
34
|
+
</div>
|
35
|
+
|
36
|
+
<% end %>
|
37
|
+
|
38
|
+
</div>
|
39
|
+
|
40
|
+
<!-- Scripts -->
|
41
|
+
<script>
|
42
|
+
|
43
|
+
</script>
|
44
|
+
</body>
|
45
|
+
</html>
|
@@ -0,0 +1,37 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8">
|
5
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
7
|
+
<title>Run not found</title>
|
8
|
+
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css">
|
9
|
+
|
10
|
+
<style type="text/css">
|
11
|
+
div {
|
12
|
+
margin-top: 4em;
|
13
|
+
text-align: center;
|
14
|
+
font-size: 3em;
|
15
|
+
}
|
16
|
+
</style>
|
17
|
+
</head>
|
18
|
+
<body>
|
19
|
+
|
20
|
+
<!-- Content -->
|
21
|
+
<div>
|
22
|
+
<% if locals[:not_found_type] == 'run' %>
|
23
|
+
<p>Sorry, we don't have that run ID!</p>
|
24
|
+
<p>(?_?)</p>
|
25
|
+
<% elsif locals[:not_found_type] == 'idx' %>
|
26
|
+
<p>Sorry, this interface doesn't seem to exist for this application</p>
|
27
|
+
<p>(゜_゜)</p>
|
28
|
+
<% elsif locals[:not_found_type] == 'file' %>
|
29
|
+
<p>The page you are looking for doesn't seem to exist</p>
|
30
|
+
<p>(;_;)</p>
|
31
|
+
<% end %>
|
32
|
+
|
33
|
+
|
34
|
+
</div>
|
35
|
+
|
36
|
+
</body>
|
37
|
+
</html>
|
data/lib/cli/nutella_cli.rb
CHANGED
@@ -10,33 +10,42 @@ module Nutella
|
|
10
10
|
|_| |_|\\__,_|\\__\\___|_|_|\\__,_|
|
11
11
|
"
|
12
12
|
|
13
|
-
#
|
13
|
+
# Nutella entry point. Every time the "nutella" command is invoked this is
|
14
|
+
# the method that gets called.
|
15
|
+
# It reads the command line parameters and it invokes the right sub-command
|
14
16
|
def self.run
|
15
17
|
# Read parameters
|
16
18
|
args = ARGV.dup
|
17
19
|
args.shift
|
18
|
-
|
20
|
+
|
21
|
+
# Check that the command is not empty, if so, simply print the nutella logo
|
19
22
|
command = ARGV.first
|
20
23
|
if command == nil
|
21
|
-
|
24
|
+
print_nutella_logo
|
22
25
|
exit 0
|
23
26
|
end
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
+
|
28
|
+
# If nutella is not ready to be used (i.e. nobody has invoked the "nutella checkup" command yet),
|
29
|
+
# append warning/reminder message
|
30
|
+
if Nutella.config['ready'].nil? && command!='checkup'
|
31
|
+
console.warn 'Looks like this is a fresh installation of nutella. Please run \'nutella checkup\' to check all dependencies are installed.'
|
27
32
|
end
|
28
|
-
|
33
|
+
|
34
|
+
# Execute the appropriate command
|
35
|
+
Nutella.execute_command command, args
|
29
36
|
exit 0
|
30
37
|
end
|
31
|
-
|
32
|
-
|
33
|
-
|
38
|
+
|
39
|
+
|
40
|
+
# Print nutella logo
|
41
|
+
def self.print_nutella_logo
|
34
42
|
console.info(NUTELLA_LOGO)
|
35
|
-
nutella_version = File.open(
|
36
|
-
console.info("Welcome to nutella version #{nutella_version}! For a complete lists of available commands type
|
37
|
-
#
|
38
|
-
|
39
|
-
|
43
|
+
nutella_version = File.open("#{Nutella.config['nutella_home']}VERSION", 'rb').read
|
44
|
+
console.info("Welcome to nutella version #{nutella_version}! For a complete lists of available commands type 'nutella help'\n")
|
45
|
+
# If nutella is not ready to be used (i.e. nobody has invoked the "nutella checkup" command yet),
|
46
|
+
# append warning/reminder message
|
47
|
+
if Nutella.config['ready'].nil?
|
48
|
+
console.warn 'Looks like this is a fresh installation of nutella. Please run \'nutella checkup\' to check all dependencies are installed.'
|
40
49
|
end
|
41
50
|
end
|
42
51
|
end
|
data/lib/config/config.rb
CHANGED
@@ -1,73 +1,11 @@
|
|
1
|
-
|
2
|
-
# It's basically a hash overload that stores into a file
|
3
|
-
require 'json'
|
1
|
+
require 'config/persisted_hash'
|
4
2
|
|
5
3
|
module Nutella
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
def initialize(file)
|
10
|
-
@config_file=file
|
11
|
-
end
|
12
|
-
|
13
|
-
|
14
|
-
def []=(key,val)
|
15
|
-
hash = loadConfig
|
16
|
-
hash[key]=val
|
17
|
-
storeConfig hash
|
18
|
-
end
|
19
|
-
|
20
|
-
def [](key)
|
21
|
-
hash = loadConfig
|
22
|
-
hash[key]
|
23
|
-
end
|
24
|
-
|
25
|
-
def empty?
|
26
|
-
hash = loadConfig
|
27
|
-
hash.empty?
|
28
|
-
end
|
29
|
-
|
30
|
-
def has_key?(key)
|
31
|
-
hash = loadConfig
|
32
|
-
hash.has_key? key
|
33
|
-
end
|
34
|
-
|
35
|
-
def to_s
|
36
|
-
hash = loadConfig
|
37
|
-
hash.to_s
|
38
|
-
end
|
39
|
-
|
40
|
-
def to_h
|
41
|
-
hash = loadConfig
|
42
|
-
hash
|
43
|
-
end
|
44
|
-
|
45
|
-
private
|
46
|
-
|
47
|
-
def storeConfig(hash)
|
48
|
-
File.open(@config_file, "w+") do |f|
|
49
|
-
f.write(JSON.pretty_generate(hash))
|
50
|
-
end
|
51
|
-
File.chmod(0777,@config_file)
|
52
|
-
end
|
53
|
-
|
54
|
-
def loadConfig
|
55
|
-
begin
|
56
|
-
return JSON.parse IO.read @config_file
|
57
|
-
rescue
|
58
|
-
# File doesn't exist... do nothing
|
59
|
-
Hash.new
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
def removeConfigFile
|
64
|
-
File.delete(@config_file) if File.exist?(@config_file)
|
65
|
-
end
|
66
|
-
|
67
|
-
end
|
68
|
-
|
4
|
+
|
5
|
+
# Calling this method (Nutella.config) simply returns and instance of
|
6
|
+
# PersistedHash linked to file config.json in nutella home directory
|
69
7
|
def Nutella.config
|
70
|
-
|
8
|
+
PersistedHash.new("#{File.dirname(__FILE__)}/../../config.json")
|
71
9
|
end
|
72
10
|
|
73
11
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# handles current project files
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Nutella
|
5
|
+
|
6
|
+
module CurrentProjectUtils
|
7
|
+
|
8
|
+
# Checks that the current directory is actually a nutella project
|
9
|
+
# @return [Boolean] true if the current directory is a nutella project, false otherwise
|
10
|
+
def CurrentProjectUtils.exist?
|
11
|
+
cur_prj_dir = Dir.pwd
|
12
|
+
nutella_json_file = "#{cur_prj_dir}/nutella.json"
|
13
|
+
# Check that there is a nutella.json file in the main directory of the project
|
14
|
+
if File.exist? nutella_json_file
|
15
|
+
conf = JSON.parse( IO.read(nutella_json_file) )
|
16
|
+
if conf['nutella_version'].nil?
|
17
|
+
console.warn 'The current directory is not a nutella project: nutella_version unspecified in nutella.json file'
|
18
|
+
return false
|
19
|
+
end
|
20
|
+
else
|
21
|
+
console.warn 'The current directory is not a nutella project: impossible to read nutella.json file'
|
22
|
+
return false
|
23
|
+
end
|
24
|
+
true
|
25
|
+
end
|
26
|
+
|
27
|
+
# Builds a PersistedHash of the project nutella.json file and returns it.
|
28
|
+
# This method is used to ease access to the project nutella.json file.
|
29
|
+
# @return [PersistedHash] the PersistedHash of the project nutella.json file
|
30
|
+
def CurrentProjectUtils.config
|
31
|
+
cur_prj_dir = Dir.pwd
|
32
|
+
nutella_json_file = "#{cur_prj_dir}/nutella.json"
|
33
|
+
if File.exist? nutella_json_file
|
34
|
+
return PersistedHash.new nutella_json_file
|
35
|
+
else
|
36
|
+
console.error 'The current directory is not a nutella project: impossible to read nutella.json file'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Retrieves the current project directory
|
41
|
+
# @return [String] the current project home
|
42
|
+
def CurrentProjectUtils.dir
|
43
|
+
Dir.pwd
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
# Calling this method (Nutella.current_project) simply returns
|
50
|
+
# a reference to the CurrentProjectUtils module
|
51
|
+
def Nutella.current_project
|
52
|
+
CurrentProjectUtils
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Nutella
|
4
|
+
|
5
|
+
# This class behaves *similarly* to a regular Hash but it persists every operation
|
6
|
+
# to the json file passed in the constructor. Not all Hash operations are supported
|
7
|
+
# and we added some of our own.
|
8
|
+
class PersistedHash
|
9
|
+
|
10
|
+
def initialize(file)
|
11
|
+
@config_file=file
|
12
|
+
end
|
13
|
+
|
14
|
+
def []( key )
|
15
|
+
hash = load_hash
|
16
|
+
hash[key]
|
17
|
+
end
|
18
|
+
|
19
|
+
def []=( key, val )
|
20
|
+
hash = load_hash
|
21
|
+
hash[key]=val
|
22
|
+
store_hash hash
|
23
|
+
end
|
24
|
+
|
25
|
+
def delete( key )
|
26
|
+
hash = load_hash
|
27
|
+
return_value = hash.delete key
|
28
|
+
store_hash hash
|
29
|
+
return_value
|
30
|
+
end
|
31
|
+
|
32
|
+
def empty?
|
33
|
+
hash = load_hash
|
34
|
+
hash.empty?
|
35
|
+
end
|
36
|
+
|
37
|
+
def has_key?( key )
|
38
|
+
hash = load_hash
|
39
|
+
hash.has_key? key
|
40
|
+
end
|
41
|
+
|
42
|
+
def include?( key )
|
43
|
+
has_key? key
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_s
|
47
|
+
hash = load_hash
|
48
|
+
hash.to_s
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_h
|
52
|
+
load_hash
|
53
|
+
end
|
54
|
+
|
55
|
+
def keys
|
56
|
+
hash = load_hash
|
57
|
+
hash.keys
|
58
|
+
end
|
59
|
+
|
60
|
+
def length
|
61
|
+
hash = load_hash
|
62
|
+
hash.length
|
63
|
+
end
|
64
|
+
|
65
|
+
# PersistedHash-only public methods
|
66
|
+
|
67
|
+
# Adds a <key, value> pair to the PersistedHash _only if_
|
68
|
+
# there is currently no value associated with the specified key.
|
69
|
+
# @return [Boolean] false if the key already exists, true if the
|
70
|
+
# <key, value> pair was added successfully
|
71
|
+
def add?(key, val)
|
72
|
+
hash = load_hash
|
73
|
+
return false if hash.key? key
|
74
|
+
hash[key] = val
|
75
|
+
store_hash hash
|
76
|
+
true
|
77
|
+
end
|
78
|
+
|
79
|
+
# Removes a <key, value> pair from the PersistedHash _only if_
|
80
|
+
# there is currently a value associated with the specified key.
|
81
|
+
# @return [Boolean] false if there is no value associated with
|
82
|
+
# the specified key, true otherwise
|
83
|
+
def delete?( key )
|
84
|
+
hash = load_hash
|
85
|
+
return false if hash.delete(key).nil?
|
86
|
+
store_hash hash
|
87
|
+
true
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def store_hash(hash)
|
93
|
+
File.open(@config_file, 'w+') do |f|
|
94
|
+
f.write(JSON.pretty_generate(hash))
|
95
|
+
end
|
96
|
+
File.chmod(0777, @config_file)
|
97
|
+
end
|
98
|
+
|
99
|
+
def load_hash
|
100
|
+
begin
|
101
|
+
return JSON.parse IO.read @config_file
|
102
|
+
rescue
|
103
|
+
# File doesn't exist, return new empty Hash
|
104
|
+
Hash.new
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def remove_file
|
109
|
+
File.delete(@config_file) if File.exist?(@config_file)
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|