nimbu 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.
- data/bin/nimbu +12 -0
- data/lib/nimbu.rb +6 -0
- data/lib/nimbu/auth.rb +268 -0
- data/lib/nimbu/cli.rb +12 -0
- data/lib/nimbu/client.rb +269 -0
- data/lib/nimbu/client/rendezvous.rb +76 -0
- data/lib/nimbu/command.rb +189 -0
- data/lib/nimbu/command/auth.rb +38 -0
- data/lib/nimbu/command/base.rb +212 -0
- data/lib/nimbu/command/help.rb +139 -0
- data/lib/nimbu/command/helpers.rb +382 -0
- data/lib/nimbu/command/init.rb +38 -0
- data/lib/nimbu/command/server.rb +22 -0
- data/lib/nimbu/command/themes.rb +86 -0
- data/lib/nimbu/helpers.rb +382 -0
- data/lib/nimbu/server/base.rb +82 -0
- data/lib/nimbu/server/views/index.haml +1 -0
- data/lib/nimbu/version.rb +3 -0
- data/lib/vendor/nimbu/okjson.rb +557 -0
- metadata +121 -0
@@ -0,0 +1,38 @@
|
|
1
|
+
require "nimbu/command/base"
|
2
|
+
require 'term/ansicolor'
|
3
|
+
|
4
|
+
# authentication (login, logout)
|
5
|
+
#
|
6
|
+
class Nimbu::Command::Init < Nimbu::Command::Base
|
7
|
+
include Term::ANSIColor
|
8
|
+
|
9
|
+
# index
|
10
|
+
#
|
11
|
+
# log in with your nimbu credentials
|
12
|
+
#
|
13
|
+
def index
|
14
|
+
if Nimbu::Auth.read_configuration
|
15
|
+
print green(bold("CONGRATULATIONS!")), ": this directory is already configured as a Nimbu theme."
|
16
|
+
else
|
17
|
+
display "Initialize the Nimbu configuration file."
|
18
|
+
config = Nimbu::Auth.get_configuration
|
19
|
+
|
20
|
+
display "Configuration ready: #{config}"
|
21
|
+
config = Nimbu::Auth.get_credentials
|
22
|
+
|
23
|
+
display "Initializing directories:"
|
24
|
+
display " - layouts"
|
25
|
+
FileUtils.mkdir_p(File.join(Dir.pwd,'layouts'))
|
26
|
+
display " - templates"
|
27
|
+
FileUtils.mkdir_p(File.join(Dir.pwd,'templates'))
|
28
|
+
display " - stylesheets"
|
29
|
+
FileUtils.mkdir_p(File.join(Dir.pwd,'stylesheets'))
|
30
|
+
display " - javascripts"
|
31
|
+
FileUtils.mkdir_p(File.join(Dir.pwd,'javascripts'))
|
32
|
+
display " - images"
|
33
|
+
FileUtils.mkdir_p(File.join(Dir.pwd,'images'))
|
34
|
+
print green(bold("Done. Happy coding!\n"))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "nimbu/command/base"
|
2
|
+
require "nimbu/server/base"
|
3
|
+
require 'term/ansicolor'
|
4
|
+
|
5
|
+
# running a local server to speed up designing Nimbu themes
|
6
|
+
#
|
7
|
+
class Nimbu::Command::Server < Nimbu::Command::Base
|
8
|
+
include Term::ANSIColor
|
9
|
+
# server
|
10
|
+
#
|
11
|
+
# list available commands or display help for a specific command
|
12
|
+
#
|
13
|
+
def index
|
14
|
+
# Check if config file is present?
|
15
|
+
if !Nimbu::Auth.read_configuration
|
16
|
+
print red(bold("WARNING")), ": this directory does not seem to contain any Nimbu theme. \n ==> Run \"", bold { "nimbu init ."}, "\" to initialize a new Nimbu project."
|
17
|
+
else
|
18
|
+
puts "Starting the server..."
|
19
|
+
Nimbu::Server::Base.run!
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require "nimbu/command/base"
|
2
|
+
|
3
|
+
# working with themes (upload / download)
|
4
|
+
#
|
5
|
+
class Nimbu::Command::Themes < Nimbu::Command::Base
|
6
|
+
|
7
|
+
# server
|
8
|
+
#
|
9
|
+
# list available commands or display help for a specific command
|
10
|
+
#
|
11
|
+
def index
|
12
|
+
themes = json_decode(nimbu.list_themes)
|
13
|
+
if themes.any?
|
14
|
+
puts "You have following themes for this website:"
|
15
|
+
themes.each do |theme|
|
16
|
+
puts " - #{theme['theme']['name']} (#{theme['theme']['id']})"
|
17
|
+
end
|
18
|
+
else
|
19
|
+
puts "Hm. You seem to have no themes. Is that normal?"
|
20
|
+
end
|
21
|
+
puts ""
|
22
|
+
puts "Currently this directory is configured for '#{Nimbu::Auth.theme}'"
|
23
|
+
end
|
24
|
+
|
25
|
+
# list
|
26
|
+
#
|
27
|
+
# list all layouts, templates and assets
|
28
|
+
#
|
29
|
+
def list
|
30
|
+
input = args.shift.downcase rescue nil
|
31
|
+
if !input.to_s.strip.empty?
|
32
|
+
theme = input.to_s.strip
|
33
|
+
else
|
34
|
+
theme = Nimbu::Auth.theme
|
35
|
+
end
|
36
|
+
display "Showing layouts, templates and assets for '#{theme}':"
|
37
|
+
contents = json_decode(nimbu.show_theme_contents(theme))
|
38
|
+
contents["layouts"].each do |l|
|
39
|
+
display " - layouts/#{l["name"]}"
|
40
|
+
end unless contents["layouts"].nil?
|
41
|
+
contents["templates"].each do |t|
|
42
|
+
display " - templates/#{t["name"]}"
|
43
|
+
end unless contents["templates"].nil?
|
44
|
+
contents["assets"].each do |a|
|
45
|
+
display " - #{a["folder"]}/#{a["name"]}"
|
46
|
+
end unless contents["assets"].nil?
|
47
|
+
end
|
48
|
+
|
49
|
+
# download
|
50
|
+
#
|
51
|
+
# download all layouts, templates and assets
|
52
|
+
#
|
53
|
+
def download
|
54
|
+
input = args.shift.downcase rescue nil
|
55
|
+
if !input.to_s.strip.empty?
|
56
|
+
theme = input.to_s.strip
|
57
|
+
else
|
58
|
+
theme = Nimbu::Auth.theme
|
59
|
+
end
|
60
|
+
display "Downloading layouts, templates and assets for '#{theme}':"
|
61
|
+
contents = json_decode(nimbu.show_theme_contents(theme))
|
62
|
+
contents["layouts"].each do |asset|
|
63
|
+
print " - layouts/#{asset["name"]}"
|
64
|
+
data = json_decode(nimbu.fetch_theme_layout(theme,asset["id"]))
|
65
|
+
filename = File.join(Dir.pwd,"layouts",asset["name"])
|
66
|
+
FileUtils.mkdir_p(File.dirname(filename))
|
67
|
+
File.open(filename, 'w') do |file|
|
68
|
+
file.puts(data["code"])
|
69
|
+
end
|
70
|
+
|
71
|
+
print " (ok)\n"
|
72
|
+
end
|
73
|
+
|
74
|
+
contents["templates"].each do |asset|
|
75
|
+
print " - templates/#{asset["name"]}"
|
76
|
+
data = json_decode(nimbu.fetch_theme_template(theme,asset["id"]))
|
77
|
+
filename = File.join(Dir.pwd,"templates",asset["name"])
|
78
|
+
FileUtils.mkdir_p(File.dirname(filename))
|
79
|
+
File.open(filename, 'w') do |file|
|
80
|
+
file.puts(data["code"])
|
81
|
+
end
|
82
|
+
|
83
|
+
print " (ok)\n"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,382 @@
|
|
1
|
+
require "vendor/nimbu/okjson"
|
2
|
+
|
3
|
+
module Nimbu
|
4
|
+
module Helpers
|
5
|
+
def home_directory
|
6
|
+
running_on_windows? ? ENV['USERPROFILE'].gsub("\\","/") : ENV['HOME']
|
7
|
+
end
|
8
|
+
|
9
|
+
def running_on_windows?
|
10
|
+
RUBY_PLATFORM =~ /mswin32|mingw32/
|
11
|
+
end
|
12
|
+
|
13
|
+
def running_on_a_mac?
|
14
|
+
RUBY_PLATFORM =~ /-darwin\d/
|
15
|
+
end
|
16
|
+
|
17
|
+
def display(msg="", new_line=true)
|
18
|
+
if new_line
|
19
|
+
puts(msg)
|
20
|
+
else
|
21
|
+
print(msg)
|
22
|
+
STDOUT.flush
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def redisplay(line, line_break = false)
|
27
|
+
display("\r\e[0K#{line}", line_break)
|
28
|
+
end
|
29
|
+
|
30
|
+
def deprecate(version)
|
31
|
+
display "!!! DEPRECATION WARNING: This command will be removed in version #{version}"
|
32
|
+
display
|
33
|
+
end
|
34
|
+
|
35
|
+
def error(msg)
|
36
|
+
STDERR.puts(format_with_bang(msg))
|
37
|
+
exit 1
|
38
|
+
end
|
39
|
+
|
40
|
+
def confirm_billing
|
41
|
+
display
|
42
|
+
display "This action will cause your account to be billed at the end of the month"
|
43
|
+
display "For more information, see http://devcenter.nimbu.com/articles/billing"
|
44
|
+
display "Are you sure you want to do this? (y/n) ", false
|
45
|
+
if ask.downcase == 'y'
|
46
|
+
nimbu.confirm_billing
|
47
|
+
return true
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def confirm(message="Are you sure you wish to continue? (y/n)?")
|
52
|
+
display("#{message} ", false)
|
53
|
+
ask.downcase == 'y'
|
54
|
+
end
|
55
|
+
|
56
|
+
def confirm_command(app_to_confirm = app, message=nil)
|
57
|
+
raise(Nimbu::Command::CommandFailed, "No app specified.\nRun this command from app folder or set it adding --app <app name>") unless app_to_confirm
|
58
|
+
|
59
|
+
if respond_to?(:extract_option) && confirmed_app = extract_option('--confirm', false)
|
60
|
+
unless confirmed_app == app_to_confirm
|
61
|
+
raise(Nimbu::Command::CommandFailed, "Confirmed app #{confirmed_app} did not match the selected app #{app_to_confirm}.")
|
62
|
+
end
|
63
|
+
return true
|
64
|
+
else
|
65
|
+
display
|
66
|
+
message ||= "WARNING: Potentially Destructive Action\nThis command will affect the app: #{app_to_confirm}"
|
67
|
+
message << "\nTo proceed, type \"#{app_to_confirm}\" or re-run this command with --confirm #{app_to_confirm}"
|
68
|
+
output_with_bang(message)
|
69
|
+
display
|
70
|
+
display "> ", false
|
71
|
+
if ask.downcase != app_to_confirm
|
72
|
+
output_with_bang "Input did not match #{app_to_confirm}. Aborted."
|
73
|
+
false
|
74
|
+
else
|
75
|
+
true
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def format_date(date)
|
81
|
+
date = Time.parse(date) if date.is_a?(String)
|
82
|
+
date.strftime("%Y-%m-%d %H:%M %Z")
|
83
|
+
end
|
84
|
+
|
85
|
+
def ask
|
86
|
+
STDIN.gets.strip
|
87
|
+
end
|
88
|
+
|
89
|
+
def shell(cmd)
|
90
|
+
FileUtils.cd(Dir.pwd) {|d| return `#{cmd}`}
|
91
|
+
end
|
92
|
+
|
93
|
+
def run_command(command, args=[])
|
94
|
+
Nimbu::Command.run(command, args)
|
95
|
+
end
|
96
|
+
|
97
|
+
def retry_on_exception(*exceptions)
|
98
|
+
retry_count = 0
|
99
|
+
begin
|
100
|
+
yield
|
101
|
+
rescue *exceptions => ex
|
102
|
+
raise ex if retry_count >= 3
|
103
|
+
sleep 3
|
104
|
+
retry_count += 1
|
105
|
+
retry
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def has_git?
|
110
|
+
%x{ git --version }
|
111
|
+
$?.success?
|
112
|
+
end
|
113
|
+
|
114
|
+
def git(args)
|
115
|
+
return "" unless has_git?
|
116
|
+
flattened_args = [args].flatten.compact.join(" ")
|
117
|
+
%x{ git #{flattened_args} 2>&1 }.strip
|
118
|
+
end
|
119
|
+
|
120
|
+
def time_ago(elapsed)
|
121
|
+
if elapsed < 60
|
122
|
+
"#{elapsed.floor}s ago"
|
123
|
+
elsif elapsed < (60 * 60)
|
124
|
+
"#{(elapsed / 60).floor}m ago"
|
125
|
+
else
|
126
|
+
"#{(elapsed / 60 / 60).floor}h ago"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def truncate(text, length)
|
131
|
+
if text.size > length
|
132
|
+
text[0, length - 2] + '..'
|
133
|
+
else
|
134
|
+
text
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
@@kb = 1024
|
139
|
+
@@mb = 1024 * @@kb
|
140
|
+
@@gb = 1024 * @@mb
|
141
|
+
def format_bytes(amount)
|
142
|
+
amount = amount.to_i
|
143
|
+
return '(empty)' if amount == 0
|
144
|
+
return amount if amount < @@kb
|
145
|
+
return "#{(amount / @@kb).round}k" if amount < @@mb
|
146
|
+
return "#{(amount / @@mb).round}M" if amount < @@gb
|
147
|
+
return "#{(amount / @@gb).round}G"
|
148
|
+
end
|
149
|
+
|
150
|
+
def quantify(string, num)
|
151
|
+
"%d %s" % [ num, num.to_i == 1 ? string : "#{string}s" ]
|
152
|
+
end
|
153
|
+
|
154
|
+
def create_git_remote(remote, url)
|
155
|
+
return unless has_git?
|
156
|
+
return if git('remote').split("\n").include?(remote)
|
157
|
+
return unless File.exists?(".git")
|
158
|
+
git "remote add #{remote} #{url}"
|
159
|
+
display "Git remote #{remote} added"
|
160
|
+
end
|
161
|
+
|
162
|
+
def longest(items)
|
163
|
+
items.map { |i| i.to_s.length }.sort.last
|
164
|
+
end
|
165
|
+
|
166
|
+
def display_table(objects, columns, headers)
|
167
|
+
lengths = []
|
168
|
+
columns.each_with_index do |column, index|
|
169
|
+
header = headers[index]
|
170
|
+
lengths << longest([header].concat(objects.map { |o| o[column].to_s }))
|
171
|
+
end
|
172
|
+
display_row headers, lengths
|
173
|
+
display_row lengths.map { |length| "-" * length }, lengths
|
174
|
+
objects.each do |row|
|
175
|
+
display_row columns.map { |column| row[column] }, lengths
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def display_row(row, lengths)
|
180
|
+
row.zip(lengths).each do |column, length|
|
181
|
+
format = column.is_a?(Fixnum) ? "%#{length}s " : "%-#{length}s "
|
182
|
+
display format % column, false
|
183
|
+
end
|
184
|
+
display
|
185
|
+
end
|
186
|
+
|
187
|
+
def json_encode(object)
|
188
|
+
Nimbu::OkJson.encode(object)
|
189
|
+
rescue Nimbu::OkJson::ParserError
|
190
|
+
nil
|
191
|
+
end
|
192
|
+
|
193
|
+
def json_decode(json)
|
194
|
+
Nimbu::OkJson.decode(json)
|
195
|
+
rescue Nimbu::OkJson::ParserError
|
196
|
+
nil
|
197
|
+
end
|
198
|
+
|
199
|
+
def set_buffer(enable)
|
200
|
+
with_tty do
|
201
|
+
if enable
|
202
|
+
`stty icanon echo`
|
203
|
+
else
|
204
|
+
`stty -icanon -echo`
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def with_tty(&block)
|
210
|
+
return unless $stdin.tty?
|
211
|
+
begin
|
212
|
+
yield
|
213
|
+
rescue
|
214
|
+
# fails on windows
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def get_terminal_environment
|
219
|
+
{ "TERM" => ENV["TERM"], "COLUMNS" => `tput cols`, "LINES" => `tput lines` }
|
220
|
+
rescue
|
221
|
+
{ "TERM" => ENV["TERM"] }
|
222
|
+
end
|
223
|
+
|
224
|
+
def fail(message)
|
225
|
+
raise Nimbu::Command::CommandFailed, message
|
226
|
+
end
|
227
|
+
|
228
|
+
## DISPLAY HELPERS
|
229
|
+
|
230
|
+
def action(message)
|
231
|
+
output_with_arrow("#{message}... ", false)
|
232
|
+
Nimbu::Helpers.enable_error_capture
|
233
|
+
yield
|
234
|
+
Nimbu::Helpers.disable_error_capture
|
235
|
+
display "done", false
|
236
|
+
display(", #{@status}", false) if @status
|
237
|
+
display
|
238
|
+
end
|
239
|
+
|
240
|
+
def status(message)
|
241
|
+
@status = message
|
242
|
+
end
|
243
|
+
|
244
|
+
def output(message="", new_line=true)
|
245
|
+
return if message.to_s.strip == ""
|
246
|
+
display(" " + message.split("\n").join("\n "), new_line)
|
247
|
+
end
|
248
|
+
|
249
|
+
def output_with_arrow(message="", new_line=true)
|
250
|
+
return if message.to_s.strip == ""
|
251
|
+
display("----> " + message.split("\n").join("\n "), new_line)
|
252
|
+
end
|
253
|
+
|
254
|
+
def format_with_bang(message)
|
255
|
+
return '' if message.to_s.strip == ""
|
256
|
+
" ! " + message.split("\n").join("\n ! ")
|
257
|
+
end
|
258
|
+
|
259
|
+
def output_with_bang(message="", new_line=true)
|
260
|
+
return if message.to_s.strip == ""
|
261
|
+
display(format_with_bang(message), new_line)
|
262
|
+
end
|
263
|
+
|
264
|
+
def error_with_failure(message)
|
265
|
+
display "failed"
|
266
|
+
output_with_bang(message)
|
267
|
+
exit 1
|
268
|
+
end
|
269
|
+
|
270
|
+
def self.included_into
|
271
|
+
@@included_into ||= []
|
272
|
+
end
|
273
|
+
|
274
|
+
def self.extended_into
|
275
|
+
@@extended_into ||= []
|
276
|
+
end
|
277
|
+
|
278
|
+
def self.included(base)
|
279
|
+
included_into << base
|
280
|
+
end
|
281
|
+
|
282
|
+
def self.extended(base)
|
283
|
+
extended_into << base
|
284
|
+
end
|
285
|
+
|
286
|
+
def self.enable_error_capture
|
287
|
+
included_into.each do |base|
|
288
|
+
base.send(:alias_method, :error_without_failure, :error)
|
289
|
+
base.send(:alias_method, :error, :error_with_failure)
|
290
|
+
end
|
291
|
+
extended_into.each do |base|
|
292
|
+
class << base
|
293
|
+
alias_method :error_without_failure, :error
|
294
|
+
alias_method :error, :error_with_failure
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
def self.disable_error_capture
|
300
|
+
included_into.each do |base|
|
301
|
+
base.send(:alias_method, :error, :error_without_failure)
|
302
|
+
end
|
303
|
+
extended_into.each do |base|
|
304
|
+
class << base
|
305
|
+
alias_method :error, :error_without_failure
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
|
311
|
+
def display_header(message="", new_line=true)
|
312
|
+
return if message.to_s.strip == ""
|
313
|
+
display("=== " + message.to_s.split("\n").join("\n=== "), new_line)
|
314
|
+
end
|
315
|
+
|
316
|
+
def display_object(object)
|
317
|
+
case object
|
318
|
+
when Array
|
319
|
+
# list of objects
|
320
|
+
object.each do |item|
|
321
|
+
display_object(item)
|
322
|
+
end
|
323
|
+
when Hash
|
324
|
+
# if all values are arrays, it is a list with headers
|
325
|
+
# otherwise it is a single header with pairs of data
|
326
|
+
if object.values.all? {|value| value.is_a?(Array)}
|
327
|
+
object.keys.sort_by {|key| key.to_s}.each do |key|
|
328
|
+
display_header(key)
|
329
|
+
display_object(object[key])
|
330
|
+
hputs
|
331
|
+
end
|
332
|
+
end
|
333
|
+
else
|
334
|
+
hputs(object.to_s)
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
def hputs(string='')
|
339
|
+
Kernel.puts(string)
|
340
|
+
end
|
341
|
+
|
342
|
+
def hprint(string='')
|
343
|
+
Kernel.print(string)
|
344
|
+
STDOUT.flush
|
345
|
+
end
|
346
|
+
|
347
|
+
def string_distance(first, last)
|
348
|
+
distances = [] # 0x0s
|
349
|
+
0.upto(first.length) do |index|
|
350
|
+
distances << [index] + [0] * last.length
|
351
|
+
end
|
352
|
+
distances[0] = 0.upto(last.length).to_a
|
353
|
+
1.upto(last.length) do |last_index|
|
354
|
+
1.upto(first.length) do |first_index|
|
355
|
+
first_char = first[first_index - 1, 1]
|
356
|
+
last_char = last[last_index - 1, 1]
|
357
|
+
if first_char == last_char
|
358
|
+
distances[first_index][last_index] = distances[first_index - 1][last_index - 1] # noop
|
359
|
+
else
|
360
|
+
distances[first_index][last_index] = [
|
361
|
+
distances[first_index - 1][last_index], # deletion
|
362
|
+
distances[first_index][last_index - 1], # insertion
|
363
|
+
distances[first_index - 1][last_index - 1] # substitution
|
364
|
+
].min + 1 # cost
|
365
|
+
if first_index > 1 && last_index > 1
|
366
|
+
first_previous_char = first[first_index - 2, 1]
|
367
|
+
last_previous_char = last[last_index - 2, 1]
|
368
|
+
if first_char == last_previous_char && first_previous_char == last_char
|
369
|
+
distances[first_index][last_index] = [
|
370
|
+
distances[first_index][last_index],
|
371
|
+
distances[first_index - 2][last_index - 2] + 1 # transposition
|
372
|
+
].min
|
373
|
+
end
|
374
|
+
end
|
375
|
+
end
|
376
|
+
end
|
377
|
+
end
|
378
|
+
distances[first.length][last.length]
|
379
|
+
end
|
380
|
+
|
381
|
+
end
|
382
|
+
end
|