strobe 0.0.1

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,57 @@
1
+ require "excon"
2
+ require "timeout"
3
+ require "thor"
4
+ require "child_labor"
5
+
6
+ class Strobe
7
+ module Spec
8
+ module CLIExecution
9
+ def strobe(command, &block)
10
+ run("strobe #{command}", &block)
11
+ end
12
+
13
+ def run(command)
14
+ merged_env = env.merge("HOME" => system_home, "STROBE_URL" => "http://localhost:3000/")
15
+ env_string = merged_env.inject([]) { |env_str,env| env_str << %{#{env.first}="#{env.last}"} }
16
+
17
+ self.task = ChildLabor.subprocess("cd #{dir} && #{env_string.join(" ")} #{command}")
18
+
19
+ if block_given?
20
+ yield task
21
+ task.terminate unless task.terminated?
22
+ task.wait unless task.terminated?
23
+ end
24
+ end
25
+
26
+ def task
27
+ Thread.current[:task]
28
+ end
29
+
30
+ def task=(child)
31
+ Thread.current[:task] = child
32
+ end
33
+
34
+ def out
35
+ task.read
36
+ end
37
+
38
+ def err
39
+ task.read_stderr
40
+ end
41
+
42
+ def out_until_block
43
+ # read 1 first so we wait until the process is done processing the last write
44
+ chars = task.stdout.read(1)
45
+
46
+ loop do
47
+ chars << task.stdout.read_nonblock(1000)
48
+ sleep 0.05
49
+ end
50
+ rescue Errno::EAGAIN, EOFError
51
+ @last_out = chars
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ World(Strobe::Spec::CLIExecution)
@@ -0,0 +1,118 @@
1
+ require "pathname"
2
+ require "yaml"
3
+
4
+ class Strobe
5
+ class << self
6
+
7
+ # this isn't currently needed, but why start with globals when you could
8
+ # just as easily start with a singleton instance, which may be helpful
9
+ # later
10
+ def main
11
+ @main ||= new
12
+ end
13
+
14
+ def ui
15
+ main.ui
16
+ end
17
+
18
+ def ui=(ui)
19
+ main.ui = ui
20
+ end
21
+
22
+ def root
23
+ main.root
24
+ end
25
+
26
+ def app_root
27
+ main.app_root
28
+ end
29
+
30
+ def account
31
+ main.account
32
+ end
33
+
34
+ def settings
35
+ main.settings
36
+ end
37
+
38
+ def config_dir
39
+ main.config_dir
40
+ end
41
+
42
+ def local_config_dir
43
+ main.local_config_dir
44
+ end
45
+ end
46
+
47
+ attr_writer :ui
48
+
49
+ def ui
50
+ @ui ||= UI::Basic.new
51
+ end
52
+
53
+ def root
54
+ sproutcore = "#{app_root}/tmp/build"
55
+ File.directory?(sproutcore) ? sproutcore : app_root
56
+ end
57
+
58
+ def app_root
59
+ @app_root ||= Pathname.new(Dir.pwd)
60
+ end
61
+
62
+ def config_dir
63
+ @config_dir ||= begin
64
+ dir = Pathname.new(File.join(Strobe.user_home, ".strobe"))
65
+ FileUtils.mkdir_p(dir) unless dir.exist?
66
+ dir
67
+ end
68
+ end
69
+
70
+ def local_config_dir
71
+ @local_config_dir ||= begin
72
+ dir = Pathname.new(File.join(app_root, ".strobe"))
73
+ FileUtils.mkdir_p(dir) unless dir.exist?
74
+ dir
75
+ end
76
+ end
77
+
78
+ def settings
79
+ @settings ||= Settings.new
80
+ end
81
+
82
+ autoload :Account, "strobe/account"
83
+ autoload :CLI, "strobe/cli"
84
+ autoload :Deploy, "strobe/deploy"
85
+ autoload :Error, "strobe/errors"
86
+ autoload :HTTP, "strobe/http"
87
+ autoload :Server, "strobe/server"
88
+ autoload :Settings, "strobe/settings"
89
+ autoload :UI, "strobe/ui"
90
+ autoload :User, "strobe/user"
91
+ autoload :Version, "strobe/version"
92
+
93
+ # ERRORS
94
+ autoload :AccountError, "strobe/errors"
95
+ autoload :ApplicationError, "strobe/errors"
96
+
97
+ private
98
+ # sadly, copied from Rubygems
99
+ def self.user_home
100
+ unless RUBY_VERSION > '1.9' then
101
+ ['HOME', 'USERPROFILE'].each do |homekey|
102
+ return File.expand_path(ENV[homekey]) if ENV[homekey]
103
+ end
104
+
105
+ if ENV['HOMEDRIVE'] && ENV['HOMEPATH'] then
106
+ return File.expand_path("#{ENV['HOMEDRIVE']}#{ENV['HOMEPATH']}")
107
+ end
108
+ end
109
+
110
+ File.expand_path "~"
111
+ rescue
112
+ if File::ALT_SEPARATOR then
113
+ "C:/"
114
+ else
115
+ "/"
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,67 @@
1
+ require "excon"
2
+ require "json"
3
+
4
+ class Strobe
5
+ class Account
6
+ def self.signup_or_login
7
+ Strobe.ui.info "You aren't logged in with a Strobe user on this machine"
8
+ Strobe.ui.info " 1. Create a new Strobe user"
9
+ Strobe.ui.info " 2. Log in with an existing Strobe user"
10
+ option = Strobe.ui.ask "Please select an option: "
11
+
12
+ case option
13
+ when "1"
14
+ signup
15
+ when "2"
16
+ login
17
+ end
18
+ end
19
+
20
+ def self.signup
21
+ while true
22
+ email, pass = ask_for_login
23
+ passc = Strobe.ui.password "Repeat Password: "
24
+ break if pass == passc
25
+
26
+ Strobe.ui.info "The password and confirmation do not match. Please try again."
27
+ end
28
+
29
+ response = HTTP.new.signup(email, pass)
30
+ persist_details(*response)
31
+
32
+ Strobe.ui.info "Your account was created successfully."
33
+ end
34
+
35
+ def self.login
36
+ email, pass = ask_for_login
37
+
38
+ response = HTTP.new.login(email, pass)
39
+ persist_details(*response)
40
+
41
+ Strobe.ui.info "You are now logged in as #{email}"
42
+ end
43
+
44
+ # we don't need the account for anything yet, so leave it
45
+ def initialize(json)
46
+ end
47
+
48
+ private
49
+ def self.ask_for_login
50
+ Strobe.ui.info "Please enter your Strobe username and password."
51
+
52
+ email = Strobe.ui.ask "Email: "
53
+ pass = Strobe.ui.password "Password: "
54
+ return email, pass
55
+ end
56
+
57
+ def self.persist_details(user, account = nil, errors = nil)
58
+ if account && errors
59
+ raise Strobe::AccountError, "Account creation failed. Please try again.\n#{errors}"
60
+ elsif errors
61
+ raise Strobe::AccountError, "Log in failed. Please try again.\n#{errors}"
62
+ else
63
+ Strobe.settings[:token] = user.token
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,78 @@
1
+ require "thor"
2
+
3
+ class Strobe
4
+ class CLI < Thor
5
+ check_unknown_options!
6
+
7
+ class_option "no-color", :type => :boolean, :banner => "Disable colorization in output"
8
+ class_option "path", :type => :string, :banner => "The path to your application"
9
+
10
+ def initialize(*)
11
+ super
12
+
13
+ opts = {"path" => Strobe.root}.merge(options)
14
+ self.options = Thor::CoreExt::HashWithIndifferentAccess.new(opts)
15
+
16
+ use_shell = options["no-color"] ? Thor::Shell::Basic.new : shell
17
+ Strobe.ui = UI::Shell.new(use_shell)
18
+ end
19
+
20
+ desc "signup", "sign up for a new Strobe account"
21
+ def signup
22
+ Account.signup
23
+ end
24
+
25
+ desc "deploy", "deploy your application to Strobe"
26
+ def deploy
27
+ Deploy.new(options[:path]).deploy
28
+ end
29
+
30
+ desc "preview", "preview your application locally"
31
+ def preview
32
+ Server.new(options[:path]).start
33
+ end
34
+
35
+ def help(command)
36
+ case command
37
+ when "gemfile" then command = "gemfile.5"
38
+ when nil then command = "bundle"
39
+ else command = "bundle-#{cli}"
40
+ end
41
+
42
+ root = File.expand_path("../man", __FILE__)
43
+
44
+ if manpages.include?(command)
45
+ if have_groff?
46
+ groff = "groff -Wall -mtty-char -mandoc -Tascii"
47
+ pager = ENV['MANPAGER'] || ENV['PAGER'] || 'more'
48
+
49
+ Kernel.exec "#{groff} #{root}/#{command} | #{pager}"
50
+ else
51
+ puts File.read("#{root}/#{command}.txt")
52
+ end
53
+ else
54
+ super
55
+ end
56
+ end
57
+
58
+ private
59
+ def manpages
60
+ man_root = File.expand_path("../man", __FILE__)
61
+ return [] unless File.directory?(man_root)
62
+
63
+ man_pages = []
64
+ Dir["#{man_root}/*"].each do |file|
65
+ base = File.basename(file)
66
+ next if base.include?(".")
67
+ man_pages << base
68
+ end
69
+ man_pages
70
+ end
71
+
72
+ def have_groff?
73
+ `which groff 2>#{NULL}`
74
+ $? == 0
75
+ end
76
+ end
77
+ end
78
+
@@ -0,0 +1,131 @@
1
+ require "excon"
2
+ require "json"
3
+ require "mime/types"
4
+ require "active_support/secure_random"
5
+
6
+ cache_manifest = MIME::Type.new("text/cache-manifest") { |m| m.extensions = %w(manifest) }
7
+ MIME::Types.add(cache_manifest)
8
+
9
+ class Strobe
10
+ class Deploy
11
+ def initialize(path)
12
+ @path = path
13
+ @connection, @method = Strobe.settings.app_connection_and_method
14
+ end
15
+
16
+ def deploy
17
+ Strobe::Account.signup_or_login unless Strobe.settings[:token]
18
+
19
+ application, errors = HTTP.new.deploy(multipart)
20
+
21
+ if errors
22
+ raise Strobe::ApplicationError, "Application creation failed. Please try again.\n#{errors}"
23
+ else
24
+ # Update this every time for now, in case the app has changed
25
+ # on the server. This assumes that the server returns JSON
26
+ Strobe.settings.set_local(:application, application)
27
+ Strobe.ui.confirm "Your app was successfully deployed on #{application["url"]}"
28
+ end
29
+ end
30
+
31
+ def multipart
32
+ Multipart.new do |m|
33
+ if application = Strobe.settings[:application]
34
+ name = application["name"]
35
+ url = application["url"]
36
+ else
37
+ name = Strobe.ui.ask "Please enter your application's name: "
38
+ url = Strobe.ui.ask "URL to deploy to: "
39
+ end
40
+
41
+ m.name name
42
+ m.url url
43
+
44
+ Dir["#{@path}/**/*"].each do |file|
45
+ next unless File.file?(file)
46
+
47
+ filename = file.sub(/^#{@path}/, '')
48
+ mime_type = MIME::Types.type_for(file).first
49
+ m.file filename, mime_type ? mime_type.to_s : "application/octet-stream", File.read(file)
50
+ end
51
+
52
+ apps = []
53
+
54
+ Dir["#{@path}/static/*"].each do |app|
55
+ next if File.file?(app)
56
+
57
+ dir = File.basename(app)
58
+ index = Dir["#{app}/*/*/index.html"].first
59
+
60
+ next if !index || !File.file?(index)
61
+
62
+ index = File.read(index)
63
+ apps << index
64
+
65
+ m.file "#{dir}/index.html", "text/html", index
66
+ end
67
+
68
+ m.file "index.html", "text/html", apps.first if apps.size == 1
69
+ end
70
+ end
71
+
72
+ class Multipart
73
+ def initialize
74
+ @name = nil
75
+ @url = nil
76
+ @files = []
77
+ yield self if block_given?
78
+ end
79
+
80
+ def name(name)
81
+ @name = name
82
+ end
83
+
84
+ def url(url)
85
+ @url = url
86
+ end
87
+
88
+ def file(path, content_type, body)
89
+ @files << [path, content_type, body]
90
+ end
91
+
92
+ def headers
93
+ { "Content-Type" => %[multipart/form-data; boundary="#{boundary}"] }
94
+ end
95
+
96
+ def body
97
+ b = "--#{boundary}\r\n"
98
+ b << part("metadata", "application/json", metadata)
99
+ b << "--#{boundary}\r\n"
100
+
101
+ @files.each do |path, type, body|
102
+ b << part("files[]", type, body)
103
+ b << "--#{boundary}\r\n"
104
+ end
105
+
106
+ b
107
+ end
108
+
109
+ private
110
+
111
+ def boundary
112
+ @boundary ||= ActiveSupport::SecureRandom.hex(25)
113
+ end
114
+
115
+ def metadata
116
+ hash = { "name" => @name, "url" => @url }
117
+ hash.merge!( "paths" => @files.map { |path,*| path } )
118
+ hash.to_json
119
+ end
120
+
121
+ def part(name, type, body)
122
+ b = ""
123
+ b << %[Content-Disposition: form-data; name="#{name}"; filename="array"\r\n]
124
+ b << %[Content-Type: #{type}\r\n]
125
+ b << %[\r\n]
126
+ b << body
127
+ b << %[\r\n]
128
+ end
129
+ end
130
+ end
131
+ end