nutella_framework 0.1.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.
@@ -0,0 +1,91 @@
1
+ require 'core/command'
2
+ require 'semantic'
3
+
4
+ module Nutella
5
+ class Checkup < Command
6
+ @description = "Checks that all the dependencies are installed and prepares nutella to run"
7
+
8
+ def run(args=nil)
9
+ # First check that we have all the tools we need to run nutella
10
+ if !allDependenciesInstalled?
11
+ return
12
+ end
13
+
14
+ # Check if we have a broker and install one if not
15
+ if !File.directory? Nutella.config["broker_dir"]
16
+ console.warn "You don't seem to have a local broker installed so we are going to go ahead and install one for you. This might take some time..."
17
+ if !installBroker
18
+ console.error "Whoops...something went wrong while installing the broker. "
19
+ return
20
+ end
21
+ else
22
+ console.info "You have a local broker installed. Yay!"
23
+ end
24
+
25
+ # Set config and output message
26
+ Nutella.config["ready"] = true
27
+ console.success("All systems go! You are ready to use nutella!")
28
+ end
29
+
30
+
31
+ private
32
+
33
+
34
+ def installBroker
35
+ # Clone, cd and npm install
36
+ out1 = system "git clone git://github.com/mcollina/mosca.git #{Nutella.config["broker_dir"]} > /dev/null 2>&1"
37
+ Dir.chdir(Nutella.config["broker_dir"])
38
+ out2 = system "npm install > /dev/null 2>&1"
39
+
40
+ # Add startup script
41
+ File.open("startup", 'w') { |file| file.write("#!/bin/sh\n\nBASEDIR=$(dirname $0)\n$BASEDIR/bin/mosca --http-port 1884 &\necho $! > $BASEDIR/bin/.pid\n") }
42
+ File.chmod(0755, "startup")
43
+
44
+ # Add configuration
45
+ Nutella.config["broker"] = "localhost"
46
+ return out1 && out2
47
+ end
48
+
49
+ def allDependenciesInstalled?
50
+ # Node version lambda
51
+ node_semver = lambda do
52
+ out = `node --version`
53
+ out[0] = ''
54
+ Semantic::Version.new out
55
+ end
56
+ # Git version lambda
57
+ git_semver = lambda do
58
+ out = `git --version`
59
+ out.slice!(0,12)
60
+ Semantic::Version.new out[0..4]
61
+ end
62
+ # Check versions
63
+ if checkVersion?("node", "0.10.0", node_semver) && checkVersion?("git", "1.8.0", git_semver)
64
+ return true
65
+ end
66
+ false
67
+ end
68
+
69
+
70
+ def checkVersion?(dep, req_version, lambda)
71
+ begin
72
+ actual_version = lambda.call
73
+ rescue
74
+ console.warn "Doesn't look like #{dep} is installed in your system. " +
75
+ "Unfotunately nutella can't do much unless all the dependencies are installed :("
76
+ return
77
+ end
78
+ required_version = Semantic::Version.new req_version
79
+ if actual_version < required_version
80
+ console.warn "Your version of #{dep} is a little old (#{actual_version}). Nutella requires #{required_version}. Please upgrade!"
81
+ return
82
+ else
83
+ console.info "Your #{dep} version is #{actual_version}. Yay!"
84
+ true
85
+ end
86
+ end
87
+
88
+
89
+ end
90
+ end
91
+
@@ -0,0 +1,22 @@
1
+ require 'core/command'
2
+
3
+ module Nutella
4
+
5
+ class Help < Command
6
+ @description = "Displays what every command does and how to use it"
7
+
8
+ def run(args=nil)
9
+ message=""
10
+ Dir[File.dirname(__FILE__)+"/*.rb"].each do |file|
11
+ message += "#{File.basename(file, File.extname(file))}\t\t"
12
+ message += Object::const_get("Nutella::#{File.basename(file, File.extname(file)).capitalize}").description
13
+ message += "\n"
14
+ end
15
+ console.info message, 200
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+
22
+
@@ -0,0 +1,153 @@
1
+ require 'core/command'
2
+ require 'json'
3
+ require 'git'
4
+ require 'net/http'
5
+
6
+ module Nutella
7
+
8
+ class Install < Command
9
+ @description = "Copies an arbitrary template (from central DB, directory or URL) into the current project"
10
+
11
+ def run(args=nil)
12
+ # Is current directory a nutella prj?
13
+ if !Nutella.currentProject.exist?
14
+ return
15
+ end
16
+
17
+ # Check args
18
+ if args.empty?
19
+ console.warn "You need to specify a template name, directory or URL"
20
+ return
21
+ end
22
+ @template = args[0]
23
+ if args.length==2
24
+ @destinationFolder = args[1]
25
+ end
26
+
27
+ # Extract project directory
28
+ @prj_dir = Nutella.currentProject.dir
29
+
30
+ # What kind of template are we handling?
31
+ if isTemplateALocalDir?
32
+ addLocalTemplate
33
+ elsif isTemplateAGitRepo?
34
+ addRemoteTemplate
35
+ elsif isTemplateInCentralDB?
36
+ addCentralTemplate
37
+ else
38
+ console.warn "The specified template is not a valid nutella template"
39
+ end
40
+
41
+ end
42
+
43
+
44
+ private
45
+
46
+
47
+ def isTemplateALocalDir?
48
+ # Does the specified directory exist?
49
+ if !File.directory?(@template)
50
+ return false
51
+ end
52
+ return validateTemplate @template
53
+ end
54
+
55
+
56
+ def isTemplateAGitRepo?
57
+ begin
58
+ dest_dir = @template[@template.rindex("/")+1 .. @template.length-5]
59
+ cloneTemplateFromRemoteTo(dest_dir)
60
+ return validateTemplate("#{Nutella.config["tmp_dir"]}/#{dest_dir}")
61
+ rescue
62
+ return false
63
+ end
64
+ false
65
+ end
66
+
67
+
68
+ def isTemplateInCentralDB?
69
+ uri = URI.parse("https://raw.githubusercontent.com/ltg-uic/nutella/templates-database/" + @template + ".json")
70
+ begin
71
+ nutella_json = JSON.parse(Net::HTTP.get(uri))
72
+ if nutella_json["name"]==@template
73
+ @template = nutella_json["repo"]
74
+ return isTemplateAGitRepo?
75
+ end
76
+ rescue
77
+ return false
78
+ end
79
+ false
80
+ end
81
+
82
+
83
+ def addLocalTemplate
84
+ templateNutellaFileJson = JSON.parse(IO.read("#{@template}/nutella.json"))
85
+
86
+ # If destination is not specified, set it to the template name
87
+ if @destinationFolder==nil
88
+ @destinationFolder = templateNutellaFileJson["name"]
89
+ end
90
+
91
+ # Am I trying to copy onto a template that already exists?
92
+ if templateNutellaFileJson["type"]=="bot"
93
+ # Look into bots folder
94
+ dest_dir = "#{@prj_dir}/bots/#{@destinationFolder}"
95
+ else
96
+ # Look into interfaces folder
97
+ dest_dir = "#{@prj_dir}/interfaces/#{@destinationFolder}"
98
+ end
99
+ if File.directory?(dest_dir)
100
+ console.error("Folder #{dest_dir} aready exists! Can't add template #{@template}")
101
+ return
102
+ end
103
+ FileUtils.copy_entry @template, dest_dir
104
+ console.success("Installed template: #{@template} as #{dest_dir}")
105
+ end
106
+
107
+
108
+ def addRemoteTemplate
109
+ templ_name = @template[@template.rindex("/")+1 .. @template.length-5]
110
+ addLocalTemplate "#{Nutella.config["tmp_dir"]}/#{templ_name}"
111
+ end
112
+
113
+
114
+ def addCentralTemplate
115
+ return addRemoteTemplate
116
+ end
117
+
118
+
119
+ def cloneTemplateFromRemoteTo(dest_dir)
120
+ cleanTmpDir
121
+ if !Dir.exists?(Nutella.config["tmp_dir"])
122
+ Dir.mkdir(Nutella.config["tmp_dir"])
123
+ end
124
+ Git.clone(@template, dest_dir, :path => Nutella.config["tmp_dir"])
125
+ end
126
+
127
+
128
+ def validateTemplate(dir)
129
+ # Parse the template's nutella.json file
130
+ begin
131
+ templateNutellaFileJson = JSON.parse(IO.read("#{dir}/nutella.json"))
132
+ rescue
133
+ return false
134
+ end
135
+ # If template is a bot, perform additional checks
136
+ if templateNutellaFileJson["type"]=="bot"
137
+ # Is there a andatory 'startup' script and is it executable
138
+ if !File.executable?("#{dir}/startup")
139
+ return false
140
+ end
141
+ end
142
+ true
143
+ end
144
+
145
+
146
+ def cleanTmpDir
147
+ FileUtils.rm_rf Nutella.config["tmp_dir"]
148
+ end
149
+
150
+ end
151
+
152
+ end
153
+
@@ -0,0 +1,62 @@
1
+ require 'core/command'
2
+ require 'fileutils'
3
+
4
+ module Nutella
5
+ class New < Command
6
+ @description = "Creates a new project"
7
+
8
+ def run(args=nil)
9
+ @prj_dir = args[0]
10
+
11
+ # If no other arguments, show help and quit here
12
+ if args.empty?
13
+ console.warn "You need to specify a name for your new project. Can't create project."
14
+ return
15
+ end
16
+
17
+ # Does a project/directory with the same name exist already?
18
+ if File.directory?(@prj_dir)
19
+ if File.exist?("#{@prj_dir}/conf/project.json")
20
+ console.warn "A project named #{@prj_dir} already exists. Can't create project."
21
+ return
22
+ else
23
+ console.warn "A directory named #{@prj_dir} already exists. Can't create project."
24
+ return
25
+ end
26
+ end
27
+
28
+ # Generate project structure
29
+ @cur_dir = Dir.pwd # Store current directory
30
+ createDirStructure # Create project directory structure
31
+ Dir.chdir @prj_dir # CD into the project
32
+ # Display a nice success message and return
33
+ console.success "Your new project #{@prj_dir} is ready!"
34
+ end
35
+
36
+
37
+ private
38
+
39
+
40
+ def createDirStructure
41
+ FileUtils.mkdir_p("#{@prj_dir}/bots") # bots dir
42
+ FileUtils.mkdir_p("#{@prj_dir}/interfaces") # interfaces dir
43
+ FileUtils.mkdir_p("#{@prj_dir}/conf") # conf dir
44
+ # create base configuration file
45
+ config_file_hash = {
46
+ "nutella_version" => File.open(NUTELLA_HOME+"VERSION", "rb").read,
47
+ "name" => @prj_dir,
48
+ "version" => "0.1.0-SNAPSHOT"
49
+ }
50
+ File.open("#{@prj_dir}/conf/project.json","w") do |f|
51
+ f.write(JSON.pretty_generate(config_file_hash))
52
+ end
53
+ end
54
+
55
+ def removeDirStructure
56
+ Dir.chdir @cur_dir
57
+ console.info "Removing project #{@prj_dir}"
58
+ FileUtils.rm_rf(@prj_dir)
59
+ end
60
+
61
+ end
62
+ end
@@ -0,0 +1,42 @@
1
+ require 'core/command'
2
+
3
+
4
+ module Nutella
5
+ class Runs < Command
6
+ @description = "Displays list of all the runs, you can filter by passing a project id"
7
+
8
+ def run(args=nil)
9
+ # If invoked with "--all" it will show all the runs under this instance of nutella
10
+ if args[0]=="--all"
11
+ if Nutella.runlist.empty?
12
+ console.info "You are not running any projects"
13
+ else
14
+ console.info "Currently running:"
15
+ Nutella.runlist.to_a.each { |run| console.info " #{run}" }
16
+ end
17
+ else # Just show the runs associated with this project
18
+ # Is current directory a nutella prj?
19
+ if !Nutella.currentProject.exist?
20
+ return
21
+ end
22
+ project_name = Nutella.currentProject.config["name"]
23
+ runs = Nutella.runlist.to_a project_name
24
+ if runs.empty?
25
+ console.info "Currently running #{runs.length} instances of project #{project_name}"
26
+ return
27
+ end
28
+ console.info "Currently running #{runs.length} instances of project #{project_name}:"
29
+ runs.to_a.each { |run|
30
+ run.slice! "#{project_name}_"
31
+ if run.empty?
32
+ console.info "progetto (default)"
33
+ else
34
+ console.info " #{run}"
35
+ end
36
+ }
37
+ end
38
+ end
39
+ end
40
+
41
+ end
42
+
@@ -0,0 +1,101 @@
1
+ require 'core/command'
2
+ require 'core/tmux'
3
+
4
+ module Nutella
5
+ class Start < Command
6
+ @description = "Starts all or some of the bots in the current project"
7
+
8
+ def run(args=nil)
9
+ # Is current directory a nutella prj?
10
+ if !Nutella.currentProject.exist?
11
+ return
12
+ end
13
+
14
+ # Extract runid
15
+ runid = args[0].to_s.empty? ? Nutella.currentProject.config["name"] : Nutella.currentProject.config["name"] + "_" + args[0]
16
+
17
+ # Add to the list of runs and check the runId is unique
18
+ if !Nutella.runlist.add?(runid)
19
+ console.error "Impossible to start project: an instance of this project with the same name is already running!"
20
+ console.error "You might want to kill it with 'nutella stop "+ runid + "'"
21
+ return;
22
+ end
23
+
24
+ # Extract project directory
25
+ @prj_dir = Nutella.currentProject.dir
26
+
27
+ # If running on internal broker, start it
28
+ if Nutella.config["broker"] == "localhost" # Are we using the internal broker
29
+ startBroker
30
+ end
31
+
32
+ # Create .botsconfig file
33
+ deleteBotsConfig
34
+ createBotsConfig
35
+
36
+
37
+ # Start all the bots
38
+ tmux = Tmux.new(runid)
39
+ Dir.entries("#{@prj_dir}/bots").select {|entry| File.directory?(File.join("#{@prj_dir}/bots",entry)) and !(entry =='.' || entry == '..') }.each do |bot|
40
+ if !File.exist?("#{@prj_dir}/bots/#{bot}/startup")
41
+ console.warn "Impossible to start bot #{bot}. Couldn't locate 'startup' script."
42
+ next
43
+ end
44
+ tmux.newWindow(bot)
45
+ end
46
+
47
+ # Output success message
48
+ if runid == Nutella.currentProject.config["name"]
49
+ console.success "Project " + Nutella.currentProject.config["name"] + " started"
50
+ else
51
+ console.success "Project " + Nutella.currentProject.config["name"] + ", run " + args[0] + " started"
52
+ end
53
+ end
54
+
55
+
56
+ private
57
+
58
+
59
+ def startBroker
60
+ pidFile = "#{Nutella.config["broker_dir"]}/bin/.pid"
61
+ if File.exist?(pidFile) # Does the broker pid file exist?
62
+ pidF = File.open(pidFile, "rb")
63
+ pid = pidF.read.to_i
64
+ pidF.close()
65
+ begin
66
+ Process.getpgid(pid) #PID is still alive
67
+ # broker is already started and I do nothing
68
+ rescue
69
+ # broker is dead but we have a stale pid file
70
+ File.delete(pidFile)
71
+ startAndCreatePid()
72
+ end
73
+ else
74
+ # Broker is not running and there is no file
75
+ startAndCreatePid()
76
+ end
77
+ end
78
+
79
+ def startAndCreatePid()
80
+ pid = fork
81
+ exec("#{Nutella.config["broker_dir"]}/startup") if pid.nil?
82
+ end
83
+
84
+ def createBotsConfig
85
+ botsconfig = Nutella.config.to_h
86
+ botsconfig.delete(:runs)
87
+ botsconfig[:prj_name] = Nutella.currentProject.config["name"]
88
+ File.open("#{@prj_dir}/.botsconfig.json", "w") do |f|
89
+ f.write(JSON.pretty_generate(botsconfig))
90
+ end
91
+ end
92
+
93
+ def deleteBotsConfig
94
+ File.delete("#{@prj_dir}/.botsconfig.json") if File.exist?("#{@prj_dir}/.botsconfig.json")
95
+ end
96
+
97
+
98
+ end
99
+
100
+ end
101
+