diff --git a/Gemfile b/Gemfile index 0c2de7e..f66b26d 100644 --- a/Gemfile +++ b/Gemfile @@ -7,5 +7,6 @@ gem 'rb-fsevent', git: 'git://github.com/niw/rb-fsevent.git' group :test do gem 'rake' gem 'rspec' + gem 'guard-rspec' gem 'mocha' end diff --git a/Gemfile.lock b/Gemfile.lock index 02bfdef..40b490e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -9,7 +9,13 @@ GEM specs: ansi (1.4.2) diff-lcs (1.1.3) - hashr (0.0.19) + ffi (1.0.11) + guard (1.0.1) + ffi (>= 0.5.0) + thor (~> 0.14.6) + guard-rspec (0.7.0) + guard (>= 0.10.0) + hashr (0.0.20) metaclass (0.0.1) mocha (0.10.5) metaclass (~> 0.0.1) @@ -19,15 +25,17 @@ GEM rspec-expectations (~> 2.9.0) rspec-mocks (~> 2.9.0) rspec-core (2.9.0) - rspec-expectations (2.9.0) + rspec-expectations (2.9.1) diff-lcs (~> 1.1.3) rspec-mocks (2.9.0) + thor (0.14.6) PLATFORMS ruby DEPENDENCIES ansi + guard-rspec hashr mocha rake diff --git a/Guardfile b/Guardfile new file mode 100644 index 0000000..251148c --- /dev/null +++ b/Guardfile @@ -0,0 +1,5 @@ +guard 'rspec' do + watch(%r{^spec/.+_spec\.rb$}) + watch(%r{^lib/space/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } + watch('spec/spec_helper.rb') { "spec" } +end diff --git a/lib/core_ext/enumerable.rb b/lib/core_ext/enumerable/map_slice.rb similarity index 50% rename from lib/core_ext/enumerable.rb rename to lib/core_ext/enumerable/map_slice.rb index c6720e0..441dd4e 100644 --- a/lib/core_ext/enumerable.rb +++ b/lib/core_ext/enumerable/map_slice.rb @@ -1,13 +1,8 @@ module Enumerable - def map_with_index(&block) - result = [] - each_with_index { |element, ix| result << yield(element, ix) } - result - end - def map_slice(num, &block) result = [] each_slice(num) { |element| result << yield(element) } result - end + end unless method_defined?(:map_slice) end + diff --git a/lib/core_ext/string.rb b/lib/core_ext/string/wrap.rb similarity index 73% rename from lib/core_ext/string.rb rename to lib/core_ext/string/wrap.rb index 31772e0..6c6ff4a 100644 --- a/lib/core_ext/string.rb +++ b/lib/core_ext/string/wrap.rb @@ -1,7 +1,6 @@ class String def wrap(width) gsub(/(.{1,#{width}})( +|$\n?)|(.{1,#{width}})/, "\\1\\3\n") - end + end unless method_defined?(:wrap) end - diff --git a/lib/space.rb b/lib/space.rb index cbb16c7..39eee51 100644 --- a/lib/space.rb +++ b/lib/space.rb @@ -1,26 +1,15 @@ require 'yaml' require 'hashr' -require 'core_ext/enumerable' module Space - autoload :Action, 'space/action' + # autoload :Action, 'space/action' autoload :App, 'space/app' autoload :Config, 'space/config' + autoload :Events, 'space/events' autoload :Helpers, 'space/helpers' + autoload :Models, 'space/models' autoload :Screen, 'space/screen' - autoload :System, 'space/system' - autoload :View, 'space/view' + autoload :Shell, 'space/shell' autoload :Tmux, 'space/tmux' - autoload :Watch, 'space/watch' - - autoload :Bundler, 'space/models/bundler' - autoload :Bundle, 'space/models/bundle' - autoload :Commands, 'space/models/commands' - autoload :Command, 'space/models/command' - autoload :Dependency, 'space/models/dependency' - autoload :Git, 'space/models/git' - autoload :Project, 'space/models/project' - autoload :Repos, 'space/models/repos' - autoload :Repo, 'space/models/repo' - autoload :Watcher, 'space/models/watcher' end + diff --git a/lib/space/action.rb b/lib/space/action.rb deleted file mode 100644 index bda793f..0000000 --- a/lib/space/action.rb +++ /dev/null @@ -1,60 +0,0 @@ -module Space - class Action - autoload :Parser, 'space/action/parser' - autoload :Builtin, 'space/action/builtin' - autoload :Development, 'space/action/development' - - include Builtin, Development - - class << self - def run(project, line) - ::Bundler.with_clean_env do - new(project, *Parser.new(project.names).parse(line)).run - end - end - end - - attr_reader :project, :repos, :command, :args - - def initialize(project, repos, command, *args) - @project = project - @repos = project.repos.select_by_names(repos) if repos - @command = normalize(command) - @args = args - end - - def run - if respond_to?(command) - send(command) - elsif command - execute(command) - end - end - - private - - def run_scoped(refreshing = false) - (repos || project.repos.scope).each { |repo| yield repo } - confirm unless refreshing - end - - def system(cmd) - ::Bundler.with_clean_env do - puts cmd - super - end - end - - def confirm - puts "\n--- hit any key to continue ---\n" - STDIN.getc - end - - def normalize(command) - command = (command || '').strip - command = 'unscope' if command == '-' - command = 'scope' if command.empty? - command - end - end -end diff --git a/lib/space/action/builtin.rb b/lib/space/action/builtin.rb deleted file mode 100644 index f9af2b5..0000000 --- a/lib/space/action/builtin.rb +++ /dev/null @@ -1,27 +0,0 @@ -module Space - class Action - module Builtin - def refresh - project.bundler.reset - run_scoped(true) do |repo| - repo.reset - end - end - - def scope - project.repos.scope = repos - end - - def unscope - project.repos.scope = nil - end - - def execute(cmd) - run_scoped do |repo| - puts - repo.execute(cmd) - end - end - end - end -end diff --git a/lib/space/action/development.rb b/lib/space/action/development.rb deleted file mode 100644 index 8fb399a..0000000 --- a/lib/space/action/development.rb +++ /dev/null @@ -1,44 +0,0 @@ -module Space - class Action - module Development - def local - run_scoped do |repo| - system "bundle config --global local.#{repo.name} #{repo.path}" - end - project.bundler.reset - end - - def remote - run_scoped do |repo| - system "bundle config --delete local.#{repo.name}" - end - project.bundler.reset - end - - def install - run_scoped do |repo| - repo.execute 'bundle install' - repo.reset - end - end - - def update - run_scoped do |repo| - repo.execute 'bundle update' - repo.execute 'git commit -am "bump dependencies"' - repo.reset - end - end - - def checkout - # check if branch exists, git co (-b) - # check Gemfiles, replace gem "travis-*", :branch => '...' with the new branch - end - - def pull_deps - # pull all dependencies - end - end - end -end - diff --git a/lib/space/app.rb b/lib/space/app.rb index 7431780..947560b 100644 --- a/lib/space/app.rb +++ b/lib/space/app.rb @@ -2,50 +2,50 @@ module Space class App + autoload :Command, 'space/app/command' + autoload :Handler, 'space/app/handler' + autoload :Parser, 'space/app/parser' + include Readline - attr_reader :name, :config, :project, :screen + attr_reader :name, :project, :screen def initialize(name) @name = name - @config = Config.load(name) - @project = Project.new(name, config) - @screen = Screen.new(name, config, project, project.repos) + @project = Models::Project.new(name) + @screen = Screen.new(project) - project.add_observer(self) + project.subscribe(screen) end def run - render - loop do - line = readline(prompt, true) - break if line.nil? - handle(line) unless line.empty? - end - end - - def update - render(prompt: prompt) + refresh + screen.display(:dashboard) + prompt end private - def render(options = {}) - Watcher.ignore do - screen.clear - print 'gathering data ' - Commands.preload - screen.render(options) + def refresh + screen.display(:progress) + Shell::Watcher.ignore do + project.refresh end end - def handle(line) - Action.run(project, line) - render + def prompt + loop do + line = readline(screen.prompt, true) + break if line.nil? + handle(line) unless line.empty? + end end - def prompt - "#{project.repos.scoped? ? project.repos.scope.map { |r| r.name }.join(', ') : name} > " + def handle(line) + screen.display(:progress) + Handler.new(project).run(line) + screen.display(:dashboard) end end end + diff --git a/lib/space/app/command.rb b/lib/space/app/command.rb new file mode 100644 index 0000000..72bd612 --- /dev/null +++ b/lib/space/app/command.rb @@ -0,0 +1,48 @@ +module Space + class App + class Command + autoload :Execute, 'space/app/command/builtin' + autoload :Refresh, 'space/app/command/builtin' + autoload :Scope, 'space/app/command/builtin' + autoload :Unscope, 'space/app/command/builtin' + + autoload :Local, 'space/app/command/development' + autoload :Remote, 'space/app/command/development' + + attr_reader :project, :scope, :args + + def initialize(project, scope, *args) + @project = project + @scope = scope + @args = args + end + + # ::Bundler.with_clean_env do + # end + + def run + raise 'not implemented' + end + + private + + def repos + @repos ||= project.repos.select_by_names(scope) + end + + def in_scope + repos.each { |repo| yield repo } + end + + def system(cmd) + puts cmd + super + end + + def confirm + puts "\n--- hit any key to continue ---\n" + STDIN.getc + end + end + end +end diff --git a/lib/space/app/command/builtin.rb b/lib/space/app/command/builtin.rb new file mode 100644 index 0000000..35c5e2e --- /dev/null +++ b/lib/space/app/command/builtin.rb @@ -0,0 +1,45 @@ +module Space + class App + class Command + class Execute < Command + def run + Bundler.with_clean_env do + Shell::Watcher.ignore do + in_scope do |repo| + puts + repo.execute(*args) + end + confirm + end + end + end + end + + class Refresh < Command + def run + Bundler.with_clean_env do + Shell::Watcher.ignore do + project.bundler.refresh + in_scope do |repo| + repo.refresh + end + end + end + end + end + + class Scope < Command + def run + project.repos.scope = repos + end + end + + class Unscope < Command + def run + project.repos.scope = nil + end + end + end + end +end + diff --git a/lib/space/app/command/development.rb b/lib/space/app/command/development.rb new file mode 100644 index 0000000..0e23c4e --- /dev/null +++ b/lib/space/app/command/development.rb @@ -0,0 +1,61 @@ +module Space + class App + class Command + class Local < Command + def run + Shell::Watcher.ignore do + repos.each do |repo| + system "bundle config --global local.#{repo.name} #{repo.path}" + end + end + confirm + project.bundler.refresh + end + end + + class Remote < Command + def run + Shell::Watcher.ignore do + repos.each do |repo| + system "bundle config --delete local.#{repo.name}" + end + end + confirm + project.bundler.refresh + end + end + + # class Install < Command + # def run + # in_scope do |repo| + # repo.execute 'bundle install' + # repo.refresh + # end + # end + # end + + # class Update < Command + # def run + # in_scope do |repo| + # repo.execute 'bundle update' + # repo.execute 'git commit -am "bump dependencies"' + # repo.refresh + # end + # end + # end + + # class Checkout < Command + # def run + # # check if branch exists, git co (-b) + # # check Gemfiles, replace gem "travis-*", :branch => '...' with the new branch + # end + # end + + # class PullDeps < Command + # def run + # # pull all dependencies + # end + # end + end + end +end diff --git a/lib/space/app/handler.rb b/lib/space/app/handler.rb new file mode 100644 index 0000000..6d8b12e --- /dev/null +++ b/lib/space/app/handler.rb @@ -0,0 +1,47 @@ +module Space + class App + class Handler + ALIASES = { + '' => 'scope', + '-' => 'unscope', + 'r' => 'refresh' + } + attr_reader :project + + def initialize(project) + @project = project + end + + def run(line) + scope, type = parse(line) + command = command_for(scope, type) + command.run + end + + private + + def command_for(scope, type) + const = const_for(type) + args = [project, scope] + args << type if const == Command::Execute + const.new(*args) + end + + def parse(line) + Parser.new(project.names).parse(line) + end + + def const_for(type) + Command.const_get(const_name(type)) + rescue NameError + Command::Execute + end + + def const_name(type) + type = (type || '').strip + type = ALIASES[type] if ALIASES.key?(type) + type.capitalize + end + end + end +end diff --git a/lib/space/action/parser.rb b/lib/space/app/parser.rb similarity index 93% rename from lib/space/action/parser.rb rename to lib/space/app/parser.rb index bb9ae7a..c9d6e02 100644 --- a/lib/space/action/parser.rb +++ b/lib/space/app/parser.rb @@ -1,5 +1,5 @@ module Space - class Action + class App class Parser attr_reader :names, :line @@ -11,7 +11,7 @@ def parse(line) @line = line scope = parse_scope command = parse_command - [scope, command] + [scope || names, command] end private @@ -35,3 +35,4 @@ def resolve(repo) end end end + diff --git a/lib/space/config.rb b/lib/space/config.rb index e5af0dc..adc2e45 100644 --- a/lib/space/config.rb +++ b/lib/space/config.rb @@ -5,16 +5,16 @@ def load(name) new(YAML.load(File.read(path(name)))) end - def path(name) - path = paths(name).detect { |path| File.exists?(path) } - path || abort("Could not find #{name}.yml at either of ~/.space/#{name}.yml and ./.#{name}.yml") - end + private - def paths(name) - %W(~/.space/#{name}.yml ./.#{name}.yml).map do |path| - File.expand_path(path) + def path(name) + path = paths(name).detect { |path| File.exists?(path) } + path || raise("Could not find #{name}.yml at either of ~/.space/#{name}.yml and ./.space/#{name}.yml") + end + + def paths(name) + %w(. ~).map { |path| File.expand_path("#{path}/.space/#{name}.yml") } end - end end define :template_dir => File.expand_path('../templates', __FILE__) diff --git a/lib/space/events.rb b/lib/space/events.rb new file mode 100644 index 0000000..4fefa23 --- /dev/null +++ b/lib/space/events.rb @@ -0,0 +1,15 @@ +module Space + module Events + def observers + @observers ||= [] + end + + def subscribe(observer) + observers << observer + end + + def notify(event, data) + observers.each { |observer| observer.notify(event, data) } + end + end +end diff --git a/lib/space/helpers.rb b/lib/space/helpers.rb index 4e5c7d4..6c551c8 100644 --- a/lib/space/helpers.rb +++ b/lib/space/helpers.rb @@ -1,6 +1,6 @@ # encoding: UTF-8 require 'ansi/core' -require 'core_ext/string' +require 'core_ext/string/wrap' module Space module Helpers @@ -65,3 +65,4 @@ def i(string, width = 2) end end end + diff --git a/lib/space/models.rb b/lib/space/models.rb new file mode 100644 index 0000000..1dd3d14 --- /dev/null +++ b/lib/space/models.rb @@ -0,0 +1,7 @@ +module Space + module Models + autoload :Project, 'space/models/project' + autoload :Repos, 'space/models/repos' + autoload :Repo, 'space/models/repo' + end +end diff --git a/lib/space/models/bundle.rb b/lib/space/models/bundle.rb deleted file mode 100644 index e841681..0000000 --- a/lib/space/models/bundle.rb +++ /dev/null @@ -1,44 +0,0 @@ -require 'observer' - -module Space - class Bundle - include Watcher, Observable, Commands - - COMMANDS = { - check: 'bundle check', - list: ->(command) { "bundle list | grep #{command.name}" } - } - - WATCH = [ - 'Gemfile', - 'Gemfile.lock' - ] - - attr_reader :project - - def initialize(project, path) - @project = project - super(path) - end - - def name - project.name - end - - def clean? - info =~ /dependencies are satisfied/ - end - - def info - result(:check).split("\n").first - end - - def deps - result(:list).split("\n").map do |dep| - if matches = dep.strip.match(/^\* (?[\S]+) \(\d+\.\d+\.\d+(?: (?.+))?\)/) - Dependency.new(project.repos, matches[:name], matches[:ref]) - end - end.compact - end - end -end diff --git a/lib/space/models/bundler.rb b/lib/space/models/bundler.rb deleted file mode 100644 index 4b542e9..0000000 --- a/lib/space/models/bundler.rb +++ /dev/null @@ -1,27 +0,0 @@ -require 'observer' - -module Space - class Bundler - include Watcher, Observable, Commands - - COMMANDS = { - config: 'bundle config' - } - - WATCH = [ - '.bundle/config' - ] - - def initialize - super('.') - end - - def config - lines = result(:config).split("\n")[2..-1] - values = lines.map_slice(3) do |name, value, _| - [name, value =~ /: "(.*)"/ && $1] - end - Hash[*values.compact.flatten] - end - end -end diff --git a/lib/space/models/command.rb b/lib/space/models/command.rb deleted file mode 100644 index d74b947..0000000 --- a/lib/space/models/command.rb +++ /dev/null @@ -1,38 +0,0 @@ -require 'ansi/code' - -module Space - class Command - attr_reader :context, :command - - def initialize(context, command) - @context = context - @command = command - end - - def result - @result ||= strip_ansi(run) - end - - def reset - @result = nil - end - - private - - def run - ::Bundler.with_clean_env do - chdir do - `#{command.is_a?(Proc) ? context.instance_eval(&command) : command}` - end - end - end - - def chdir(&block) - Dir.chdir(context.path, &block) - end - - def strip_ansi(string) - string.gsub(ANSI::Code::PATTERN, '') - end - end -end diff --git a/lib/space/models/commands.rb b/lib/space/models/commands.rb deleted file mode 100644 index 026d88a..0000000 --- a/lib/space/models/commands.rb +++ /dev/null @@ -1,41 +0,0 @@ -module Space - module Commands - class << self - def all - @all ||= [] - end - - def preload - all.map(&:preload) - end - end - - attr_reader :path - - def initialize(path) - @path = File.expand_path(path) - Commands.all << self - end - - def result(command) - commands[command].result - end - - def commands - @commands ||= Hash[*self.class::COMMANDS.map do |key, cmd| - [key, Command.new(self, cmd)] - end.flatten] - end - - def reset - commands.each { |key, command| command.reset } - end - - def preload - commands.each do |key, command| - print '.' - command.result - end - end - end -end diff --git a/lib/space/models/dependency.rb b/lib/space/models/dependency.rb deleted file mode 100644 index 2df5f1f..0000000 --- a/lib/space/models/dependency.rb +++ /dev/null @@ -1,19 +0,0 @@ -module Space - class Dependency - attr_reader :repos, :name, :ref - - def initialize(repos, name, ref) - @repos = repos - @name = name - @ref = ref - end - - def fresh? - repo.ref == ref - end - - def repo - repos.find_by_name(name) || raise("cannot find repo #{name}") - end - end -end diff --git a/lib/space/models/git.rb b/lib/space/models/git.rb deleted file mode 100644 index 9101302..0000000 --- a/lib/space/models/git.rb +++ /dev/null @@ -1,45 +0,0 @@ -require 'observer' - -module Space - class Git - include Watcher, Observable, Commands - - COMMANDS = { - status: 'git status', - branch: 'git branch --no-color', - commit: 'git log -1 HEAD' - } - - WATCH = [ - '.' - ] - - def branch - result(:branch) =~ /^\* (.+)/ && $1.strip - end - - def commit - result(:commit) =~ /^commit (\S{7})/ && $1 - end - - def status - dirty? ? :dirty : (ahead? ? :ahead : :clean) - end - - def ahead? - ahead > 0 - end - - def ahead - result(:status) =~ /Your branch is ahead of .* by (\d+) commits?\./ ? $1.to_i : 0 - end - - def dirty? - !clean? - end - - def clean? - result(:status).include?('nothing to commit (working directory clean)') - end - end -end diff --git a/lib/space/models/project.rb b/lib/space/models/project.rb index a0cc216..e127228 100644 --- a/lib/space/models/project.rb +++ b/lib/space/models/project.rb @@ -1,38 +1,43 @@ -require 'observer' - module Space - class Project - attr_reader :name, :repos, :bundler + module Models + class Project + include Events - def initialize(name, config) - @name = name - @repos = Repos.new(self, config.paths) - @bundler = Bundler.new - end + autoload :Bundler, 'space/models/project/bundler' - def names - @names ||= Tmux.windows || repos.names - end + attr_reader :name, :repos, :bundler, :config - def local_repos - bundler.config.keys.map do |key| - key =~ /^local\.(.+)$/ - $1 if repos.names.include?($1) - end.compact - end + def initialize(name) + @name = name + @config = Config.load(name) + @repos = Repos.new(self, config.paths) + @bundler = Bundler.new(self) + end - def number(name) - if number = names.index(name) - number + 1 - else - names << name - number(name) + def local_repos + bundler.config.keys.map do |key| + key =~ /^local\.(.+)$/ + $1 if repos.names.include?($1) + end.compact end - end - def add_observer(observer) - repos.add_observer(observer) - bundler.add_observer(observer) + def names + @names ||= Tmux.windows || repos.names + end + + def number(name) + if number = names.index(name) + number + 1 + else + names << name + number(name) + end + end + + def refresh + bundler.refresh + repos.all.each(&:refresh) + end end end end diff --git a/lib/space/models/project/bundler.rb b/lib/space/models/project/bundler.rb new file mode 100644 index 0000000..cdb0070 --- /dev/null +++ b/lib/space/models/project/bundler.rb @@ -0,0 +1,34 @@ +require 'core_ext/enumerable/map_slice' + +module Space + module Models + class Project + class Bundler + include Shell + + commands config: 'bundle config' + + watch '.bundle/config' + + attr_reader :project + + def initialize(project) + @project = project + super() + end + + def config + lines = result(:config).split("\n")[2..-1] || [] + values = lines.map_slice(3) do |name, value, _| + [name, value =~ /: "(.*)"/ && $1] + end + Hash[*values.compact.flatten] + end + + def notify(event, data) + project.notify(event, data) + end + end + end + end +end diff --git a/lib/space/models/repo.rb b/lib/space/models/repo.rb index c6d037b..e900f67 100644 --- a/lib/space/models/repo.rb +++ b/lib/space/models/repo.rb @@ -1,49 +1,54 @@ module Space - class Repo - attr_reader :project, :path, :git, :bundle - - def initialize(project, path) - @project = project - @path = File.expand_path(path) - @git = Git.new(path) - @bundle = Bundle.new(project, path) - end + module Models + class Repo + autoload :Bundle, 'space/models/repo/bundle' + autoload :Dependency, 'space/models/repo/dependency' + autoload :Git, 'space/models/repo/git' + + attr_reader :project, :path, :git, :bundle + + def initialize(project, path) + @project = project + @path = File.expand_path(path) + @git = Git.new(self) + @bundle = Bundle.new(self, project.repos) + end - def name - @name ||= File.basename(path) - end + def name + @name ||= File.basename(path) + end - def number - @number ||= project.number(name) - end + def number + @number ||= project.number(name) + end - def ref - git.commit - end + def ref + git.commit + end - def dependencies - project.repos.select_by_names(bundle.deps.map(&:name)) - end + def deps + bundle.deps + end - def reset - git.reset - bundle.reset - end + def refresh + git.refresh + bundle.refresh + end - def execute(cmd) - chdir do - puts "in #{path}".ansi(:bold, :yellow) - system(cmd) + def execute(cmd) + chdir do + puts "in #{path}".ansi(:bold, :yellow) + system(cmd) + end end - end - def chdir(&block) - Dir.chdir(path, &block) - end + def chdir(&block) + Dir.chdir(path, &block) + end - def add_observer(observer) - git.add_observer(observer) - bundle.add_observer(observer) + def notify(event, data) + project.notify(event, data) + end end end end diff --git a/lib/space/models/repo/bundle.rb b/lib/space/models/repo/bundle.rb new file mode 100644 index 0000000..a68de94 --- /dev/null +++ b/lib/space/models/repo/bundle.rb @@ -0,0 +1,43 @@ +require 'observer' + +module Space + module Models + class Repo + class Bundle + include Shell + + commands check: 'bundle check', + list: 'bundle list' + + # watch 'Gemfile', + # 'Gemfile.lock' + + attr_reader :repo, :repos + + def initialize(repo, repos) + @repo = repo + @repos = repos + super(repo.path) + end + + def clean? + info =~ /dependencies are satisfied/ + end + + def info + result(:check).split("\n").first + end + + def deps + result(:list).scan(/\* ([\w-]+) \(.* ([\d|\w]+)\)/).map do |name, ref| + Dependency.new(repos.find_by_name(name), ref) if repos.names.include?(name) + end.compact + end + + def notify(event, data) + repo.notify(event, data) + end + end + end + end +end diff --git a/lib/space/models/repo/dependency.rb b/lib/space/models/repo/dependency.rb new file mode 100644 index 0000000..7d556be --- /dev/null +++ b/lib/space/models/repo/dependency.rb @@ -0,0 +1,22 @@ +module Space + module Models + class Repo + class Dependency + attr_reader :repo, :ref + + def initialize(repo, ref) + @repo = repo + @ref = ref + end + + def name + repo.name + end + + def fresh? + repo.ref == ref + end + end + end + end +end diff --git a/lib/space/models/repo/git.rb b/lib/space/models/repo/git.rb new file mode 100644 index 0000000..a25235d --- /dev/null +++ b/lib/space/models/repo/git.rb @@ -0,0 +1,56 @@ +require 'observer' + +module Space + module Models + class Repo + class Git + include Shell + + commands status: 'git status', + branch: 'git branch --no-color', + commit: 'git log -1 --no-color HEAD' + + watch '.' + + attr_reader :repo + + def initialize(repo) + @repo = repo + super(repo.path) + end + + def branch + result(:branch) =~ /^\* (.+)/ && $1.strip + end + + def commit + result(:commit) =~ /^commit (\S{7})/ && $1 + end + + def status + dirty? ? :dirty : (ahead? ? :ahead : :clean) + end + + def ahead? + ahead > 0 + end + + def ahead + result(:status) =~ /Your branch is ahead of .* by (\d+) commits?\./ ? $1.to_i : 0 + end + + def dirty? + !clean? + end + + def clean? + result(:status).include?('nothing to commit (working directory clean)') + end + + def notify(event, data) + repo.notify(event, data) + end + end + end + end +end diff --git a/lib/space/models/repos.rb b/lib/space/models/repos.rb index d8fef7c..7c19b9b 100644 --- a/lib/space/models/repos.rb +++ b/lib/space/models/repos.rb @@ -1,51 +1,42 @@ module Space - class Repos - class Scope < Array - attr_reader :repos + module Models + class Repos + autoload :Collection, 'space/models/repos/collection' - def initialize(repos, elements) - @repos = repos - super(elements) - end + attr_accessor :project, :paths, :scope - def self_and_dependencies - Scope.new(repos, (self + map(&:dependencies)).flatten.uniq) + def initialize(project, paths) + @project = project + @paths = paths end - end - - attr_accessor :project, :paths, :scope - - def initialize(project, paths) - @project = project - @paths = paths - end - def all - @all ||= Scope.new(self, paths.map { |path| Repo.new(project, path) }) - end + def all + @all ||= Collection.new(self, paths.map { |path| Repo.new(project, path) }) + end - def scope - @scope || all - end + def names + @names ||= all.map(&:name) + end - def scoped? - !!@scope - end + def scope + @scope || all + end - def names - all.map(&:name) - end + def scoped? + !!@scope + end - def find_by_name(name) - all.detect { |repo| repo.name == name } - end + def find_by_name(name) + all.detect { |repo| repo.name == name } || raise("cannot find repo #{name.inspect}") + end - def select_by_names(names) - Scope.new(self, all.select { |repo| names.include?(repo.name) }) - end + def select_by_names(names) + Collection.new(self, all.select { |repo| names.include?(repo.name) }) + end - def add_observer(observer) - all.each { |repo| repo.add_observer(observer) } + # def add_observer(observer) + # all.each { |repo| repo.add_observer(observer) } + # end end end end diff --git a/lib/space/models/repos/collection.rb b/lib/space/models/repos/collection.rb new file mode 100644 index 0000000..bc58970 --- /dev/null +++ b/lib/space/models/repos/collection.rb @@ -0,0 +1,26 @@ +module Space + module Models + class Repos + class Collection < Array + attr_reader :repos + + def initialize(repos, elements) + @repos = repos + super(elements) + end + + def names + map(&:name) + end + + def self_and_deps + Collection.new(repos, (self + deps).uniq) + end + + def deps + map(&:deps).flatten.map(&:repo).compact + end + end + end + end +end diff --git a/lib/space/models/repos/scope.rb b/lib/space/models/repos/scope.rb new file mode 100644 index 0000000..56c73e3 --- /dev/null +++ b/lib/space/models/repos/scope.rb @@ -0,0 +1,18 @@ +module Space + module Models + class Repos + class Collection < Array + attr_reader :repos + + def initialize(repos, elements) + @repos = repos + super(elements) + end + + def self_and_dependencies + Collection.new(repos, (self + map(&:dependencies)).flatten.uniq) + end + end + end + end +end diff --git a/lib/space/models/watcher.rb b/lib/space/models/watcher.rb deleted file mode 100644 index 80c6458..0000000 --- a/lib/space/models/watcher.rb +++ /dev/null @@ -1,47 +0,0 @@ -module Space - module Watcher - class << self - def ignore - @ignore = true - yield.tap do - @ignore= false - end - end - - def ignore? - !!@ignore - end - end - - def initialize(*args) - super - watch - end - - private - - def watch - targets.each do |path| - Watch.new(path, &method(:update)).run - end - end - - def ignore_updates(*paths, &block) - Watcher.ignore_updates(*paths, &block) - end - - def update(paths) - unless Watcher.ignore? - reset - changed - notify_observers - end - end - - def targets - Array(self.class::WATCH).map do |path| - "#{self.path}/#{path}" - end - end - end -end diff --git a/lib/space/screen.rb b/lib/space/screen.rb index f63474e..bef2f19 100644 --- a/lib/space/screen.rb +++ b/lib/space/screen.rb @@ -1,35 +1,31 @@ module Space class Screen - attr_reader :name, :project, :repos, :view + autoload :Progress, 'space/screen/progress' + autoload :Dashboard, 'space/screen/dashboard' + autoload :View, 'space/screen/view' - def initialize(name, config, project, repos) - @name = name + attr_reader :project, :current + + def initialize(project) @project = project - @repos = repos - @view = View.new(config.template_dir) end - def clear - print "\e[2J\e[0;0H" # clear screen, move cursor to home - puts render_project + def prompt + "#{project.repos.scoped? ? project.repos.scope.map { |r| r.name }.join(', ') : project.name} > " end - def render(options = {}) - clear - repos.scope.self_and_dependencies.each do |repo| - puts render_repo(repo) - end - print options[:prompt].to_s + def display(screen) + @current = create(screen).tap { |screen| screen.render } end - private + def notify(event, data) + current.notify(event, data) + end - def render_project - view.render(:project, name: name, project: project) - end + private - def render_repo(repo) - view.render(:repo, repos: repos, repo: repo, git: repo.git, bundle: repo.bundle) + def create(screen) + self.class.const_get(screen.to_s.capitalize).new(project) end end end diff --git a/lib/space/screen/dashboard.rb b/lib/space/screen/dashboard.rb new file mode 100644 index 0000000..3c62b4c --- /dev/null +++ b/lib/space/screen/dashboard.rb @@ -0,0 +1,28 @@ +module Space + class Screen + class Dashboard < View + def render(options = {}) + clear + render_header + render_repos + print options[:prompt].to_s + end + + def notify(event, data) + render(prompt: prompt) + end + + private + + def render_repos + project.repos.scope.self_and_deps.each do |repo| + render_template(:repo, assigns(repo)) + end + end + + def assigns(repo) + { repos: project.repos, repo: repo, git: repo.git, bundle: repo.bundle } + end + end + end +end diff --git a/lib/space/screen/progress.rb b/lib/space/screen/progress.rb new file mode 100644 index 0000000..eb193e7 --- /dev/null +++ b/lib/space/screen/progress.rb @@ -0,0 +1,14 @@ +module Space + class Screen + class Progress < View + def render + clear + render_header + end + + def notify(event, data) + print '.' + end + end + end +end diff --git a/lib/space/screen/view.rb b/lib/space/screen/view.rb new file mode 100644 index 0000000..aec8923 --- /dev/null +++ b/lib/space/screen/view.rb @@ -0,0 +1,48 @@ +require 'erb' + +module Space + class Screen + class View + include Helpers + + attr_reader :project + + def initialize(project) + @project = project + end + + private + + # TODO duplicate from screen + def prompt + "#{project.repos.scoped? ? project.repos.scope.map { |r| r.name }.join(', ') : project.name} > " + end + + def clear + print "\e[2J\e[0;0H" # clear screen, move cursor to home + end + + def render_header + puts "Project #{project.name}\n\n" + end + + def render_template(name, assigns) + assigns.each { |name, value| assign(name, value) } + print template(name).result(binding) + end + + def assign(key, value) + instance_variable_set(:"@#{key}", value) + (class << self; self; end).send(:attr_reader, key) + end + + def templates + @templates ||= {} + end + + def template(name) + templates[name] ||= ERB.new(File.read("#{project.config.template_dir}/#{name}.erb"), nil, '%<>-') + end + end + end +end diff --git a/lib/space/shell.rb b/lib/space/shell.rb new file mode 100644 index 0000000..0e9734f --- /dev/null +++ b/lib/space/shell.rb @@ -0,0 +1,56 @@ +module Space + module Shell + autoload :Command, 'space/shell/command' + autoload :Watch, 'space/shell/watch' + autoload :Watcher, 'space/shell/watcher' + + module ClassMethods + def commands(commands = nil) + commands ? @commands = commands : @commands + end + + def watch(paths = nil) + paths ? @paths = paths : @paths + end + end + + include Watcher + + class << self + def included(base) + base.extend(ClassMethods) + end + + def all + @all ||= [] + end + + def refresh + all.map(&:refresh) + end + end + + attr_reader :path + + def initialize(path = '.') + @path = File.expand_path(path) + Shell.all << self + super + end + + def result(command) + commands[command].result + end + + def commands + @commands ||= self.class.commands.inject({}) do |commands, (key, command)| + commands.merge(key => Command.new(self, command)) + end + end + + def refresh + commands.each { |key, command| command.run } + notify(:refresh, nil) + end + end +end diff --git a/lib/space/shell/command.rb b/lib/space/shell/command.rb new file mode 100644 index 0000000..18efec5 --- /dev/null +++ b/lib/space/shell/command.rb @@ -0,0 +1,60 @@ +require 'ansi/code' + +module Space + module Shell + class Command + class << self + def execute(command) + `#{command}` + end + end + + attr_reader :context, :command, :result + + def initialize(context, command) + @context = context + @command = command + end + + def run + notifying do + @result = chain.call + end + end + + private + + def notifying(&block) + old = result + yield.tap do |new| + notify unless old == new + end + end + + def notify + context.notify(command, result) + end + + def chain + runner = -> { clean(self.class.execute(command)) } + filters.reverse.inject(runner) { |runner, filter| -> { filter.call(&runner) } } + end + + def filters + [method(:chdir), ::Bundler.method(:with_clean_env) ] + end + + def chdir(&block) + Dir.chdir(context.path) { |path| block.call } + end + + def clean(string) + strip_ansi(string.chomp) + end + + def strip_ansi(string) + string.gsub(ANSI::Code::PATTERN, '') + end + end + end +end diff --git a/lib/space/shell/watch.rb b/lib/space/shell/watch.rb new file mode 100644 index 0000000..b4df505 --- /dev/null +++ b/lib/space/shell/watch.rb @@ -0,0 +1,46 @@ +require 'rb-fsevent' + +module Space + module Shell + class Watch + LATENCY = 0 + NO_DEFER = FALSE + + attr_reader :path, :callback + + def initialize(path, &block) + @path = File.expand_path(path) + @callback = block + self + end + + def run + Thread.new do + watch + end + end + + private + + def watch + fsevent.watch(path, file: file?, latency: LATENCY, no_defer: NO_DEFER) do |paths| + fsevent.stop + callback.call(paths) + # sleep(1) + fsevent.run + end + fsevent.run + rescue Exception => e + puts e.message, e.backtrace + end + + def file? + File.file?(path) + end + + def fsevent + @fsevent ||= FSEvent.new + end + end + end +end diff --git a/lib/space/shell/watcher.rb b/lib/space/shell/watcher.rb new file mode 100644 index 0000000..6ea5975 --- /dev/null +++ b/lib/space/shell/watcher.rb @@ -0,0 +1,46 @@ +module Space + module Shell + module Watcher + class << self + def ignore + @ignore = true + yield.tap do + @ignore= false + end + end + + def ignore? + !!@ignore + end + end + + def initialize(*args) + watch + end + + private + + def watch + targets.each do |path| + Watch.new(path, &method(:trigger)).run + end + end + + def ignore_updates(*paths, &block) + Watcher.ignore_updates(*paths, &block) + end + + def trigger(paths) + unless Watcher.ignore? + refresh + end + end + + def targets + Array(self.class.watch).map do |path| + "#{self.path}/#{path}" + end + end + end + end +end diff --git a/lib/space/templates/project.erb b/lib/space/templates/header.erb similarity index 95% rename from lib/space/templates/project.erb rename to lib/space/templates/header.erb index e83ac4c..3950402 100644 --- a/lib/space/templates/project.erb +++ b/lib/space/templates/header.erb @@ -1,2 +1 @@ <%= project_title %> - diff --git a/lib/space/tmux.rb b/lib/space/tmux.rb index e93ff82..649cfcd 100644 --- a/lib/space/tmux.rb +++ b/lib/space/tmux.rb @@ -8,3 +8,4 @@ def windows end end end + diff --git a/lib/space/view.rb b/lib/space/view.rb deleted file mode 100644 index bd7cc1e..0000000 --- a/lib/space/view.rb +++ /dev/null @@ -1,33 +0,0 @@ -require 'erb' - -module Space - class View - include Helpers - - attr_reader :template_dir - - def initialize(template_dir) - @template_dir = template_dir - end - - def render(name, assigns) - assigns.each { |name, value| assign(name, value) } - template(name).result(binding) - end - - private - - def assign(key, value) - instance_variable_set(:"@#{key}", value) - (class << self; self; end).send(:attr_reader, key) - end - - def templates - @templates ||= {} - end - - def template(name) - templates[name] ||= ERB.new(File.read("#{template_dir}/#{name}.erb"), nil, '%<>-') - end - end -end diff --git a/lib/space/watch.rb b/lib/space/watch.rb deleted file mode 100644 index 85c180d..0000000 --- a/lib/space/watch.rb +++ /dev/null @@ -1,45 +0,0 @@ -require 'rb-fsevent' - -module Space - class Watch - LATENCY = 0 - NO_DEFER = FALSE - - attr_reader :path, :callback - - def initialize(path, &block) - @path = File.expand_path(path) - @callback = block - self - end - - def run - Thread.new do - watch - end - end - - private - - def watch - fsevent.watch(path, file: file?, latency: LATENCY, no_defer: NO_DEFER) do |paths| - fsevent.stop - callback.call(paths) - # sleep(1) - fsevent.run - end - fsevent.run - rescue Exception => e - puts e.message, e.backtrace - end - - def file? - File.file?(path) - end - - def fsevent - @fsevent ||= FSEvent.new - end - end -end - diff --git a/spec/action/parser_spec.rb b/spec/action/parser_spec.rb deleted file mode 100644 index 0ed1958..0000000 --- a/spec/action/parser_spec.rb +++ /dev/null @@ -1,36 +0,0 @@ -require 'spec_helper' - -describe Action::Parser do - let(:names) { %w(travis-ci travis-core) } - let(:parser) { Action::Parser.new(names) } - - describe 'parse' do - it 'returns [["travis-ci"], nil] for "travis-ci"' do - parser.parse('travis-ci').should == [['travis-ci'], nil] - end - - it 'returns [["travis-ci"], "reload"] for "travis-ci reload"' do - parser.parse('travis-ci reload').should == [['travis-ci'], 'reload'] - end - - it 'returns [["travis-ci", "travis-core"], "reload"] for "travis-ci travis-core reload"' do - parser.parse('travis-ci travis-core reload').should == [['travis-ci', 'travis-core'], 'reload'] - end - - it 'returns [["travis-ci"], nil] for "1"' do - parser.parse('1').should == [['travis-ci'], nil] - end - - it 'returns [["travis-ci"], "reload"] for "1 reload"' do - parser.parse('1 reload').should == [['travis-ci'], 'reload'] - end - - it 'returns [["travis-ci", "travis-core"], "reload"] for "1 2 reload"' do - parser.parse('1 2 reload').should == [['travis-ci', 'travis-core'], 'reload'] - end - - it 'returns [nil, "reload"] for "reload"' do - parser.parse('reload').should == [nil, 'reload'] - end - end -end diff --git a/spec/action_spec.rb b/spec/action_spec.rb new file mode 100644 index 0000000..17a0e21 --- /dev/null +++ b/spec/action_spec.rb @@ -0,0 +1,8 @@ +# +# * actions like `bundle update`, `git commit`, `rm file` should +# display the progress bar and then re-render +# +# * actions like `refresh` can work the same way +# +# * actions like `scope` can just re-render the dashboard because +# they don't touch any files diff --git a/spec/app/handler_spec.rb b/spec/app/handler_spec.rb new file mode 100644 index 0000000..68047c1 --- /dev/null +++ b/spec/app/handler_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe Space::App::Handler do + let(:project) { stub('project') } + let(:handler) { Space::App::Handler.new(project) } + + def command_for(command) + handler.send(:command_for, nil, command) + end + + describe 'command_for' do + it 'returns a Command::Refresh instance for an empty command string' do + command_for('refresh').should be_a(Space::App::Command::Refresh) + end + + it 'returns a Command::Scope instance for an empty command string' do + command_for('').should be_a(Space::App::Command::Scope) + end + + it 'returns a Command::Unscope instance for "-"' do + command_for('-').should be_a(Space::App::Command::Unscope) + end + + it 'returns a Command::Execute instance for "ls -al"' do + command_for('ls -al').should be_a(Space::App::Command::Execute) + end + end +end diff --git a/spec/app/parser_spec.rb b/spec/app/parser_spec.rb new file mode 100644 index 0000000..ff5b3b2 --- /dev/null +++ b/spec/app/parser_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +describe Space::App::Parser do + let(:names) { %w(travis-ci travis-core) } + let(:parser) { Space::App::Parser.new(names) } + + describe 'parse' do + results = { + 'travis-ci' => [['travis-ci'], nil], + 'travis-ci travis-core' => [['travis-ci', 'travis-core'], nil], + '1' => [['travis-ci'], nil], + '1 2' => [['travis-ci', 'travis-core'], nil], + 'travis-ci reload' => [['travis-ci'], 'reload'], + 'travis-ci travis-core reload' => [['travis-ci', 'travis-core'], 'reload'], + '1 reload' => [['travis-ci'], 'reload'], + '1 2 reload' => [['travis-ci', 'travis-core'], 'reload'], + } + + results.each do |line, result| + it "returns #{result.inspect} for #{line.inspect}" do + parser.parse(line.dup).should == result + end + end + end +end + diff --git a/spec/app_spec.rb b/spec/app_spec.rb new file mode 100644 index 0000000..8950e9f --- /dev/null +++ b/spec/app_spec.rb @@ -0,0 +1,57 @@ +# encoding: utf-8 + +require 'spec_helper' + +describe Space::App do + APP_COMMANDS = { + 'bundle config' => "Settings are listed in order of priority. The top value will be used.\n", + 'bundle check' => "The Gemfile's dependencies are satisfied", + 'bundle list' => " * travis-core (0.0.1 123456)", + 'git status' => "nothing to commit (working directory clean)", + 'git branch --no-color' => " feature\n* master\n", + 'git log -1 --no-color HEAD' => "commit ce8fb952efdd29371ebdc", + } + + let(:app) { Space::App.new('travis') } + let(:config) { Space::Config.new(base_dir: '~/Development/projects/travis', repositories: %w(travis-ci travis-core)) } + + before :each do + Space::Config.stubs(:load).returns(config) + stub_commands + app.stubs(:prompt) + end + + def stub_commands + APP_COMMANDS.each do |command, result| + Space::Shell::Command.stubs(:execute).with(command).returns(result) + end + end + + describe 'running the app' do + subject { strip_ansi(capture_stdout { app.run }) } + + it 'lists the repository' do + should =~ /travis-ci:/ + end + + it 'displays the current branch' do + should =~ /master/ + end + + it 'displays the current commit hash' do + should =~ /ce8fb95/ + end + + it 'displays the git status' do + should =~ /Git: ✔/ + end + + it 'displays the bundle status' do + should =~ /Bundle: ✔/ + end + + it 'lists the dependencies' do + should =~ /• 123456 ⚡ travis-core/ + end + end +end diff --git a/spec/config_spec.rb b/spec/config_spec.rb new file mode 100644 index 0000000..374aa10 --- /dev/null +++ b/spec/config_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe Space::Config do + let(:config) { Space::Config.new } + + describe 'class methods' do + describe 'load' do + let(:local_filename) { File.expand_path('./.space/travis.yml') } + let(:global_filename) { File.expand_path('~/.space/travis.yml') } + + before :each do + File.stubs(:exists?).returns(false) + File.stubs(:read).with(local_filename).returns('local: true') + File.stubs(:read).with(global_filename).returns('global: true') + end + + subject { Space::Config.load('travis') } + + it 'loads a local config file if present' do + File.stubs(:exists?).with(local_filename).returns(true) + subject.local?.should be_true + end + + it 'loads globale config file if present' do + File.stubs(:exists?).with(global_filename).returns(true) + subject.global?.should be_true + end + + it 'raises an exception if no config file can be found' do + -> { subject }.should raise_error + end + end + end + + describe 'defaults' do + it 'template_dir defaults to the expanded path lib/space/templates' do + config.template_dir.should == File.expand_path('../../lib/space/templates', __FILE__) + end + end +end diff --git a/spec/models/command_spec.rb b/spec/models/command_spec.rb deleted file mode 100644 index a1ea7ff..0000000 --- a/spec/models/command_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'spec_helper' - -describe Command do - describe 'result' do - it 'changes to the path' - it 'calls the command' - it 'removes ansi codes' - end - - describe 'strip_ansi' do - it 'removes \e[34m' - it 'removes \e[0m' - end -end - diff --git a/spec/models/commands_spec.rb b/spec/models/commands_spec.rb deleted file mode 100644 index 5022de6..0000000 --- a/spec/models/commands_spec.rb +++ /dev/null @@ -1,16 +0,0 @@ -require 'spec_helper' - -describe Commands do - describe 'result' do - it 'returns the result from the given command' - end - - describe 'commands' do - it 'returns a hash of commands as defined for this class' - end - - describe 'reset' do - it 'calls reset on all commands' - end -end - diff --git a/spec/models/dependency_spec.rb b/spec/models/dependency_spec.rb deleted file mode 100644 index 5606d4d..0000000 --- a/spec/models/dependency_spec.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'spec_helper' - -describe Dependency do - describe 'fresh?' do - it 'returns true if the (locked) dep ref is the actual current repository ref' - it 'returns false if the (locked) dep ref differs from the actual current repository ref' - end - - describe 'repo' do - it 'it returns the repository this dependency refers to' - end -end - diff --git a/spec/models/git_spec.rb b/spec/models/git_spec.rb deleted file mode 100644 index 0c1633e..0000000 --- a/spec/models/git_spec.rb +++ /dev/null @@ -1,59 +0,0 @@ -require 'spec_helper' - -describe Git do - let(:git) { Git.new('path/to/somewhere') } - - describe 'ahead?' do - it 'returns true if ahead is greater than 0' do - git.stubs(:ahead).returns(1) - git.ahead?.should be_true - end - - it 'returns false if ahead equals 0' do - git.stubs(:ahead).returns(0) - git.ahead?.should be_false - end - end - - describe 'ahead' do - it "returns 2 if `git status` contains: ahead of '...' by 2 commits." do - git.stubs(:result).with(:status).returns %(Your branch is ahead of 'origin/master' by 2 commits.\n\nnothing to commit (working directory clean)) - git.ahead.should == 2 - end - - it "returns 1 if `git status` contains: ahead of '...' by 1 commit." do - git.stubs(:result).with(:status).returns %(Your branch is ahead of 'origin/master' by 1 commit.\n\nnothing to commit (working directory clean)) - git.ahead.should == 1 - end - - it "returns 0 if `git status` does not contain a line about commits ahead of a remote branch." do - git.stubs(:result).with(:status).returns %(Your branch is nothing to commit (working directory clean)) - git.ahead.should == 0 - end - end - - describe 'status' do - - end - - describe 'clean?' do - it 'returns true if `git status` contains: nothing to commit' do - git.stubs(:result).with(:status).returns 'nothing to commit (working directory clean)' - git.clean?.should be_true - end - - it 'returns false if `git status` does not contain: nothing to commit' do - git.stubs(:result).with(:status).returns 'Changes not staged for commit:' - git.clean?.should be_false - end - end - - describe 'branch' do - it 'returns the current branch name from `git branch --no-color`' - end - - describe 'commit' do - it 'it returns the commit hash from `git log -1 head`' - end -end - diff --git a/spec/models/project/bundler_spec.rb b/spec/models/project/bundler_spec.rb new file mode 100644 index 0000000..8d5f3c6 --- /dev/null +++ b/spec/models/project/bundler_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +describe Space::Models::Project::Bundler do + BUNDLER_RESULTS = { + :config => <<-config +Settings are listed in order of priority. The top value will be used. + +without + Set for the current user (/Volumes/Users/sven/.bundle/config): "" + +local_override_require_branch + Set for the current user (/Volumes/Users/sven/.bundle/config): "false" +config + } + + let(:project) { stub('project') } + let(:bundler) { Space::Models::Project::Bundler.new(project) } + + def stub_command(command, result = command) + bundler.commands[command].stubs(:result).returns BUNDLER_RESULTS[result].dup + end + + describe 'config' do + it 'returns the global bundler config as a hash' do + stub_command :config + bundler.config['local_override_require_branch'].should == 'false' + end + end +end + + + diff --git a/spec/models/repo/bundle_spec.rb b/spec/models/repo/bundle_spec.rb new file mode 100644 index 0000000..0d50aaf --- /dev/null +++ b/spec/models/repo/bundle_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +describe Space::Models::Repo::Bundle do + BUNDLE_RESULTS = { + check_clean: %(The Gemfile's dependencies are satisfied), + check_dirty: %(Your Gemfile's dependencies could not be satisfied), + list: %( * travis-ci (0.0.1 123456)) + } + + let(:repo) { stub('repo', path: '.') } + let(:repos) { stub('repos', names: %w(travis-ci), find_by_name: repo) } + let(:bundle) { Space::Models::Repo::Bundle.new(repo, repos) } + + def stub_command(command, result = command) + bundle.commands[command].stubs(:result).returns BUNDLE_RESULTS[result].dup + end + + describe 'clean?' do + it 'returns true if info includes "dependencies are satisfied"' do + stub_command :check, :check_clean + bundle.clean?.should be_true + end + + it 'returns false if info does not include "dependencies are satisfied"' do + stub_command :check, :check_dirty + bundle.clean?.should be_false + end + end + + describe 'info' do + it 'returns the first line from `bundle check`' do + stub_command :check, :check_clean + bundle.info.should == BUNDLE_RESULTS[:check_clean] + end + end + + describe 'deps' do + it 'returns dependencies listend in `bundle list` that match the app name' do + stub_command :list + dep = bundle.deps.first + [dep.repo, dep.ref].should == [repo, '123456'] + end + end +end + + diff --git a/spec/models/repo/git_spec.rb b/spec/models/repo/git_spec.rb new file mode 100644 index 0000000..8035977 --- /dev/null +++ b/spec/models/repo/git_spec.rb @@ -0,0 +1,88 @@ +require 'spec_helper' + +describe Space::Models::Repo::Git do + GIT_RESULTS = { + branch: %( feature\n* master\n), + commit: %(commit ce8fb952efdd29371ebdc572f8f47e59a9bd5ee5\nAuthor: Sven Fuchs \nDate: Fri Apr 6 23:17:04 2012 +0200), + status_clean: %(nothing to commit (working directory clean)), + status_dirty: %(Changes not staged for commit:), + ahead_by_2: %(Your branch is ahead of 'origin/master' by 2 commits.\n\nnothing to commit (working directory clean)), + ahead_by_1: %(Your branch is ahead of 'origin/master' by 1 commit.\n\nnothing to commit (working directory clean)), + ahead_by_0: %(Your branch is nothing to commit (working directory clean)) + } + + let(:repo) { stub('repo', :path => '.') } + let(:git) { Space::Models::Repo::Git.new(repo) } + + def stub_command(command, result) + git.commands[command].stubs(:result).returns GIT_RESULTS[result] + end + + it 'branch returns the git branch' do + stub_command :branch, :branch + git.branch.should == 'master' + end + + it 'commit returns the git commit' do + stub_command :commit, :commit + git.commit.should == 'ce8fb95' + end + + describe 'status' do + it 'returns :dirty if the working directory is dirty' do + stub_command :status, :status_dirty + git.status.should == :dirty + end + + it 'returns :ahead if the repository is ahead of origin' do + stub_command :status, :ahead_by_1 + git.status.should == :ahead + end + + it 'returns :clean if the working directory is clean and the repository not ahead of origin' do + stub_command :status, :status_clean + git.status.should == :clean + end + end + + describe 'ahead?' do + it 'returns true if ahead is greater than 0' do + stub_command :status, :ahead_by_1 + git.ahead?.should be_true + end + + it 'returns false if ahead equals 0' do + stub_command :status, :ahead_by_0 + git.ahead?.should be_false + end + end + + describe 'ahead' do + it "returns 2 if `git status` contains: ahead of '...' by 2 commits." do + stub_command :status, :ahead_by_2 + git.ahead.should == 2 + end + + it "returns 1 if `git status` contains: ahead of '...' by 1 commit." do + stub_command :status, :ahead_by_1 + git.ahead.should == 1 + end + + it "returns 0 if `git status` does not contain a line about commits ahead of a remote branch." do + stub_command :status, :ahead_by_0 + git.ahead.should == 0 + end + end + + describe 'clean?' do + it 'returns true if `git status` contains: nothing to commit' do + stub_command :status, :status_clean + git.clean?.should be_true + end + + it 'returns false if `git status` does not contain: nothing to commit' do + stub_command :status, :status_dirty + git.clean?.should be_false + end + end +end diff --git a/spec/models/repo_spec.rb b/spec/models/repo_spec.rb index b67b425..67de759 100644 --- a/spec/models/repo_spec.rb +++ b/spec/models/repo_spec.rb @@ -1,34 +1,13 @@ require 'spec_helper' -describe Repo do - describe 'name' do - it 'returns the basename of the repository directory' do - end - end - - describe 'ref' do - it 'returns the git commit' do +describe Space::Models::Repo do + let(:project) { stub('project', repos: stub('repos')) } + let(:repo) { Space::Models::Repo.new(project, './travis-ci') } + describe 'name' do + it 'returns the basename of its directory' do + repo.name.should == 'travis-ci' end end - - describe 'current?' do - it 'returns true when the app scope is the current repo' - it 'returns false when the app scope is not the current repo' - end - - describe 'dependent_repos' do - it 'returns repos that this repo depends on' - end - - describe 'reset' do - it 'resets its git' - it 'resets its bundle' - end - - describe 'execute' do - it 'changes to the repository directory' - it 'runds the given command' - end end diff --git a/spec/models/repos_spec.rb b/spec/models/repos_spec.rb deleted file mode 100644 index 6097ad3..0000000 --- a/spec/models/repos_spec.rb +++ /dev/null @@ -1,31 +0,0 @@ -require 'spec_helper' - -describe Repos do - let(:config) { Hashr.new(paths: paths) } - let(:paths) { %w(/path/to/foo /path/to/bar /path/to/baz) } - - before :each do - App.stubs(:config).returns(config) - end - - # describe 'class methods' do - # it 'all returns a collection of all repos as defined in the config' do - # Repos.all.map(&:path).should == config.paths - # end - - # it 'select returns a collection scoped to the given names' do - # Repos.select(%w(foo bar)).names.should == %w(foo bar) - # end - - # it 'find_by_name finds the repository by name' do - # Repos.find_by_name('foo').name.should == 'foo' - # end - # end - - # describe 'instance methods' do - # it 'names returns the names of the contained repositories' do - # Repos.all.names.should == %w(foo bar baz) - # end - # end -end - diff --git a/spec/shell/command_spec.rb b/spec/shell/command_spec.rb new file mode 100644 index 0000000..d13f88c --- /dev/null +++ b/spec/shell/command_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +module Mock + module Shell + def events + @events ||= [] + end + + def notify(event, data) + events << [event, data] + end + + def path + '.' + end + end +end + +describe Space::Shell::Command do + include Mock::Shell + + let(:command) { Space::Shell::Command.new(self, 'echo "FOO"') } + + describe 'run' do + subject { command.run } + + it 'runs the command' do + should == 'FOO' + end + + it 'switches to the context directory' do + Dir.expects(:chdir).with('.').returns('bar') + should == 'bar' + end + + it 'clears the bundler env' do + Bundler.expects(:with_clean_env).returns('bar') + should == 'bar' + end + + it 'notifies the context about changes' do + subject + events.should include(['echo "FOO"', 'FOO']) + end + end +end diff --git a/spec/shell_spec.rb b/spec/shell_spec.rb new file mode 100644 index 0000000..ab0c8a0 --- /dev/null +++ b/spec/shell_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +class Shell + include Space::Shell + commands :list => 'ls -al' +end + +describe Space::Shell do + let(:shell) { Shell.new('path') } + + describe '.commands' do + it 'defines the commands this class cares about' do + Shell.commands[:list].should == 'ls -al' + end + end + + describe 'initialize' do + it 'registers the consumer class to Space::Shell.all' do + Space::Shell.all.should include(shell) + end + end + + describe 'commands' do + it 'returns a hash holding command instances' do + shell.commands[:list].command.should == 'ls -al' + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index a8121c0..d89e0ed 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,4 +1,26 @@ require 'mocha' require 'space' +require 'ansi' -include Space +module Ansi + def strip_ansi(string) + string.gsub!(/\e\[[\d]+(;[\d]+)?m/, '') + string.gsub!("\e[2J\e[0;0H", '') + end +end + +RSpec.configure do |config| + config.include Ansi + config.mock_framework = :mocha +end + +module Kernel + def capture_stdout + out = StringIO.new + $stdout = out + yield + return out.string + ensure + $stdout = STDOUT + end +end