nutella_framework 0.1.2 → 0.2.0
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.
- 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
|