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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b06d936ef60bf41dac3eb872609ba9906a50cecd
4
- data.tar.gz: e7a51bdbff02a0fa306eaf5267f80168c3563e30
3
+ metadata.gz: 2e2c581262282b014fb5dc0911616b442534da8c
4
+ data.tar.gz: 3908173709fc9ee02ba4cd339615dde180e4776c
5
5
  SHA512:
6
- metadata.gz: 23c35cb1768d1469bd29bd27b3eb4b8686c911082ec233556e9526188b544beee945bc2ff8005f588d64f0ed25f97fb41a4e3bafbd8b8c2be22a71f97003f67b
7
- data.tar.gz: 23aa14c916de0d105faf807bf548ea9f6c1249868609a80756d4ba7a53b578a77f330c08080408a6bda34eaccaf920a8687514a052462ea5d8cd593ddaa351a6
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 'erubis', '~>2.7.0', '>=2.7.0'
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 "yard", '~> 0.8', '>= 0.8.7'
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 "rake"
22
+ gem 'rake'
22
23
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.2
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,5 @@
1
+ #!/bin/sh
2
+
3
+ BASEDIR=$(dirname $0)
4
+ ruby $BASEDIR/main_interface_bot.rb $1 $2 &>/dev/null &
5
+ echo $! > $BASEDIR/.pid
@@ -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>
@@ -10,33 +10,42 @@ module Nutella
10
10
  |_| |_|\\__,_|\\__\\___|_|_|\\__,_|
11
11
  "
12
12
 
13
- # Reads the parameters and executes commands
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
- # Check that the command is not empty, if so, print the prompt
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
- printPrompt
24
+ print_nutella_logo
22
25
  exit 0
23
26
  end
24
- # Prepend warning if nutella is not ready
25
- if (Nutella.config["ready"].nil? && command!="checkup")
26
- console.warn "Looks like this is a fresh installation of nutella. Please run `nutella checkup` to check all dependencies are installed."
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
- Nutella.executeCommand command, args
33
+
34
+ # Execute the appropriate command
35
+ Nutella.execute_command command, args
29
36
  exit 0
30
37
  end
31
-
32
- # Print Nutella logo
33
- def self.printPrompt
38
+
39
+
40
+ # Print nutella logo
41
+ def self.print_nutella_logo
34
42
  console.info(NUTELLA_LOGO)
35
- nutella_version = File.open(NUTELLA_HOME+"VERSION", "rb").read
36
- console.info("Welcome to nutella version #{nutella_version}! For a complete lists of available commands type `nutella help`\n")
37
- # Append warning if nutella is not ready
38
- if (Nutella.config["ready"].nil?)
39
- console.warn "Looks like this is a fresh installation of nutella. Please run `nutella checkup` to check all dependencies are installed."
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
- # This handles the configuration files of Nutella
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
- class ConfigHash
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
- ConfigHash.new(File.dirname(__FILE__)+"/../../config.json")
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