nutella_framework 0.3.1 → 0.4.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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -0
  3. data/README.md +3 -4
  4. data/VERSION +1 -1
  5. data/data/startup +3 -3
  6. data/framework_components/example_framework_interface/dandelion-flowers-card.jpg +0 -0
  7. data/framework_components/example_framework_interface/index.html +18 -0
  8. data/framework_components/main_interface/main_interface_bot.rb +183 -0
  9. data/framework_components/main_interface/public/index.html +54 -0
  10. data/framework_components/main_interface/views/index.erb +63 -0
  11. data/framework_components/order.json.example +6 -0
  12. data/framework_components/runs_list_bot/app_runs_list_bot.rb +15 -0
  13. data/lib/{core/commands → commands}/broker.rb +2 -2
  14. data/lib/{core/commands → commands}/checkup.rb +2 -2
  15. data/lib/commands/compile.rb +13 -0
  16. data/lib/commands/dependencies.rb +13 -0
  17. data/lib/commands/help.rb +35 -0
  18. data/lib/{core/commands → commands}/install.rb +19 -15
  19. data/lib/commands/meta/command.rb +19 -0
  20. data/lib/commands/meta/run_command.rb +114 -0
  21. data/lib/{core → commands/meta}/template_command.rb +1 -1
  22. data/lib/commands/new.rb +60 -0
  23. data/lib/commands/runs.rb +54 -0
  24. data/lib/commands/start.rb +321 -0
  25. data/lib/commands/stop.rb +101 -0
  26. data/lib/{core/commands → commands}/template.rb +59 -39
  27. data/lib/config/current_app_utils.rb +51 -0
  28. data/lib/config/persisted_hash.rb +14 -12
  29. data/lib/config/runlist.rb +116 -16
  30. data/lib/{cli → core}/nutella_cli.rb +1 -1
  31. data/lib/core/nutella_core.rb +2 -6
  32. data/lib/nutella_framework.rb +5 -3
  33. data/lib/nutella_lib_framework/api.rb +333 -0
  34. data/lib/tmux/tmux.rb +76 -0
  35. data/nutella_framework.gemspec +42 -29
  36. data/test/commands/test_cmd_cli_params_parsing.rb +56 -0
  37. data/test/commands/test_command_template.rb +31 -0
  38. data/test/config/test_current_app_utils.rb +34 -0
  39. data/test/config/test_persisted_hash.rb +48 -0
  40. data/test/config/test_runlist.rb +15 -23
  41. data/test/framework_apis/test_framework_api.rb +74 -0
  42. metadata +74 -27
  43. data/actors/main_interface/main_interface_bot.rb +0 -163
  44. data/actors/main_interface/public/index.html +0 -51
  45. data/actors/main_interface/views/index.erb +0 -45
  46. data/lib/config/current_project.rb +0 -58
  47. data/lib/core/command.rb +0 -12
  48. data/lib/core/commands/compile.rb +0 -21
  49. data/lib/core/commands/dependencies.rb +0 -21
  50. data/lib/core/commands/help.rb +0 -28
  51. data/lib/core/commands/new.rb +0 -60
  52. data/lib/core/commands/runs.rb +0 -52
  53. data/lib/core/commands/start.rb +0 -271
  54. data/lib/core/commands/stop.rb +0 -100
  55. data/lib/core/run_command.rb +0 -106
  56. data/lib/core/tmux.rb +0 -38
  57. data/test/config/test_config.rb +0 -48
  58. data/test/config/test_project.rb +0 -34
  59. data/test/test_run_command.rb +0 -54
  60. /data/{actors → framework_components}/main_interface/startup +0 -0
  61. /data/{actors → framework_components}/main_interface/views/not_found_404.erb +0 -0
@@ -1,9 +1,9 @@
1
- require 'core/template_command'
1
+ require 'commands/meta/template_command'
2
2
 
3
3
  module Nutella
4
4
 
5
5
  class Template < TemplateCommand
6
- @description = 'Helps create and validate templates'
6
+ @description = 'Creates and validates nutella components templates'
7
7
 
8
8
  def run(args=nil)
9
9
 
@@ -30,9 +30,9 @@ module Nutella
30
30
  private
31
31
 
32
32
  def display_help
33
- console.info 'You need to specify a sub command.'
34
- console.info 'create <template_name> creates a template skeleton in the folder with the same name'
35
- console.info 'validate <temple_dir> validates a template that already exists'
33
+ console.warn 'You need to specify a sub command.'
34
+ console.warn 'create <template_name> creates a template skeleton in the folder with the same name'
35
+ console.warn 'validate <temple_dir> validates a template that already exists'
36
36
  end
37
37
 
38
38
 
@@ -45,73 +45,93 @@ module Nutella
45
45
  end
46
46
 
47
47
 
48
- def create_template_sub_command( name_d )
49
- ver_d = '0.1.0'
50
- type_d = 'bot'
48
+ def create_template_sub_command( template_name )
49
+ @default_version = '0.1.0'
50
+ @default_type = 'bot'
51
+ # Get template parameters from command line
52
+ name = prompt_and_read_name template_name
53
+ version = prompt_and_read_version
54
+ type = prompt_and_read_type
55
+ description = prompt_and_read_description type
56
+ repo = prompt_and_read_repo
57
+ # Build JSON
58
+ json = build_nutella_json( name, version, type, description, repo )
59
+ # Show confirmation and read
60
+ prompt_and_read_confirm json
61
+
62
+ end
51
63
 
52
- # Name
64
+
65
+ def prompt_and_read_name(template_name)
53
66
  puts 'What is the name of your template?'
54
- print "(#{name_d}) "
55
- c = $stdin.gets.chomp!
56
- name = c.empty? ? name_d : c
57
- # Version
58
- puts 'What is the version of your template?'
59
- print "(#{ver_d}) "
67
+ print "(#{template_name}) "
60
68
  c = $stdin.gets.chomp!
61
- version = c.empty? ? ver_d : c
62
- # Type
63
- puts 'Are you creating a template for a "bot" or "interface"?'
64
- print "(#{type_d}) "
69
+ c.empty? ? template_name : c
70
+ end
71
+
72
+ def prompt_and_read_version
73
+ prompt_and_read 'What is the version of your template?', @default_version
74
+ end
75
+
76
+ def prompt_and_read_type
77
+ prompt_and_read 'Are you creating a template for a "bot" or "interface"?', @default_type
78
+ end
79
+
80
+ def prompt_and_read( prompt, default )
81
+ puts prompt
82
+ print "(#{default}) "
65
83
  c = $stdin.gets.chomp!
66
- type = c.empty? ? type_d : c
67
- # Description
84
+ c.empty? ? default : c
85
+ end
86
+
87
+ def prompt_and_read_description(type)
68
88
  puts "Do you want to provide a short description for your #{type} template?"
69
89
  print '(optional, hit enter if no description) '
70
- description = $stdin.gets.chomp!
71
- # Repo
90
+ $stdin.gets.chomp!
91
+ end
92
+
93
+ def prompt_and_read_repo
72
94
  puts 'Do you want to provide a git repository for your template?'
73
95
  print '(optional, hit enter if no repo) '
74
- repo = $stdin.gets.chomp!
96
+ $stdin.gets.chomp!
97
+ end
75
98
 
76
- # Build JSON and show confirmation
99
+ def prompt_and_read_confirm(json)
77
100
  puts 'Looks good?'
78
- json = build_nutella_json( name, version, type, description, repo )
79
101
  puts JSON.pretty_generate json
80
102
  print '(yes/no) '
81
-
82
- # If user confirms, create template
83
103
  confirm = $stdin.gets.chomp!
84
104
  if confirm=='yes'
85
- create_template_files json
105
+ create_template json
86
106
  else
87
107
  console.warn 'Template creation aborted'
88
108
  end
89
-
90
109
  end
91
110
 
92
-
93
- def create_template_files( json )
111
+ def create_template( json )
94
112
  # First validate the JSON
95
113
  unless validate_nutella_file_json json
96
114
  console.error 'Something was wrong with your nutella.json file. Template creation aborted'
97
115
  return
98
116
  end
99
- # Assemble destination directory
117
+ # Check that the template directory doesn't already exist
100
118
  template_dir = File.join( Dir.pwd, json['name'] )
101
- # Check that the directory doesn't exist already
102
- if File.directory?(template_dir)
119
+ if File.directory? template_dir
103
120
  console.error("The directory #{template_dir} already exists! Can't create template #{json[:name]}")
104
121
  return
105
122
  end
123
+ # Create template directory and files
124
+ create_template_files(json, template_dir)
125
+ end
126
+
127
+ def create_template_files(json, template_dir)
106
128
  # Create directory
107
129
  Dir.mkdir template_dir
108
130
  # Create nutella.json file
109
131
  File.open("#{template_dir}/nutella.json", 'w') { |f| f.write(JSON.pretty_generate json) }
110
132
  # Add bot/interface specific files
111
- bot_specific_file = nil
112
- bot_specific_file = File.join( Nutella.config['nutella_home'], 'data/startup' ) if json['type']=='bot'
113
- bot_specific_file = File.join( Nutella.config['nutella_home'], 'data/index.html' ) if json['type']=='interface'
114
- FileUtils.copy( bot_specific_file, template_dir )
133
+ FileUtils.copy(File.join(Nutella::NUTELLA_HOME, 'data/startup'), template_dir) if json['type']=='bot'
134
+ FileUtils.copy(File.join(Nutella::NUTELLA_HOME, 'data/index.html'), template_dir) if json['type']=='interface'
115
135
  console.success "Template #{json['name']} created successfully!"
116
136
  end
117
137
 
@@ -0,0 +1,51 @@
1
+ require 'json'
2
+
3
+ module Nutella
4
+
5
+ # This module contains a series of utilities methods to handle the nutella
6
+ # application contained in the directory we are at this moment
7
+ module CurrentAppUtils
8
+
9
+ # Checks that the current directory is actually a nutella application
10
+ # @return [Boolean] true if the current directory is a nutella application, false otherwise
11
+ def CurrentAppUtils.exist?
12
+ cur_app_dir = Dir.pwd
13
+ nutella_json_file = "#{cur_app_dir}/nutella.json"
14
+ # Check that there is a nutella.json file in the main directory of the application
15
+ if File.exist? nutella_json_file
16
+ conf = JSON.parse( IO.read(nutella_json_file) )
17
+ if conf['nutella_version'].nil?
18
+ return false
19
+ end
20
+ else
21
+ return false
22
+ end
23
+ true
24
+ end
25
+
26
+ # Builds a PersistedHash of the application nutella.json file and returns it.
27
+ # This method is used to ease access to the app nutella.json file.
28
+ # @return [PersistedHash] the PersistedHash of the app nutella.json file
29
+ def CurrentAppUtils.config
30
+ cur_app_dir = Dir.pwd
31
+ nutella_json_file = "#{cur_app_dir}/nutella.json"
32
+ if File.exist? nutella_json_file
33
+ return PersistedHash.new(nutella_json_file)
34
+ else
35
+ raise 'The current directory is not a nutella app: impossible to read nutella.json file'
36
+ end
37
+ end
38
+
39
+ end
40
+
41
+
42
+ # Calling this method (Nutella.current_app) simply returns
43
+ # a reference to the CurrentAppUtils module
44
+ def Nutella.current_app
45
+ CurrentAppUtils
46
+ end
47
+
48
+ end
49
+
50
+
51
+
@@ -4,12 +4,12 @@ require 'fileutils'
4
4
  module Nutella
5
5
 
6
6
  # This class behaves *similarly* to a regular Hash but it persists every operation
7
- # to the json file passed in the constructor. Not all Hash operations are supported
7
+ # to the file passed in the constructor. Not all Hash operations are supported
8
8
  # and we added some of our own.
9
9
  class PersistedHash
10
10
 
11
11
  def initialize(file)
12
- @config_file=file
12
+ @file=file
13
13
  end
14
14
 
15
15
  def []( key )
@@ -69,7 +69,7 @@ module Nutella
69
69
  # there is currently no value associated with the specified key.
70
70
  # @return [Boolean] false if the key already exists, true if the
71
71
  # <key, value> pair was added successfully
72
- def add?(key, val)
72
+ def add_key_value?(key, val)
73
73
  hash = load_hash
74
74
  return false if hash.key? key
75
75
  hash[key] = val
@@ -81,37 +81,39 @@ module Nutella
81
81
  # there is currently a value associated with the specified key.
82
82
  # @return [Boolean] false if there is no value associated with
83
83
  # the specified key, true otherwise
84
- def delete?( key )
84
+ def delete_key_value?( key )
85
85
  hash = load_hash
86
86
  return false if hash.delete(key).nil?
87
87
  store_hash hash
88
88
  true
89
89
  end
90
90
 
91
+
92
+ # Removes the file the hash is persisted to
93
+ def remove_file
94
+ File.delete(@file) if File.exist?(@file)
95
+ end
96
+
91
97
  private
92
98
 
93
99
  def store_hash(hash)
94
- dirname = File.dirname(@config_file)
100
+ dirname = File.dirname(@file)
95
101
  FileUtils.mkdir_p(dirname) unless File.directory?(dirname)
96
- File.open(@config_file, 'w+') do |f|
102
+ File.open(@file, 'w+') do |f|
97
103
  f.write(JSON.pretty_generate(hash))
98
104
  end
99
- File.chmod(0777, @config_file)
105
+ File.chmod(0777, @file)
100
106
  end
101
107
 
102
108
  def load_hash
103
109
  begin
104
- return JSON.parse IO.read @config_file
110
+ return JSON.parse IO.read @file
105
111
  rescue
106
112
  # File doesn't exist, return new empty Hash
107
113
  Hash.new
108
114
  end
109
115
  end
110
116
 
111
- def remove_file
112
- File.delete(@config_file) if File.exist?(@config_file)
113
- end
114
-
115
117
  end
116
118
 
117
119
  end
@@ -1,32 +1,132 @@
1
1
  require 'config/persisted_hash'
2
2
 
3
3
  module Nutella
4
-
5
- class RunListHash < PersistedHash
6
4
 
7
- # Returns the +run_id+ names for a certain project
8
- # If no project is specified, +run_id+s for all projects are returned
9
- # @param [String] project_name the name of the project we want to find run names for
10
- # @return [Array<String>] list of +run_id+s associated to the specified project
11
- def runs_by_project( project_name=nil )
12
- (project_name == nil) ? keys : keys.select { |run| run.start_with?(project_name) }
5
+ # Manages the list of nutella applications and runs handled by the framework.
6
+ # The list has a structure similar this one:
7
+ # {
8
+ # "app_a": {
9
+ # "runs": [ "default", "run_1", "run_2" ],
10
+ # "path": "/path/to/app/a/files/"
11
+ # },
12
+ # "app_b": {
13
+ # "runs": [ "run_1", "run_3" ],
14
+ # "path": "/path/to/app/b/files/"
15
+ # }
16
+ # }
17
+ class RunListHash
18
+
19
+ def initialize( file )
20
+ @ph = PersistedHash.new file
21
+ end
22
+
23
+
24
+ # Returns all the +run_id+s for ALL applications
25
+ #
26
+ # @return [Array<String>] list of +run_id+s associated to the specified app_id
27
+ def all_runs
28
+ @ph.to_h
29
+ end
30
+
31
+
32
+ # Returns all the +run_id+s for a certain application
33
+ #
34
+ # @param [String] app_id the id of the application we want to find run_ids for
35
+ # @return [Array<String>] list of +run_id+s associated to the specified app_id
36
+ def runs_for_app( app_id )
37
+ # If there is no app, then return false and do nothing
38
+ return [] if @ph[app_id].nil?
39
+ runs = @ph[app_id]['runs']
40
+ runs.nil? ? [] : runs
41
+ end
42
+
43
+
44
+ # Adds a run_id to the runlist
45
+ #
46
+ # @param [String] app_id the app_id the run_id belongs to
47
+ # @param [String] run_id the run_id we are trying to add to the runs list
48
+ # @param [String] path_to_app_files the path to the application files
49
+ # @return [Boolean] true if the run_id is added to the list (i.e. there is no other
50
+ # run_id with for the same app_id)
51
+ def add?( app_id, run_id, path_to_app_files )
52
+ # If no run_id is specified, we are adding the "default" run
53
+ run_id = 'default' if run_id.nil?
54
+ # Check if we are adding the first run for a certain application
55
+ if @ph.add_key_value?(app_id, Hash.new)
56
+ t = @ph[app_id]
57
+ # Add path and initialize runs
58
+ t['path'] = path_to_app_files
59
+ t['runs'] = [run_id]
60
+ else
61
+ t = @ph[app_id]
62
+ # Check a run with this name doesn't already exist
63
+ return false if t['runs'].include? run_id
64
+ # Add the run_id to list of runs
65
+ t['runs'].push(run_id)
66
+ end
67
+ @ph[app_id] = t
68
+ true
13
69
  end
14
70
 
15
- # Extracts the +run_id+ from the run name (specified at command line)
16
- # @param [String] run_name
17
- # @return [String] the +run_id+ which is either the +project_name+ (if no +run_name+
18
- # was specified) or the concatenation of +project_name+ and +run_name+
19
- def extract_run_id( run_name )
20
- run_name.to_s.empty? ? Nutella.current_project.config['name'] : "#{Nutella.current_project.config['name']}_#{run_name}"
71
+
72
+ # Remove a run_id from the list
73
+ #
74
+ # @param [String] app_id the app_id the run_id belongs to
75
+ # @param [String] run_id the run_if we are trying to remove from the runs list
76
+ # @return [Boolean] true if the run_id is removed from the list (i.e. a run_id with that name exists
77
+ # and is successfully removed)
78
+ def delete?( app_id, run_id )
79
+ # If there is no app, then return false and do nothing
80
+ return false if @ph[app_id].nil?
81
+ t = @ph[app_id]
82
+ result = t['runs'].delete run_id
83
+ if t['runs'].empty?
84
+ # If run_id was the last run for this app, remove the app as well
85
+ @ph.delete_key_value? app_id
86
+ else
87
+ # otherwise write the hash back
88
+ @ph[app_id] = t
89
+ end
90
+ result.nil? ? false : true
91
+ end
92
+
93
+
94
+ # Returns true if the runs list is empty
95
+ # @return [Boolean] true if the list is empty, false otherwise
96
+ def empty?
97
+ @ph.empty?
98
+ end
99
+
100
+
101
+ # Removes the runs list file
102
+ def remove_file
103
+ @ph.remove_file
104
+ end
105
+
106
+
107
+ # This method checks that the list reflects the actual
108
+ # state of the system. It does so by checking that there is
109
+ # still a tmux session with the run name. If that's not the case,
110
+ # it removes the missing runs from the list.
111
+ def clean_list
112
+ all_runs.each do |app, _|
113
+ runs_for_app(app).each do |run|
114
+ unless Tmux.session_exist? Tmux.session_name(app, run)
115
+ delete? app, run
116
+ end
117
+ end
118
+ end
21
119
  end
22
-
23
120
 
24
121
  end
25
122
 
123
+
26
124
  # Calling this method (Nutella.runlist) simply returns and instance of
27
125
  # RunListHash linked to file runlist.json in the nutella home directory
28
126
  def Nutella.runlist
29
- RunListHash.new( "#{ENV['HOME']}/.nutella/runlist.json" )
127
+ rl = RunListHash.new( "#{ENV['HOME']}/.nutella/runlist.json" )
128
+ rl.clean_list
129
+ rl
30
130
  end
31
131
 
32
132
  end
@@ -40,7 +40,7 @@ module Nutella
40
40
  # Print nutella logo
41
41
  def self.print_nutella_logo
42
42
  console.info(NUTELLA_LOGO)
43
- nutella_version = File.open("#{Nutella.config['nutella_home']}VERSION", 'rb').read
43
+ nutella_version = File.open("#{Nutella::NUTELLA_HOME}VERSION", 'rb').read
44
44
  console.info("Welcome to nutella version #{nutella_version}! For a complete lists of available commands type 'nutella help'\n")
45
45
  # If nutella is not ready to be used (i.e. nobody has invoked the "nutella checkup" command yet),
46
46
  # append warning/reminder message
@@ -1,8 +1,8 @@
1
1
  # Require all commands by iterating through all the files
2
2
  # in the commands directory
3
- Dir["#{File.dirname(__FILE__)}/commands/*.rb"].each do |file|
3
+ Dir["#{File.dirname(__FILE__)}/../commands/*.rb"].each do |file|
4
4
  # noinspection RubyResolve
5
- require "core/commands/#{File.basename(file, File.extname(file))}"
5
+ require "commands/#{File.basename(file, File.extname(file))}"
6
6
  end
7
7
 
8
8
  module Nutella
@@ -29,14 +29,10 @@ module Nutella
29
29
  end
30
30
 
31
31
  # This method initializes the nutella configuration file (config.json) with:
32
- # - nutella_home: the directory nutella is installed in
33
- # - tmp_dir: temporary directory used when installing remote templates
34
32
  # - config_dir: directory where the configuration files are stored in
35
33
  # - broker_dir: directory where the local broker is installed in
36
34
  # - main_interface_port: the port used to serve interfaces
37
35
  def Nutella.init
38
- Nutella.config['nutella_home'] = NUTELLA_HOME
39
- Nutella.config['tmp_dir'] = "#{NUTELLA_HOME}.tmp/"
40
36
  Nutella.config['config_dir'] = "#{ENV['HOME']}/.nutella/"
41
37
  Nutella.config['broker_dir'] = "#{Nutella.config['config_dir']}broker/"
42
38
  Nutella.config['main_interface_port'] = 57880
@@ -1,15 +1,17 @@
1
1
  # Import all the modules
2
2
  require 'logging/nutella_logging'
3
3
  require 'core/nutella_core'
4
+ require 'core/nutella_cli'
4
5
  require 'config/config'
5
6
  require 'config/runlist'
6
- require 'config/current_project'
7
- require 'cli/nutella_cli'
7
+ require 'config/current_app_utils'
8
8
 
9
9
  module Nutella
10
- # Initialize nutella home
10
+
11
+ # Initialize nutella home and temporary folder
11
12
  home_dir = File.dirname(__FILE__)
12
13
  NUTELLA_HOME = home_dir[0..-4]
14
+ NUTELLA_TMP = "#{NUTELLA_HOME}.tmp/"
13
15
 
14
16
  # If the nutella configuration file (config.json) is empty (or doesn't exist) we're going to initialize it
15
17
  # with nutella constants and defaults