space 0.0.2

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/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source :rubygems
2
+
3
+ gem 'ansi'
4
+ gem 'hashr'
5
+
6
+ group :test do
7
+ gem 'rake'
8
+ gem 'rspec'
9
+ gem 'mocha'
10
+ end
@@ -0,0 +1,28 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ ansi (1.4.2)
5
+ diff-lcs (1.1.3)
6
+ hashr (0.0.19)
7
+ metaclass (0.0.1)
8
+ mocha (0.10.5)
9
+ metaclass (~> 0.0.1)
10
+ rake (0.9.2.2)
11
+ rspec (2.9.0)
12
+ rspec-core (~> 2.9.0)
13
+ rspec-expectations (~> 2.9.0)
14
+ rspec-mocks (~> 2.9.0)
15
+ rspec-core (2.9.0)
16
+ rspec-expectations (2.9.0)
17
+ diff-lcs (~> 1.1.3)
18
+ rspec-mocks (2.9.0)
19
+
20
+ PLATFORMS
21
+ ruby
22
+
23
+ DEPENDENCIES
24
+ ansi
25
+ hashr
26
+ mocha
27
+ rake
28
+ rspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT LICENSE
2
+
3
+ Copyright (c) Sven Fuchs <svenfuchs@artweb-design.de>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1 @@
1
+ # Space [![Build Status](https://secure.travis-ci.org/svenfuchs/space.png?branch=master)](http://travis-ci.org/svenfuchs/space)
@@ -0,0 +1,9 @@
1
+ require 'rspec/core/rake_task'
2
+ require 'bundler/setup'
3
+
4
+ desc 'Run specs'
5
+ RSpec::Core::RakeTask.new do |t|
6
+ t.pattern = './spec/**/*_spec.rb'
7
+ end
8
+
9
+ task :default => :spec
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $: << 'lib'
4
+ require 'space'
5
+
6
+ Space::App.run(ARGV[0])
@@ -0,0 +1,13 @@
1
+ module Enumerable
2
+ def map_with_index(&block)
3
+ result = []
4
+ each_with_index { |element, ix| result << yield(element, ix) }
5
+ result
6
+ end
7
+
8
+ def map_slice(num, &block)
9
+ result = []
10
+ each_slice(num) { |element| result << yield(element) }
11
+ result
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ class String
2
+ def wrap(width)
3
+ gsub(/(.{1,#{width}})( +|$\n?)|(.{1,#{width}})/, "\\1\\3\n")
4
+ end
5
+ end
6
+
7
+
@@ -0,0 +1,25 @@
1
+ require 'yaml'
2
+ require 'hashr'
3
+ require 'core_ext/enumerable'
4
+
5
+ module Space
6
+ autoload :Action, 'space/action'
7
+ autoload :App, 'space/app'
8
+ autoload :Config, 'space/config'
9
+ autoload :Helpers, 'space/helpers'
10
+ autoload :Bundle, 'space/models/bundle'
11
+ autoload :Commands, 'space/models/commands'
12
+ autoload :Command, 'space/models/command'
13
+ autoload :Dependency, 'space/models/dependency'
14
+ autoload :Git, 'space/models/git'
15
+ autoload :Repos, 'space/models/repos'
16
+ autoload :Repo, 'space/models/repo'
17
+ autoload :Screen, 'space/screen'
18
+ autoload :System, 'space/system'
19
+ autoload :View, 'space/view'
20
+
21
+ TEMPLATES = {
22
+ :project => 'lib/space/templates/project.erb',
23
+ :repo => 'lib/space/templates/repository.erb'
24
+ }
25
+ end
@@ -0,0 +1,84 @@
1
+ module Space
2
+ class Action
3
+ autoload :Parser, 'space/action/parser'
4
+
5
+ class << self
6
+ def run(app, line)
7
+ new(app, *Parser.new(app.repos.names).parse(line)).run
8
+ end
9
+ end
10
+
11
+ attr_reader :app, :repos, :command, :args
12
+
13
+ def initialize(app, repos, command, *args)
14
+ @app = app
15
+ @repos = Repos.select(repos) if repos
16
+ @command = normalize(command)
17
+ @args = args
18
+ end
19
+
20
+ def run
21
+ if respond_to?(command)
22
+ send(command)
23
+ elsif command
24
+ execute(command)
25
+ end
26
+ end
27
+
28
+ def reload
29
+ app.bundle.reset
30
+ run_scoped(true) do |repo|
31
+ repo.reset
32
+ end
33
+ end
34
+
35
+ def scope
36
+ app.repos = repos
37
+ end
38
+
39
+ def unscope
40
+ app.repos = nil
41
+ end
42
+
43
+ def local
44
+ run_scoped do |repo|
45
+ system "bundle config --global local.#{repo.name} #{repo.path}"
46
+ end
47
+ end
48
+
49
+ def remote
50
+ run_scoped do |repo|
51
+ system "bundle config --delete local.#{repo.name}"
52
+ end
53
+ end
54
+
55
+ def execute(cmd)
56
+ run_scoped do |repo|
57
+ puts
58
+ repo.execute(cmd)
59
+ end
60
+ end
61
+
62
+ def run_scoped(reloading = false)
63
+ (repos || app.repos).each { |repo| yield repo }
64
+ confirm && reload unless reloading
65
+ end
66
+
67
+ def system(cmd)
68
+ puts cmd
69
+ super
70
+ end
71
+
72
+ def confirm
73
+ puts "\n--- hit any key to continue ---\n"
74
+ STDIN.getc
75
+ end
76
+
77
+ def normalize(command)
78
+ command = (command || '').strip
79
+ command = 'unscope' if command == '-'
80
+ command = 'scope' if command.empty?
81
+ command
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,37 @@
1
+ module Space
2
+ class Action
3
+ class Parser
4
+ attr_reader :names, :line
5
+
6
+ def initialize(names)
7
+ @names = names
8
+ end
9
+
10
+ def parse(line)
11
+ @line = line
12
+ scope = parse_scope
13
+ command = parse_command
14
+ [scope, command]
15
+ end
16
+
17
+ private
18
+
19
+ def parse_scope
20
+ scope = []
21
+ pattern = /^(#{names.join('|')}|\d+)\s*/
22
+ line.gsub!(pattern) { |repo| scope << resolve(repo.strip); '' } while line =~ pattern
23
+ line.strip!
24
+ scope unless scope.empty?
25
+ end
26
+
27
+ def parse_command
28
+ line.strip
29
+ line unless line.empty?
30
+ end
31
+
32
+ def resolve(repo)
33
+ repo =~ /^\d+$/ ? names[repo.to_i - 1] : repo
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,63 @@
1
+ require 'readline'
2
+
3
+ module Space
4
+ class App
5
+ class << self
6
+ attr_reader :config
7
+
8
+ def run(name)
9
+ @config = Config.load(name)
10
+ new(config.name || name).run
11
+ end
12
+ end
13
+
14
+ include Readline
15
+
16
+ attr_accessor :screen, :name, :repos, :bundle, :path
17
+
18
+ def initialize(name)
19
+ @screen = Screen.new(self)
20
+ @name = name
21
+ @bundle = Bundle.new(self.class.config.paths.first)
22
+ @path = File.expand_path('.')
23
+ end
24
+
25
+ def prompt
26
+ # "#{repos.name} >".strip + ' '
27
+ '> '
28
+ end
29
+
30
+ def repos
31
+ @repos ||= Repos.all
32
+ end
33
+
34
+ def run
35
+ screen.render
36
+ loop do
37
+ line = readline(prompt, true)
38
+ break if line.nil?
39
+ handle(line) unless line.empty?
40
+ end
41
+ puts
42
+ end
43
+
44
+ def handle(line)
45
+ Action.run(self, line)
46
+ screen.render
47
+ end
48
+
49
+ def reset
50
+ bundle.reset
51
+ repos.each(&:reset)
52
+ end
53
+
54
+ def execute(cmd)
55
+ chdir { system(cmd) }
56
+ end
57
+
58
+ def chdir(&block)
59
+ Dir.chdir(path, &block)
60
+ end
61
+ end
62
+ end
63
+
@@ -0,0 +1,18 @@
1
+ module Space
2
+ class Config < Hashr
3
+ class << self
4
+ def load(name)
5
+ paths = %W(~/.space/#{name}.yml ./.#{name}.yml).map { |path| File.expand_path(path) }
6
+ unless path = paths.detect { |path| File.exists?(path) }
7
+ puts("Could not find #{name}.yml at either of ~/.space/#{name}.yml and ./.#{name}.yml")
8
+ exit
9
+ end
10
+ new(YAML.load(File.read(path)))
11
+ end
12
+ end
13
+
14
+ def paths
15
+ @paths ||= repositories.map { |path| base_dir ? "#{base_dir}/#{path}" : path }
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,53 @@
1
+ # encoding: UTF-8
2
+ require 'ansi/core'
3
+ require 'core_ext/string'
4
+
5
+ module Space
6
+ module Helpers
7
+ def project_title
8
+ "Project: #{app.name}".ansi(:bold)
9
+ end
10
+
11
+ def repo_title
12
+ title = "#{repo.name.ansi(:bold)} [#{git.branch}, #{git.commit}] [#{repo.number}]"
13
+ title += " *" if repo_selected?
14
+ title
15
+ end
16
+
17
+ def repo_selected?
18
+ app.repos.scoped? && app.repos.include?(repo)
19
+ end
20
+
21
+ def git_status
22
+ "Git: #{format_boolean(git.clean?)}"
23
+ end
24
+
25
+ def bundle_status
26
+ "Bundle: #{format_boolean(bundle.clean?)}"
27
+ end
28
+
29
+ def bundle_info
30
+ bundle.info.ansi(:red) unless bundle.clean?
31
+ end
32
+
33
+ def bundle_deps
34
+ bundle.deps.map { |dep| "• #{dep.ref} #{format_boolean(dep.fresh?)} #{dep.name}" }.join("\n")
35
+ end
36
+
37
+ def bundle_local
38
+ repos = bundle.local_repos
39
+ "\nLocal: #{repos.join(', ')}\n" unless repos.empty?
40
+ end
41
+
42
+ def format_boolean(value)
43
+ value ? '✔'.ansi(:green, :bold) : '⚡'.ansi(:red, :bold)
44
+ end
45
+
46
+ def i(string)
47
+ lines = string.split("\n")
48
+ lines = lines.map { |line| line.wrap(80).split("\n") }.flatten
49
+ lines = lines.map { |line| " #{line}" }
50
+ lines.join("\n")
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,44 @@
1
+ module Space
2
+ class Bundle
3
+ include Commands
4
+
5
+ COMMANDS = {
6
+ :check => 'bundle check',
7
+ :list => 'bundle list',
8
+ :config => 'bundle config'
9
+ }
10
+
11
+ def initialize(path)
12
+ super(path)
13
+ end
14
+
15
+ def clean?
16
+ info =~ /dependencies are satisfied/
17
+ end
18
+
19
+ def info
20
+ result(:check).split("\n").first
21
+ end
22
+
23
+ def deps
24
+ result(:list).split("\n").map do |dep|
25
+ dep =~ /(#{App.name}.*) \(\d\.\d\.\d (.+)\)/
26
+ Dependency.new($1, $2) if Repos.names.include?($1)
27
+ end.compact
28
+ end
29
+
30
+ def local_repos
31
+ config.keys.map do |key|
32
+ key =~ /^local\.(.+)$/ && $1
33
+ end.compact
34
+ end
35
+
36
+ def config
37
+ lines = result(:config).split("\n")[2..-1]
38
+ values = lines.map_slice(3) do |name, value, _|
39
+ [name, value =~ /: "(.*)"/ && $1]
40
+ end
41
+ Hash[*values.compact.flatten]
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,28 @@
1
+ require 'ansi/code'
2
+
3
+ module Space
4
+ class Command
5
+ attr_reader :path, :command
6
+
7
+ def initialize(path, command)
8
+ @path = File.expand_path(path)
9
+ @command = command
10
+ end
11
+
12
+ def result
13
+ @result ||= chdir { strip_ansi(`#{command}`) }
14
+ end
15
+
16
+ def reset
17
+ @result = nil
18
+ end
19
+
20
+ def chdir(&block)
21
+ Dir.chdir(path, &block)
22
+ end
23
+
24
+ def strip_ansi(string)
25
+ string.gsub(ANSI::Code::PATTERN, '')
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,23 @@
1
+ module Space
2
+ module Commands
3
+ attr_reader :path
4
+
5
+ def initialize(path)
6
+ @path = path
7
+ end
8
+
9
+ def result(command)
10
+ commands[command].result
11
+ end
12
+
13
+ def commands
14
+ @commands ||= Hash[*self.class::COMMANDS.map do |key, cmd|
15
+ [key, Command.new(path, cmd)]
16
+ end.flatten]
17
+ end
18
+
19
+ def reset
20
+ commands.each { |key, command| command.reset }
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,18 @@
1
+ module Space
2
+ class Dependency
3
+ attr_reader :name, :ref
4
+
5
+ def initialize(name, ref)
6
+ @name = name
7
+ @ref = ref
8
+ end
9
+
10
+ def fresh?
11
+ repo.ref == ref
12
+ end
13
+
14
+ def repo
15
+ Repos.find_by_name(name)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,23 @@
1
+ module Space
2
+ class Git
3
+ include Commands
4
+
5
+ COMMANDS = {
6
+ :status => 'git status -s',
7
+ :branch => 'git branch --no-color',
8
+ :commit => 'git log -1 head'
9
+ }
10
+
11
+ def clean?
12
+ result(:status).empty?
13
+ end
14
+
15
+ def branch
16
+ result(:branch) =~ /^\* (.+)/ && $1.strip
17
+ end
18
+
19
+ def commit
20
+ result(:commit) =~ /^commit (\S{7})/ && $1
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,40 @@
1
+ module Space
2
+ class Repo
3
+ attr_reader :number, :path, :git, :bundle
4
+
5
+ def initialize(number, path)
6
+ @number = number
7
+ @path = File.expand_path(path)
8
+ @git = Git.new(path)
9
+ @bundle = Bundle.new(path)
10
+ end
11
+
12
+ def name
13
+ @name ||= File.basename(path)
14
+ end
15
+
16
+ def ref
17
+ git.commit
18
+ end
19
+
20
+ def dependencies
21
+ Repos.select(bundle.deps.map(&:name))
22
+ end
23
+
24
+ def reset
25
+ git.reset
26
+ bundle.reset
27
+ end
28
+
29
+ def execute(cmd)
30
+ chdir do
31
+ puts "in #{path}".ansi(:bold, :yellow)
32
+ system(cmd)
33
+ end
34
+ end
35
+
36
+ def chdir(&block)
37
+ Dir.chdir(path, &block)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,45 @@
1
+ module Space
2
+ class Repos < Array
3
+ class << self
4
+ def all
5
+ @all ||= create(App.config.paths)
6
+ end
7
+
8
+ def names
9
+ @names ||= all.map(&:name)
10
+ end
11
+
12
+ def select(names)
13
+ new(all.select { |repo| names.include?(repo.name) })
14
+ end
15
+
16
+ def find_by_name(name)
17
+ all.find_by_name(name)
18
+ end
19
+
20
+ def create(paths)
21
+ new(paths.map_with_index { |path, ix| Repo.new(ix + 1, path) })
22
+ end
23
+ end
24
+
25
+ def names
26
+ map(&:name)
27
+ end
28
+
29
+ def scoped?
30
+ size != self.class.all.size
31
+ end
32
+
33
+ # def name
34
+ # "#{App.config.name}-(#{names.join('|').gsub("#{App.config.name}-", '')})"
35
+ # end
36
+
37
+ def find_by_name(name)
38
+ detect { |repo| repo.name == name }
39
+ end
40
+
41
+ def self_and_dependencies
42
+ self.class.new(map { |repo| [repo.name] + repo.dependencies }.flatten.uniq)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,25 @@
1
+ module Space
2
+ class Screen
3
+ attr_reader :app
4
+
5
+ def initialize(app)
6
+ @app = app
7
+ end
8
+
9
+ def render
10
+ system 'clear'
11
+ puts render_project
12
+ app.repos.each { |repo| puts render_repo(repo) }
13
+ end
14
+
15
+ private
16
+
17
+ def render_project
18
+ View.new.render(:project, :app => app, :bundle => app.bundle)
19
+ end
20
+
21
+ def render_repo(repo)
22
+ View.new.render(:repo, :app => app, :repo => repo, :git => repo.git, :bundle => repo.bundle)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,2 @@
1
+ <%= project_title %>
2
+ <%= bundle_local %>
@@ -0,0 +1,3 @@
1
+ <%= repo_title %>
2
+ <%=i git_status %> <%= bundle_status %>
3
+ <%=i bundle.clean? ? bundle_deps : bundle_info %>
@@ -0,0 +1,3 @@
1
+ module Space
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,25 @@
1
+ require 'erb'
2
+
3
+ module Space
4
+ class View
5
+ include Helpers
6
+
7
+ def template_name
8
+ self.class.name.downcase.split('::').last
9
+ end
10
+
11
+ def render(name, assigns)
12
+ assigns.each { |name, value| assign(name, value) }
13
+ template(name).result(binding)
14
+ end
15
+
16
+ def assign(key, value)
17
+ instance_variable_set(:"@#{key}", value)
18
+ (class << self; self; end).send(:attr_reader, key)
19
+ end
20
+
21
+ def template(name)
22
+ ERB.new(File.read(TEMPLATES[name.to_sym]), nil, '%<>-')
23
+ end
24
+ end
25
+ end
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: space
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Sven Fuchs
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-03-27 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: space.
15
+ email:
16
+ - me@svenfuchs.com
17
+ executables:
18
+ - space
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - lib/core_ext/enumerable.rb
23
+ - lib/core_ext/string.rb
24
+ - lib/space/action/parser.rb
25
+ - lib/space/action.rb
26
+ - lib/space/app.rb
27
+ - lib/space/config.rb
28
+ - lib/space/helpers.rb
29
+ - lib/space/models/bundle.rb
30
+ - lib/space/models/command.rb
31
+ - lib/space/models/commands.rb
32
+ - lib/space/models/dependency.rb
33
+ - lib/space/models/git.rb
34
+ - lib/space/models/repo.rb
35
+ - lib/space/models/repos.rb
36
+ - lib/space/screen.rb
37
+ - lib/space/templates/project.erb
38
+ - lib/space/templates/repository.erb
39
+ - lib/space/version.rb
40
+ - lib/space/view.rb
41
+ - lib/space.rb
42
+ - Gemfile
43
+ - Gemfile.lock
44
+ - LICENSE
45
+ - Rakefile
46
+ - README.md
47
+ - !binary |-
48
+ YmluL3NwYWNl
49
+ homepage: https://github.com/svenfuchs/space
50
+ licenses: []
51
+ post_install_message:
52
+ rdoc_options: []
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ! '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ! '>='
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ requirements: []
68
+ rubyforge_project: ! '[none]'
69
+ rubygems_version: 1.8.17
70
+ signing_key:
71
+ specification_version: 3
72
+ summary: space
73
+ test_files: []