nutella_framework 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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