grimen-shelly 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Jonas Grimfelt
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.textile ADDED
@@ -0,0 +1,109 @@
1
+ h1. SHELLY
2
+
3
+ _Remotely stored shell scripts executable locally._
4
+
5
+ h2. The Problem
6
+
7
+ There are plenty of tools out there that makes it more or less straightforward to run scripts on a remote machine. What's funny though is that many people tend to spend more time on trying to get libraries to work on the local machine, because this is the place where the development and testing takes place. The reason for this is that compiling 3rd party libraries can be a real pain when it comes to different hardware architecture, OSs, OS versions, etc. No, package managers (such as port, apt-get, etc.) is not solving this all the way - sometimes not at all. What I usually do to in such cases is to find and copy-paste shell script "setup recipes" from docs/forums/blogs that - well, usually - works for a specific setup. This is not very scalable though, and actually a waste in many senses:
8
+
9
+ *Key reasons:*
10
+
11
+ * Old-school sharing => Setup recipes that works are spread thorough blogs/forums mostly, there's no single resource if you not having your own repository and cloning the scripts manually.
12
+ * No versioning => Same 'ol scripts might stop being "the way of doing it" because of library changes
13
+ * New machine => Same solution probably not works now, i.e. back to finding new setup recipes
14
+ * Conventions?
15
+
16
+ h2. A Solution: Shelly
17
+
18
+ Shelly solves this by letting you run scripts - on your local machine - from a remote server like local shell commands almost. You don't need to get paid storage for this; Shelly can load scripts from code hosts solutions like GitHub, Gist, Pastie, etc., which is usually used for purposes of sharing code/snippets with each other anyway.
19
+
20
+ *Key concepts:*
21
+
22
+ * Run remotely stored setup scripts/recipes on local machine directly - without downloading/copying stuff manually.
23
+ * No new DSL, just a command that runs a shell script resource (Note: No Windows scripting support).
24
+ * Easy to share setup recipes (shell scripts) - shell script platform?
25
+ * Easy on the eye and memory; easy to learn how to use and remember how to access a script.
26
+ * Out-of-the-box supported script sources: GitHub repos, Gist, Pastie, Pastebin, Raw/URL.
27
+ * Local script aliases for personalization - optional though.
28
+
29
+ h2. Installation
30
+
31
+ <pre>$ sudo gem install grimen-shelly</pre>
32
+
33
+ h2. Dependencies&nbsp;
34
+
35
+ <pre>$ sudo gem install activesupport</pre>
36
+ <pre>$ sudo gem install octopi</pre>
37
+
38
+ h2. Basic Usage
39
+
40
+ Run *GitHub* script source:
41
+
42
+ <pre>$ shelly run github:grimen/my_shell_scripts/install_geoip-city.sh</pre>
43
+
44
+ Run *Gist* script source:
45
+
46
+ <pre>$ shelly run gist:112090</pre>
47
+
48
+ Run *Pastie* script source:
49
+
50
+ <pre>$ shelly run pastie:478898</pre>
51
+
52
+ Run *Pastebin* script source:
53
+
54
+ <pre>$ shelly run pastebin:23233</pre>
55
+
56
+ Run *custom* script source:
57
+
58
+ <pre>$ shelly run http://somedomain.com/my_scripts/quack.sh</pre>
59
+
60
+ Example Output:
61
+
62
+ <pre>$ shelly run pastie:478898
63
+ =============================================================================
64
+ [shelly]: Script source type: PASTIE
65
+ [shelly]: Script source URL: http://pastie.org/478898.txt
66
+ [shelly]: Fetching script...DONE
67
+ [shelly]: Executing script...
68
+ ============================================================== SCRIPT =======
69
+ Quack, quack!
70
+ Quack, quack!
71
+ =============================================================================
72
+ [shelly]: Cleaning up...DONE
73
+ [shelly]: END</pre>
74
+
75
+ h2. Advanced Usage
76
+
77
+ h3. Repositories
78
+
79
+ Add repository:
80
+
81
+ <pre>$ shelly add repo github:grimen/my_shell_scripts</pre>
82
+
83
+ Note: The files within this repo is now accessible. Example-file: @install_geoip-city.sh@
84
+
85
+ Run:
86
+
87
+ <pre>$ shelly run install_geoip-city.sh</pre>
88
+
89
+ h3. Aliases
90
+
91
+ Add alias:
92
+
93
+ <pre>$ shelly add alias quack:gist/112090</pre>
94
+
95
+ Run:
96
+
97
+ <pre>$ shelly run quack</pre>
98
+
99
+ h2. NOTE: Security Implications
100
+
101
+ There are obvious security implications with running shell scripts from remote locations, but the same applies to scripts downloaded manually - which is why Shelly got born in the first place. As long as you know what you doing, security should not be a big issue really. You'll need root access (sudo) to run a script no matter if it's required by the script or not.
102
+
103
+ h2. Bugs & Features
104
+
105
+ Shelly is in a sort of conceptual stage, so a lot of improvements can be made. If you got any suggestions, issues, or find any vicious bugs, just file an issue or notify me.
106
+
107
+ h2. License
108
+
109
+ Copyright (c) 2009 Jonas Grimfelt, released under the MIT-license.
data/Rakefile ADDED
@@ -0,0 +1,48 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ NAME = "shelly"
6
+ SUMMARY = %Q{Remotely stored shell scripts runned locally - shell scripts á la rubygems sort of.}
7
+ HOMEPAGE = "http://github.com/grimen/#{NAME}/tree/master"
8
+ AUTHOR = "Jonas Grimfelt"
9
+ EMAIL = "grimen@gmail.com"
10
+ SUPPORT_FILES = %w(README.textile TODO.textile)
11
+
12
+ begin
13
+ require 'jeweler'
14
+ Jeweler::Tasks.new do |gem|
15
+ gem.name = NAME
16
+ gem.summary = SUMMARY
17
+ gem.description = SUMMARY
18
+ gem.homepage = HOMEPAGE
19
+ gem.author = AUTHOR
20
+ gem.email = EMAIL
21
+
22
+ gem.require_paths = %w{lib}
23
+ gem.files = %w(MIT-LICENSE Rakefile) + SUPPORT_FILES + Dir.glob(File.join('{bin,config,lib,test}', '**', '*'))
24
+ gem.executables = %w(shelly)
25
+ gem.extra_rdoc_files = SUPPORT_FILES
26
+ end
27
+ rescue LoadError
28
+ puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo NAME install technicalpickles-jeweler -s http://gems.github.com"
29
+ end
30
+
31
+ desc %Q{Run unit tests for "#{NAME}".}
32
+ task :default => :test
33
+
34
+ desc %Q{Run unit tests for "#{NAME}".}
35
+ Rake::TestTask.new(:test) do |test|
36
+ test.libs << 'lib'
37
+ test.pattern = 'test/**/*_test.rb'
38
+ test.verbose = true
39
+ end
40
+
41
+ desc %Q{Generate documentation for "#{NAME}".}
42
+ Rake::RDocTask.new(:rdoc) do |rdoc|
43
+ rdoc.rdoc_dir = 'rdoc'
44
+ rdoc.title = NAME
45
+ rdoc.options << '--line-numbers' << '--inline-source' << '--charset=UTF-8'
46
+ rdoc.rdoc_files.include(SUPPORT_FILES)
47
+ rdoc.rdoc_files.include(File.join('lib', '**', '*.rb'))
48
+ end
data/TODO.textile ADDED
@@ -0,0 +1,10 @@
1
+ h1. TODO
2
+
3
+ h2. Next
4
+
5
+ * Feature: shelly help
6
+ * Enhance: Make the shell script execution output more *stable*, i.e. not being printed out in wrong order
7
+ * Enhance: Arguments handling; make use of existing lib for nicer args-parsing?
8
+ * Feature: Write boring tests
9
+ * Other: Comment the code where needed - for everyone's best
10
+ * ____________?
data/bin/shelly ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'shelly'))
4
+
5
+ Shelly.run(ARGV)
data/config/shelly.yml ADDED
@@ -0,0 +1,6 @@
1
+ ---
2
+ repos:
3
+ github:
4
+ - grimen/my_shell_scripts
5
+ aliases:
6
+ quack: gist:112090
data/lib/shelly.rb ADDED
@@ -0,0 +1,169 @@
1
+ Dir[File.expand_path(File.join(File.dirname(__FILE__), 'shelly', '**', '*.rb'))].uniq.each do |file|
2
+ require file
3
+ end
4
+
5
+ require 'net/http'
6
+ require 'uri'
7
+
8
+ module Shelly
9
+
10
+ VALID_SCRIPT_SOURCES = [:github, :gist, :pastie, :pastebin, :raw].freeze
11
+ VALID_COMMANDS = {
12
+ :run => [],
13
+ :create => [:config],
14
+ :add => [:repo, :alias],
15
+ :remove => [:repo, :alias],
16
+ :list => [:repos, :aliases],
17
+ :help => []
18
+ }.freeze
19
+
20
+ def run(args)
21
+ (args ||= []).collect! { |arg| arg.strip }
22
+
23
+ unless Shelly.internet_connection?
24
+ puts "[shelly]: You are not connected to the Internet."
25
+ else
26
+ unless args.empty?
27
+ if Shelly.valid_command?(args[0], args[1])
28
+ case args[0].to_sym
29
+ when :run then Shelly.run!(args[1])
30
+ when :create then Shelly.create!(args[1])
31
+ when :add then Shelly.add!(args[1], args[2])
32
+ when :remove then Shelly.remove!(args[1], args[2])
33
+ when :list then Shelly.list(args[1])
34
+ when :help then Shelly.help(args[1])
35
+ end
36
+ else
37
+ puts "[shelly]: FAIL: '#{args[1]}' is not a valid command for '#{args[0]}'. Try 'help' for help."
38
+ end
39
+ else
40
+ puts "[shelly]: FAIL: No valid command."
41
+ end
42
+ end
43
+
44
+ end
45
+
46
+ def run!(what)
47
+ if what
48
+ begin
49
+ if Shelly::Store::Alias.include?(what)
50
+ # Alias, e.g. "shelly run hello_world"
51
+ what = Shelly::Store::Alias.value(what)
52
+ elsif Shelly::Store::Repo.include?(what)
53
+ # File from repo, e.g. "shelly run hello_world"
54
+ what = Shelly::Store::Repo.value(what)
55
+ end
56
+
57
+ if what.include?(':')
58
+ # Explicit script source, e.g. "shelly run pastie:1234"
59
+ whats = what.split(':')
60
+ script_source = whats.shift.to_sym
61
+ script_id = whats.join('/')
62
+
63
+ if valid_script_source?(script_source)
64
+ case script_source
65
+ when :github then Shelly::ScriptSource::Scm::Github.new(script_id).run!
66
+ when :gist then Shelly::ScriptSource::Plain::Gist.new(script_id).run!
67
+ when :pastie then Shelly::ScriptSource::Plain::Pastie.new(script_id).run!
68
+ when :pastebin then Shelly::ScriptSource::Plain::Pastebin.new(script_id).run!
69
+ when :raw then Shelly::ScriptSource::Plain::Raw.new(script_id).run!
70
+ end
71
+ else
72
+ Shelly::ScriptSource::Plain::Raw.new(script_id).run!
73
+ end
74
+ else
75
+ puts "[shelly]: FAIL: No valid script source."
76
+ end
77
+ rescue StandardError => e
78
+ puts "#{e}"
79
+ end
80
+ else
81
+ puts "[shelly]: Invalid args for RUN."
82
+ end
83
+ end
84
+
85
+ def create!(what)
86
+ if what
87
+ case what.to_sym
88
+ when :config then Shelly::Store::Base.create_root_config!
89
+ end
90
+ else
91
+ puts "[shelly]: Invalid args for CREATE."
92
+ end
93
+ end
94
+
95
+ def add!(to, what)
96
+ if to
97
+ case to.to_sym
98
+ when :repo then Shelly::Store::Repo.add(what)
99
+ when :alias then Shelly::Store::Alias.add(what)
100
+ end
101
+ else
102
+ puts "[shelly]: Invalid args for ADD."
103
+ end
104
+ end
105
+
106
+ def remove!(from, what)
107
+ if from
108
+ case from.to_sym
109
+ when :repo then Shelly::Store::Repo.remove(what)
110
+ when :alias then Shelly::Store::Alias.remove(what)
111
+ end
112
+ else
113
+ puts "[shelly]: Invalid args for REMOVE."
114
+ end
115
+ end
116
+
117
+ def list(from)
118
+ case (from ||= :all).to_sym
119
+ when :repos then puts Shelly::Store::Repo.list
120
+ when :aliases then puts Shelly::Store::Alias.list
121
+ else puts Shelly::Store::Base.list
122
+ end
123
+ end
124
+
125
+ # TODO: Implement help instructions
126
+ def help(from)
127
+ puts "[shelly]: No help available."
128
+ end
129
+
130
+ extend self
131
+
132
+ def internet_connection?
133
+ uri = URI('http://google.com')
134
+ begin
135
+ http = Net::HTTP.new(uri.host, uri.port)
136
+ http.open_timeout = 5
137
+ http.start
138
+ http.finish
139
+ true
140
+ rescue
141
+ false
142
+ end
143
+ end
144
+
145
+ def valid_alias?(arg)
146
+ Shelly::Store::Alias.include?(arg)
147
+ end
148
+
149
+ def valid_repo?(arg)
150
+ Shelly::Store::Repo.include?(arg)
151
+ end
152
+
153
+ def valid_repo_file?(arg)
154
+ Shelly::Store::Repo.include_file?(arg)
155
+ end
156
+
157
+ def valid_script_source?(arg)
158
+ VALID_SCRIPT_SOURCES.include?(arg.to_sym)
159
+ end
160
+
161
+ def valid_command?(*args)
162
+ if args[1]
163
+ VALID_COMMANDS[args[0].to_sym].empty? || VALID_COMMANDS[args[0].to_sym].include?(args[1].to_sym)
164
+ else
165
+ VALID_COMMANDS.keys.include?(args[0].to_sym)
166
+ end
167
+ end
168
+
169
+ end
@@ -0,0 +1,62 @@
1
+ module Shelly
2
+ module ScriptSource
3
+ class Base
4
+
5
+ DEFAULT_SCRIPT_DIRECTORY = '/tmp'.freeze # could not get this to work
6
+ DEFAULT_SCRIPT_EXTENSION = ''.freeze
7
+
8
+ attr_accessor :script_file_name, :script_file_path, :fetch_url
9
+
10
+ def initialize(script_identifier)
11
+ self.fetch_url = self.script_url(script_identifier)
12
+ self.script_file_name = self.script_name(script_identifier)
13
+ self.script_file_path = self.script_path(self.script_file_name)
14
+ end
15
+
16
+ def run!
17
+ begin
18
+ puts "============================================================================="
19
+ puts "[shelly]: Script source type: #{self.script_source.upcase}"
20
+ puts "[shelly]: Script source URL: #{self.fetch_url}"
21
+
22
+ print "[shelly]: Fetching script..."
23
+ Shelly::ShellScript.store!(self.fetch_url, self.script_file_path, 'script')
24
+ print "DONE\n"
25
+
26
+ puts "[shelly]: Executing script..."
27
+ puts "============================================================== SCRIPT ======="
28
+ Shelly::ShellScript.execute!(self.script_file_path)
29
+ puts "============================================================================="
30
+
31
+ print "[shelly]: Cleaning up..."
32
+ Shelly::ShellScript.delete!(self.script_file_path)
33
+ print "DONE\n"
34
+
35
+ puts "[shelly]: END\n"
36
+
37
+ rescue StandardError => m
38
+ puts "[shelly]: Error: #{m}"
39
+ end
40
+ end
41
+
42
+ protected
43
+
44
+ def script_source
45
+ self.class.to_s.split('::').last.downcase
46
+ end
47
+
48
+ def script_name(id)
49
+ "#{self.script_source}_#{id}"
50
+ end
51
+
52
+ def script_path(script_name)
53
+ File.expand_path(File.join(DEFAULT_SCRIPT_DIRECTORY, "#{self.script_file_name}#{DEFAULT_SCRIPT_EXTENSION}"))
54
+ end
55
+
56
+ def script_url
57
+ raise "[shelly]: ** Error: Should implement 'script_url'."
58
+ end
59
+
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,17 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'base'))
2
+
3
+ module Shelly
4
+ module ScriptSource
5
+ module Plain
6
+ class Base < Shelly::ScriptSource::Base
7
+
8
+ protected
9
+
10
+ def is_url(url)
11
+ url =~ /#{SCRIPT_SOURCE_BASE_URL}/
12
+ end
13
+
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,19 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'base'))
2
+
3
+ module Shelly
4
+ module ScriptSource
5
+ module Plain
6
+ class Gist < Shelly::ScriptSource::Plain::Base
7
+
8
+ SCRIPT_SOURCE_BASE_URL = 'gist.github.com'.freeze
9
+
10
+ protected
11
+
12
+ def script_url(gist_id)
13
+ "http://#{SCRIPT_SOURCE_BASE_URL}/#{gist_id}.txt"
14
+ end
15
+
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'base'))
2
+
3
+ module Shelly
4
+ module ScriptSource
5
+ module Plain
6
+ class Pastebin < Shelly::ScriptSource::Plain::Base
7
+
8
+ SCRIPT_SOURCE_BASE_URL = 'pastebin.com'.freeze
9
+
10
+ protected
11
+
12
+ def script_url(pastebin_id)
13
+ "http://#{SCRIPT_SOURCE_BASE_URL}/pastebin.php?dl=#{pastebin_id}"
14
+ end
15
+
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'base'))
2
+
3
+ module Shelly
4
+ module ScriptSource
5
+ module Plain
6
+ class Pastie < Shelly::ScriptSource::Plain::Base
7
+
8
+ SCRIPT_SOURCE_BASE_URL = 'pastie.org'.freeze
9
+
10
+ protected
11
+
12
+ def script_url(pastie_id)
13
+ "http://#{SCRIPT_SOURCE_BASE_URL}/#{pastie_id}.txt"
14
+ end
15
+
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,21 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'base'))
2
+
3
+ require 'uri'
4
+
5
+ module Shelly
6
+ module ScriptSource
7
+ module Plain
8
+ class Raw < Shelly::ScriptSource::Plain::Base
9
+
10
+ SCRIPT_SOURCE_BASE_URL = '.'.freeze
11
+
12
+ protected
13
+
14
+ def script_url(url)
15
+ URI.encode("#{url}")
16
+ end
17
+
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,11 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'base'))
2
+
3
+ module Shelly
4
+ module ScriptSource
5
+ module Scm
6
+ class Base < Shelly::ScriptSource::Base
7
+
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,41 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'base'))
2
+
3
+ require 'rubygems'
4
+
5
+ gem 'octopi', '0.0.9'
6
+ require 'octopi'
7
+
8
+ module Shelly
9
+ module ScriptSource
10
+ module Scm
11
+ class Github < Shelly::ScriptSource::Scm::Base
12
+
13
+ include Octopi
14
+
15
+ SCRIPT_SOURCE_BASE_URL = 'http://github.com'.freeze
16
+
17
+ def initialize(script_identifier)
18
+ user_id, repo_name, file_name, commit_sha = id_parts = script_identifier.split('/')
19
+ raise "Invalid script source: '#{script_identifier}'." unless id_parts.size >= 2
20
+
21
+ self.fetch_url = self.script_url(user_id, repo_name, file_name, commit_sha)
22
+ self.script_file_name = self.script_name(file_name)
23
+ self.script_file_path = self.script_path(self.script_file_name)
24
+ end
25
+
26
+ def self.files(user_id, repo_name, commit_id = nil)
27
+ commit_id ||= Repository.find(user_id, repo_name).commits.first.id
28
+ repo_files = FileObject.find(user_id, repo_name, commit_id).collect { |f| f.name }
29
+ end
30
+
31
+ protected
32
+
33
+ def script_url(user_id, repo_name, file_name, commit_id = nil)
34
+ commit_id ||= Repository.find(user_id, repo_name).commits.first.id
35
+ "#{SCRIPT_SOURCE_BASE_URL}/#{user_id}/#{repo_name}/raw/#{commit_id}/#{file_name}"
36
+ end
37
+
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,83 @@
1
+ module Shelly
2
+ module ShellScript
3
+
4
+ def store!(source_url, destination_path, type)
5
+ `sudo curl "#{source_url}" --silent -o #{destination_path}`
6
+ raise "The specified #{type} could not be found." unless File.exist?(destination_path)
7
+ end
8
+
9
+ def make_executable!(script_path)
10
+ `sudo chmod 777 #{script_path}` if File.exist?(script_path)
11
+ end
12
+
13
+ def execute!(script_path)
14
+ if self.valid?(script_path)
15
+ self.make_executable!(script_path)
16
+ self.ensure_format!(script_path)
17
+ self.execute_with_output!(script_path, false)
18
+ else
19
+ puts "Invalid script source."
20
+ end
21
+ end
22
+
23
+ def execute_with_output!(script_path, advanced = false)
24
+ if advanced
25
+ # Runs the script with output directly, but causes paths to be wrong. =/
26
+ File.open(script_path, 'r') do |file|
27
+ begin
28
+ while line = file.gets
29
+ # Print shell script instruction
30
+ # print line
31
+ # Run everyting except empty lines and comments + print out result.
32
+ print `#{line.strip}` unless line =~ /^(\#|\n)/
33
+ end
34
+ rescue StandardError => e
35
+ puts "Error: #{e}"
36
+ ensure
37
+ file.close
38
+ end
39
+ end
40
+ else
41
+ puts `sudo #{script_path}`
42
+ end
43
+ end
44
+
45
+ def delete!(script_path)
46
+ `sudo rm #{script_path}` if File.exist?(script_path)
47
+ end
48
+
49
+ def valid?(script_path)
50
+ result = false
51
+ File.open(script_path, 'r') do |file|
52
+ begin
53
+ script = file.read
54
+ # Contains a shebang? I.e. is it a valid script?
55
+ result = (script =~ /^\#\!\//)
56
+ rescue StandardError => e
57
+ puts "Error: #{e}"
58
+ ensure
59
+ file.close
60
+ end
61
+ end
62
+ result
63
+ end
64
+
65
+ def ensure_format!(script_path)
66
+ File.open(script_path, 'r+') do |file|
67
+ begin
68
+ script = file.read
69
+ # Remove invalid new-line returns
70
+ script.gsub!(/\r/, '')
71
+ file.write(script)
72
+ rescue StandardError => e
73
+ puts "Error: #{e}"
74
+ ensure
75
+ file.close
76
+ end
77
+ end
78
+ end
79
+
80
+ extend self
81
+
82
+ end
83
+ end
@@ -0,0 +1,44 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'base'))
2
+
3
+ module Shelly
4
+ module Store
5
+ class Alias < Shelly::Store::Base
6
+
7
+ CONFIG_KEY = 'aliases'.freeze
8
+
9
+ class << self
10
+
11
+ def add(what)
12
+ whats = what.split(':')
13
+ config = self.load_config
14
+ config[CONFIG_KEY] ||= {}
15
+ config[CONFIG_KEY].merge!(whats.shift => whats.join(':'))
16
+ self.store_config(config)
17
+ end
18
+
19
+ def remove(what)
20
+ whats = what.split(':')
21
+ config = self.load_config
22
+ config[CONFIG_KEY].delete(whats.shift)
23
+ config[CONFIG_KEY] = nil if config[CONFIG_KEY] == {}
24
+ self.store_config(config)
25
+ end
26
+
27
+ def list
28
+ self.config.to_yaml
29
+ end
30
+
31
+ def include?(key)
32
+ config = load_config
33
+ config[CONFIG_KEY].keys.include?(key) rescue false
34
+ end
35
+
36
+ def value(key)
37
+ "#{config[CONFIG_KEY][key.to_s]}"
38
+ end
39
+
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,43 @@
1
+ require 'yaml'
2
+
3
+ module Shelly
4
+ module Store
5
+ class Base
6
+
7
+ DEFAULT_CONFIG_FILE_PATH = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'config', 'shelly.yml')).freeze
8
+ CUSTOM_CONFIG_FILE_PATH = File.expand_path(File.join('~', '.shelly')).freeze
9
+
10
+ class << self
11
+
12
+ def config
13
+ load_config
14
+ end
15
+
16
+ def create_root_config!
17
+ `sudo cp #{self::DEFAULT_CONFIG_FILE_PATH} #{self::CUSTOM_CONFIG_FILE_PATH}`
18
+ `sudo chmod 664 #{self::CUSTOM_CONFIG_FILE_PATH}`
19
+ end
20
+
21
+ def config_file_path
22
+ File.exist?(self::CUSTOM_CONFIG_FILE_PATH) ? self::CUSTOM_CONFIG_FILE_PATH : self::DEFAULT_CONFIG_FILE_PATH
23
+ end
24
+
25
+ def load_config
26
+ config_hash = YAML.load_file(self.config_file_path) || Hash.new
27
+ end
28
+
29
+ def store_config(config_hash)
30
+ File.open(config_file_path, 'w') do |file|
31
+ YAML.dump(config_hash, file)
32
+ end
33
+ end
34
+
35
+ def list
36
+ load_config.to_yaml rescue ''
37
+ end
38
+
39
+ end
40
+
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,62 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'base'))
2
+
3
+ require 'activesupport'
4
+
5
+ module Shelly
6
+ module Store
7
+ class Repo < Shelly::Store::Base
8
+
9
+ CONFIG_KEY = 'repos'.freeze
10
+
11
+ class << self
12
+
13
+ def add(what)
14
+ whats = what.split(':')
15
+ config = self.load_config
16
+ config[CONFIG_KEY] ||= {}
17
+ config[CONFIG_KEY][whats[0].to_s] ||= []
18
+ config[CONFIG_KEY][whats[0].to_s] << whats[1]
19
+ self.store_config(config)
20
+ end
21
+
22
+ def remove(what)
23
+ whats = what.split(':')
24
+ config = self.load_config
25
+ config[CONFIG_KEY][whats[0].to_s].delete(whats[1].to_s) if config[CONFIG_KEY][whats[0].to_s]
26
+ config[CONFIG_KEY][whats[0].to_s] = nil if config[CONFIG_KEY][whats[0].to_s] == []
27
+ self.store_config(config)
28
+ end
29
+
30
+ def list
31
+ self.load_config[CONFIG_KEY].to_yaml
32
+ end
33
+
34
+ def include?(file_name)
35
+ files(file_name).present? rescue false
36
+ end
37
+
38
+ def files(match_file_name = nil)
39
+ @repo = nil
40
+ @file_names = []
41
+ config = load_config
42
+ config[CONFIG_KEY].each do |host, repos|
43
+ scm_class = "Shelly::ScriptSource::Scm::#{host.classify}".constantize
44
+ repos.each do |repo|
45
+ files = scm_class.files(*repo.split('/'))
46
+ @file_names << files
47
+ @repo = "#{host}:#{repo}/#{match_file_name}" if files.include?(match_file_name)
48
+ end
49
+ end
50
+ @file_names = @file_names.flatten.compact
51
+ @repo
52
+ end
53
+
54
+ def value(file_name)
55
+ files(file_name)
56
+ end
57
+
58
+ end
59
+
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,67 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ require 'shelly'
6
+
7
+ class ShellyTest < Test::Unit::TestCase
8
+
9
+ context "script source" do
10
+ should "should be able to fetch: gist" do
11
+
12
+ end
13
+
14
+ should "should be able to fetch: pastebin" do
15
+
16
+ end
17
+
18
+ should "should be able to fetch: pastie" do
19
+
20
+ end
21
+
22
+ should "should be able to fetch: raw" do
23
+
24
+ end
25
+ end
26
+
27
+ context "store" do
28
+ # should "create config file in user root upon request" do
29
+ # `rm #{Shelly::Store::Base::CUSTOM_CONFIG_FILE_PATH}`
30
+ # Shelly.create!(:config)
31
+ #
32
+ # assert File.exist?(Shelly::Store::Base::CUSTOM_CONFIG_FILE_PATH)
33
+ # end
34
+
35
+ should "initialize config" do
36
+ assert Shelly::Store::Base.load_config
37
+ end
38
+
39
+ should "be able to add alias to config" do
40
+ Shelly.add(:alias, 'foo:http://pastie.org/12345')
41
+
42
+ assert_equal Shelly::Store::Base.config['aliases']['foo'], 'http://pastie.org/12345'
43
+ end
44
+
45
+ should "be able to remove alias from config" do
46
+ Shelly.add(:alias, 'foo:http://pastie.org/12345')
47
+ Shelly.remove(:alias, 'foo')
48
+
49
+ assert !Shelly::Store::Base.config['aliases'].keys.include?('foo')
50
+ end
51
+
52
+ should "be able to add repo to config" do
53
+ Shelly.add(:repo, 'github/grimen/shelly')
54
+
55
+ #flunk 'Not done testing yet.'
56
+
57
+ #assert false
58
+ end
59
+
60
+ should "be able to remove repo from config" do
61
+ Shelly.remove(:repo, 'github/grimen/shelly')
62
+
63
+ #assert false
64
+ end
65
+ end
66
+
67
+ end
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: grimen-shelly
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Jonas Grimfelt
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-05-19 00:00:00 -07:00
13
+ default_executable: shelly
14
+ dependencies: []
15
+
16
+ description: "Remotely stored shell scripts runned locally - shell scripts \xC3\xA1 la rubygems sort of."
17
+ email: grimen@gmail.com
18
+ executables:
19
+ - shelly
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.textile
24
+ - TODO.textile
25
+ files:
26
+ - MIT-LICENSE
27
+ - README.textile
28
+ - Rakefile
29
+ - TODO.textile
30
+ - bin/shelly
31
+ - config/shelly.yml
32
+ - lib/shelly.rb
33
+ - lib/shelly/script_source/base.rb
34
+ - lib/shelly/script_source/plain/base.rb
35
+ - lib/shelly/script_source/plain/gist.rb
36
+ - lib/shelly/script_source/plain/pastebin.rb
37
+ - lib/shelly/script_source/plain/pastie.rb
38
+ - lib/shelly/script_source/plain/raw.rb
39
+ - lib/shelly/script_source/scm/base.rb
40
+ - lib/shelly/script_source/scm/github.rb
41
+ - lib/shelly/shell_script.rb
42
+ - lib/shelly/store/alias.rb
43
+ - lib/shelly/store/base.rb
44
+ - lib/shelly/store/repo.rb
45
+ - test/shelly_test.rb
46
+ has_rdoc: false
47
+ homepage: http://github.com/grimen/shelly/tree/master
48
+ post_install_message:
49
+ rdoc_options:
50
+ - --charset=UTF-8
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ version:
65
+ requirements: []
66
+
67
+ rubyforge_project:
68
+ rubygems_version: 1.2.0
69
+ signing_key:
70
+ specification_version: 3
71
+ summary: "Remotely stored shell scripts runned locally - shell scripts \xC3\xA1 la rubygems sort of."
72
+ test_files:
73
+ - test/shelly_test.rb