crane 0.1.9
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/.gitignore +11 -0
- data/Gemfile +4 -0
- data/README.markdown +51 -0
- data/Rakefile +9 -0
- data/bin/crane +6 -0
- data/crane.gemspec +24 -0
- data/lib/crane.rb +3 -0
- data/lib/crane/commands/init.rb +42 -0
- data/lib/crane/commands/push.rb +151 -0
- data/lib/crane/commands/test.rb +59 -0
- data/lib/crane/config.rb +110 -0
- data/lib/crane/crane.rb +118 -0
- data/lib/crane/ftp.rb +142 -0
- data/lib/crane/shell_initializer.rb +55 -0
- data/lib/crane/version.rb +3 -0
- data/lib/shell/shell.rb +125 -0
- data/test/bin/test_crane.rb +22 -0
- data/test/lib/crane/commands/test_push.rb +46 -0
- data/test/lib/crane/commands/test_test.rb +37 -0
- data/test/lib/crane/test_config.rb +107 -0
- data/test/lib/crane/test_ftp.rb +68 -0
- data/test/lib/crane/test_shell_initializer.rb +52 -0
- data/test/lib/shell/test_parser.rb +26 -0
- data/test/resources/configurations/crane +5 -0
- data/test/resources/configurations/crane_wrong_remote_dir +5 -0
- data/test/resources/source_codes/today_file.rb +0 -0
- data/test/set_test_environment.rb +1 -0
- metadata +97 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.markdown
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
What?
|
2
|
+
====
|
3
|
+
|
4
|
+
Crane is a Ruby gem for sending files through FTP without the hassle of clicking directory by directory
|
5
|
+
and sendind file by file. It gets the bore out of your path :-)
|
6
|
+
|
7
|
+
Why?
|
8
|
+
====
|
9
|
+
|
10
|
+
We have projects in servers that, for particular reasons, can't access Github or a git repo, so
|
11
|
+
Capistrano can't be used for deploying.
|
12
|
+
|
13
|
+
Most of them are small clients that demand basic designs, no
|
14
|
+
dynamic programming, usually on shared hosting. After tiny changes, HTML, CSS and image
|
15
|
+
files are sent via FTP. This is very boring.
|
16
|
+
|
17
|
+
How?
|
18
|
+
====
|
19
|
+
|
20
|
+
Crane analyses files in the current directory by date. Let's say you type:
|
21
|
+
|
22
|
+
<pre>
|
23
|
+
$ crane push today
|
24
|
+
</pre>
|
25
|
+
|
26
|
+
Crane will send all files changed *today* to the server. Automatically.
|
27
|
+
|
28
|
+
Better yet, try this:
|
29
|
+
|
30
|
+
<pre>
|
31
|
+
$ crane push 1h
|
32
|
+
</pre>
|
33
|
+
|
34
|
+
This will send all files modified in the last hour.
|
35
|
+
|
36
|
+
How to start
|
37
|
+
====
|
38
|
+
|
39
|
+
Get into your project root directory and type:
|
40
|
+
|
41
|
+
<pre>
|
42
|
+
$ crane init
|
43
|
+
</pre>
|
44
|
+
|
45
|
+
This will inquire you about FTP informations. This configuration will be saved in a file
|
46
|
+
called .crane in the same folder.
|
47
|
+
|
48
|
+
License
|
49
|
+
====
|
50
|
+
|
51
|
+
MIT. Do whatever you want. Want a suggestion? Fork and collaborate.
|
data/Rakefile
ADDED
data/bin/crane
ADDED
data/crane.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "crane/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "crane"
|
7
|
+
s.version = Crane::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Alexandre de Oliveira"]
|
10
|
+
s.email = ["chavedomundo@gmail.com"]
|
11
|
+
s.homepage = "http://github.com/kurko/crane"
|
12
|
+
s.summary = %q{Send files via FTP automatically.}
|
13
|
+
s.description = %q{This gem allows you to easily send files from your project to a remote
|
14
|
+
server via FTP. Designers and interface programmers can have great benefit
|
15
|
+
from this, for they can easily send all last modified files without needing
|
16
|
+
an specific application for that nor searching directories by hand.}
|
17
|
+
|
18
|
+
s.rubyforge_project = "crane"
|
19
|
+
|
20
|
+
s.files = `git ls-files`.split("\n")
|
21
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
22
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
23
|
+
s.require_paths = ["lib"]
|
24
|
+
end
|
data/lib/crane.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require File.expand_path("../../crane.rb", __FILE__)
|
2
|
+
require File.expand_path("../../config.rb", __FILE__)
|
3
|
+
require 'crane/ftp'
|
4
|
+
|
5
|
+
module Crane
|
6
|
+
module Commands
|
7
|
+
class Init < Crane::Engine
|
8
|
+
|
9
|
+
def run
|
10
|
+
@config = Config.load_config
|
11
|
+
ask_ftp_info
|
12
|
+
Config.save_config @config
|
13
|
+
end
|
14
|
+
|
15
|
+
def ask_ftp_info
|
16
|
+
ftp = {}
|
17
|
+
|
18
|
+
print "Your FTP host address (e.g. ftp.mysite.com.br): "
|
19
|
+
ftp[:host] = Shell::Input.text
|
20
|
+
|
21
|
+
print "FTP username: "
|
22
|
+
ftp[:username] = Shell::Input.text
|
23
|
+
|
24
|
+
system "stty -echo"
|
25
|
+
print "FTP password: "
|
26
|
+
ftp[:password] = Shell::Input.text
|
27
|
+
system "stty echo"
|
28
|
+
print "\n"
|
29
|
+
|
30
|
+
print "Type the path to the site's folder (e.g. /public_html ): "
|
31
|
+
ftp[:remote_root_dir] = Shell::Input.text
|
32
|
+
|
33
|
+
@config[:ftp] = ftp
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
def help
|
38
|
+
puts "Initializes environment."
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require File.expand_path("../../crane.rb", __FILE__)
|
2
|
+
require File.expand_path("../../config.rb", __FILE__)
|
3
|
+
require 'crane/ftp'
|
4
|
+
|
5
|
+
module Crane
|
6
|
+
module Commands
|
7
|
+
|
8
|
+
class Push < Crane::Engine
|
9
|
+
|
10
|
+
@ignored_files = []
|
11
|
+
|
12
|
+
def run
|
13
|
+
time_frame = Shell::Parser.get_arguments(@argv).first
|
14
|
+
|
15
|
+
(puts "No config file found. Run 'crane init' to create one."; exit) unless Config.has_config_file?
|
16
|
+
|
17
|
+
@local_files = get_files time_frame
|
18
|
+
|
19
|
+
if @local_files.length == 0
|
20
|
+
puts "No files were found."
|
21
|
+
exit
|
22
|
+
elsif @local_files.length == 1
|
23
|
+
do_push_files = Shell::Input.yesno "Push 1 file to the server? [Y/n]"
|
24
|
+
else
|
25
|
+
do_push_files = Shell::Input.yesno "Push "+@local_files.length.to_s+" files to the server? [Y/n]"
|
26
|
+
end
|
27
|
+
|
28
|
+
exit unless do_push_files
|
29
|
+
|
30
|
+
print "\nConnecting to host... "
|
31
|
+
|
32
|
+
ftp = Crane::Ftp.new
|
33
|
+
if ftp == false then
|
34
|
+
puts "ops, an error ocurred."
|
35
|
+
exit
|
36
|
+
else
|
37
|
+
puts "connected. Started sending files..."
|
38
|
+
end
|
39
|
+
|
40
|
+
push_files ftp
|
41
|
+
end
|
42
|
+
|
43
|
+
def push_files ftp
|
44
|
+
|
45
|
+
prefix_dir = @config[:ftp][:remote_root_dir]
|
46
|
+
prefix_dir << "/" unless prefix_dir[ prefix_dir.length-1,1 ] == "/"
|
47
|
+
|
48
|
+
@local_files.each { |f|
|
49
|
+
st_mk = Time.new
|
50
|
+
|
51
|
+
dir = prefix_dir
|
52
|
+
unless [".", ".."].include? File.dirname(f)
|
53
|
+
dir = prefix_dir + File.dirname(f)
|
54
|
+
ftp.mkdir dir
|
55
|
+
end
|
56
|
+
|
57
|
+
st_end = Time.new
|
58
|
+
t = st_end-st_mk
|
59
|
+
print "."
|
60
|
+
|
61
|
+
pwd = ftp.connection.pwd
|
62
|
+
ftp.chdir dir
|
63
|
+
st_put = Time.new
|
64
|
+
|
65
|
+
begin
|
66
|
+
result = ftp.putbinaryfile f, File.basename(f)
|
67
|
+
rescue
|
68
|
+
|
69
|
+
end
|
70
|
+
st_putend = Time.new
|
71
|
+
t = st_putend-st_put
|
72
|
+
print "."
|
73
|
+
|
74
|
+
STDOUT.flush
|
75
|
+
ftp.connection.chdir pwd
|
76
|
+
}
|
77
|
+
puts "Done!"
|
78
|
+
end
|
79
|
+
|
80
|
+
# if user defines a time interval for the file, check if it complies
|
81
|
+
def within_defined_interval? file, time_frame = ""
|
82
|
+
time = Time.new
|
83
|
+
file_time = Time.at File.stat(file).mtime
|
84
|
+
yesterday = time - (60 * 60 * 24)
|
85
|
+
|
86
|
+
if time_frame == "today"
|
87
|
+
return true if file_time.year+file_time.month+file_time.day == time.year+time.month+time.day
|
88
|
+
elsif time_frame == "yesterday"
|
89
|
+
return true if file_time.year+file_time.month+file_time.day == yesterday.year+yesterday.month+yesterday.day
|
90
|
+
elsif time_frame =~ /^[1-9]h$/
|
91
|
+
seconds = time_frame[/[1-9]/].to_i * (60 * 60)
|
92
|
+
return true if file_time > (time - seconds)
|
93
|
+
elsif time_frame == "all"
|
94
|
+
return true
|
95
|
+
elsif time_frame == ""
|
96
|
+
true
|
97
|
+
end
|
98
|
+
false
|
99
|
+
end
|
100
|
+
|
101
|
+
def get_files time_frame = "", search_folder = ""
|
102
|
+
|
103
|
+
@ignored_files = get_ignored_files
|
104
|
+
local_files = []
|
105
|
+
|
106
|
+
if search_folder == ""
|
107
|
+
search_folder = "*"
|
108
|
+
else
|
109
|
+
search_folder << "*"
|
110
|
+
end
|
111
|
+
|
112
|
+
Dir.glob(search_folder, File::FNM_DOTMATCH).each { |file|
|
113
|
+
filename = File.basename file
|
114
|
+
|
115
|
+
next if [".", ".."].include? filename
|
116
|
+
next if @ignored_files.include? filename
|
117
|
+
next if @ignored_files.include? file
|
118
|
+
|
119
|
+
if File.stat(file).directory? then
|
120
|
+
local_files += get_files(time_frame, file+"/").flatten
|
121
|
+
elsif within_defined_interval? file, time_frame
|
122
|
+
puts file if is_option("list") || is_option("l")
|
123
|
+
local_files.push file
|
124
|
+
end
|
125
|
+
}
|
126
|
+
local_files
|
127
|
+
end
|
128
|
+
|
129
|
+
def help
|
130
|
+
print "Usage:\n"
|
131
|
+
print "\s\scrane push [time_frame] [options]"
|
132
|
+
print "\n\n"
|
133
|
+
print "Time frame:"
|
134
|
+
print "\n"
|
135
|
+
print "\s\s1h\t\tAll files modified since 1h hour ago.\n"
|
136
|
+
print "\s\sxh\t\tAll files modified since xh hours ago (subtitute x by a number).\n"
|
137
|
+
print "\s\stoday\t\tAll files modified today.\n"
|
138
|
+
print "\s\syesterday\tAll files modified yesterday.\n"
|
139
|
+
print "\n"
|
140
|
+
print "If no time frame is set, Crane will parse all files."
|
141
|
+
print "\n\n"
|
142
|
+
print "Options:"
|
143
|
+
print "\n"
|
144
|
+
print "\s\s-l, --list\tList all files found in the set time frame."
|
145
|
+
print "\s\s-h, --help\tShow this help."
|
146
|
+
print "\n"
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require File.expand_path("../../crane.rb", __FILE__)
|
2
|
+
require File.expand_path("../../config.rb", __FILE__)
|
3
|
+
require 'crane/ftp'
|
4
|
+
|
5
|
+
module Crane
|
6
|
+
module Commands
|
7
|
+
class Test < Crane::Engine
|
8
|
+
|
9
|
+
include Config
|
10
|
+
|
11
|
+
def run
|
12
|
+
puts "Starting tests:"
|
13
|
+
|
14
|
+
# has configuration file?
|
15
|
+
if Config.has_config_file?
|
16
|
+
puts "\tConfiguration file: ok."
|
17
|
+
else
|
18
|
+
puts "\tConfiguration file: failed."
|
19
|
+
exit
|
20
|
+
end
|
21
|
+
|
22
|
+
# has connection?
|
23
|
+
if has_ftp_connection?
|
24
|
+
puts "\tFTP connection: ok."
|
25
|
+
else
|
26
|
+
puts "\tFTP connection: failed"
|
27
|
+
exit
|
28
|
+
end
|
29
|
+
|
30
|
+
# can change dir?
|
31
|
+
if ftp_remote_dir?
|
32
|
+
puts "\tFTP remote dir: ok."
|
33
|
+
else
|
34
|
+
puts "\tFTP remote dir: failed => inexistent remote dir"
|
35
|
+
end
|
36
|
+
|
37
|
+
puts "Everything's ok." if @errors == false
|
38
|
+
end
|
39
|
+
|
40
|
+
def has_ftp_connection?
|
41
|
+
@ftp = Crane::Ftp.new
|
42
|
+
return false if @ftp.connection.nil?
|
43
|
+
if @ftp.connection.respond_to?("closed?")
|
44
|
+
!@ftp.connection.closed?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def ftp_remote_dir?
|
49
|
+
@ftp = Crane::Ftp.new if @ftp.nil?
|
50
|
+
@ftp.remote_dir_exists?
|
51
|
+
end
|
52
|
+
|
53
|
+
def help
|
54
|
+
puts "Tests environment."
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/crane/config.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
module Config
|
2
|
+
|
3
|
+
@CONFIG = {}
|
4
|
+
|
5
|
+
@PATH = "./.crane"
|
6
|
+
|
7
|
+
@IGNORE_FILES = [
|
8
|
+
".DS_Store",
|
9
|
+
".crane",
|
10
|
+
".project",
|
11
|
+
"nb_project",
|
12
|
+
".loadpath",
|
13
|
+
".gitignore",
|
14
|
+
".git",
|
15
|
+
".gitmodules"
|
16
|
+
]
|
17
|
+
|
18
|
+
class << self
|
19
|
+
attr_accessor :PATH, :FILENAME, :IGNORE_FILES, :CONFIG
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.get_ignored_files
|
23
|
+
if File.exists? ".gitignore"
|
24
|
+
IO.readlines(".gitignore").each { |e|
|
25
|
+
@IGNORE_FILES.push e.gsub(/\n/, "")
|
26
|
+
}
|
27
|
+
end
|
28
|
+
@IGNORE_FILES
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.has_config_file?
|
32
|
+
File.exists? @PATH
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.make_config config
|
36
|
+
data = ""
|
37
|
+
|
38
|
+
config.each { |key, value|
|
39
|
+
# a new section
|
40
|
+
if value.class.to_s == "Hash" then
|
41
|
+
data << "[" + key.to_s + "]" + "\n"
|
42
|
+
data << self.make_config(value).to_s
|
43
|
+
elsif ["String", "Symbol"].include? value.class.to_s
|
44
|
+
data << key.to_s + " = " +value.to_s + "\n"
|
45
|
+
end
|
46
|
+
}
|
47
|
+
|
48
|
+
data
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.save_config config
|
52
|
+
@CONFIG = config
|
53
|
+
data = self.make_config config
|
54
|
+
unless data.empty?
|
55
|
+
# deletes configuration file to recreate
|
56
|
+
File.delete self.PATH if File.exists? self.PATH
|
57
|
+
|
58
|
+
File.open(self.PATH, "w+") { |cfile|
|
59
|
+
File.chmod(0777, self.PATH)
|
60
|
+
data.each_line { |l|
|
61
|
+
cfile.puts l
|
62
|
+
}
|
63
|
+
}
|
64
|
+
return true
|
65
|
+
end
|
66
|
+
false
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.load_config
|
70
|
+
config = {}
|
71
|
+
|
72
|
+
if File.exists? Config.PATH
|
73
|
+
cfile = File.open Config.PATH, 'r'
|
74
|
+
|
75
|
+
section = ""
|
76
|
+
|
77
|
+
all_properties = {}
|
78
|
+
cfile.each { |l|
|
79
|
+
maybe_section = l.scan(/\[(.*)\]\n/)
|
80
|
+
maybe_property = l.scan(/(.*)=(.*)\n/)
|
81
|
+
|
82
|
+
maybe_section = maybe_section[0][0] if maybe_section[0]
|
83
|
+
|
84
|
+
unless maybe_section.empty? then
|
85
|
+
unless (section.empty? and all_properties.empty? )
|
86
|
+
config[section.to_sym] = all_properties
|
87
|
+
all_properties = ""
|
88
|
+
end
|
89
|
+
section = maybe_section
|
90
|
+
else maybe_property.empty?
|
91
|
+
l.scan(/(.*) = (.*)\n/) {
|
92
|
+
|property, value|
|
93
|
+
all_properties[property.to_sym] = value.to_s
|
94
|
+
}
|
95
|
+
end
|
96
|
+
|
97
|
+
}
|
98
|
+
|
99
|
+
unless (section.empty? and all_properties.empty? )
|
100
|
+
config[section.to_sym] = all_properties
|
101
|
+
all_properties = ""
|
102
|
+
end
|
103
|
+
|
104
|
+
cfile.close
|
105
|
+
end
|
106
|
+
@CONFIG = config
|
107
|
+
config
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|