nutella_framework 0.1.0

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