space 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/Gemfile +2 -0
  2. data/Gemfile.lock +17 -2
  3. data/Guardfile +5 -0
  4. data/README.md +4 -0
  5. data/Rakefile +1 -1
  6. data/bin/space +2 -1
  7. data/lib/core_ext/{enumerable.rb → enumerable/map_slice.rb} +2 -7
  8. data/lib/core_ext/{string.rb → string/wrap.rb} +1 -2
  9. data/lib/space.rb +6 -16
  10. data/lib/space/app.rb +36 -48
  11. data/lib/space/app/command.rb +45 -0
  12. data/lib/space/app/command/builtin.rb +43 -0
  13. data/lib/space/app/command/development.rb +60 -0
  14. data/lib/space/app/handler.rb +47 -0
  15. data/lib/space/app/logger.rb +21 -0
  16. data/lib/space/{action → app}/parser.rb +3 -2
  17. data/lib/space/config.rb +14 -6
  18. data/lib/space/events.rb +34 -0
  19. data/lib/space/events/buffer.rb +18 -0
  20. data/lib/space/events/event.rb +14 -0
  21. data/lib/space/helpers.rb +30 -15
  22. data/lib/space/models.rb +7 -0
  23. data/lib/space/models/project.rb +48 -0
  24. data/lib/space/models/project/bundler.rb +30 -0
  25. data/lib/space/models/repo.rb +46 -29
  26. data/lib/space/models/repo/bundle.rb +39 -0
  27. data/lib/space/models/repo/dependency.rb +22 -0
  28. data/lib/space/models/repo/git.rb +52 -0
  29. data/lib/space/models/repos.rb +31 -26
  30. data/lib/space/models/repos/collection.rb +26 -0
  31. data/lib/space/models/repos/scope.rb +18 -0
  32. data/lib/space/screen.rb +19 -15
  33. data/lib/space/screen/dashboard.rb +28 -0
  34. data/lib/space/screen/progress.rb +15 -0
  35. data/lib/space/screen/view.rb +43 -0
  36. data/lib/space/shell.rb +56 -0
  37. data/lib/space/shell/command.rb +63 -0
  38. data/lib/space/shell/watch.rb +50 -0
  39. data/lib/space/shell/watcher.rb +40 -0
  40. data/lib/space/templates/{project.erb → header.erb} +0 -1
  41. data/lib/space/templates/{repository.erb → repo.erb} +1 -1
  42. data/lib/space/tmux.rb +11 -0
  43. data/lib/space/version.rb +1 -1
  44. metadata +33 -15
  45. data/lib/space/action.rb +0 -84
  46. data/lib/space/models/bundle.rb +0 -44
  47. data/lib/space/models/command.rb +0 -28
  48. data/lib/space/models/commands.rb +0 -23
  49. data/lib/space/models/dependency.rb +0 -18
  50. data/lib/space/models/git.rb +0 -23
  51. data/lib/space/view.rb +0 -25
@@ -0,0 +1,52 @@
1
+ require 'observer'
2
+
3
+ module Space
4
+ module Models
5
+ class Repo
6
+ class Git
7
+ include Events, Shell
8
+
9
+ commands status: 'git status',
10
+ branch: 'git branch --no-color',
11
+ commit: 'git log -1 --no-color HEAD'
12
+
13
+ watch '.'
14
+
15
+ attr_reader :repo
16
+
17
+ def initialize(repo)
18
+ @repo = repo
19
+ super(repo.path)
20
+ end
21
+
22
+ def branch
23
+ result(:branch) =~ /^\* (.+)/ && $1.strip
24
+ end
25
+
26
+ def commit
27
+ result(:commit) =~ /^commit (\S{7})/ && $1
28
+ end
29
+
30
+ def status
31
+ dirty? ? :dirty : (ahead? ? :ahead : :clean)
32
+ end
33
+
34
+ def ahead?
35
+ ahead > 0
36
+ end
37
+
38
+ def ahead
39
+ result(:status) =~ /Your branch is ahead of .* by (\d+) commits?\./ ? $1.to_i : 0
40
+ end
41
+
42
+ def dirty?
43
+ !clean?
44
+ end
45
+
46
+ def clean?
47
+ result(:status).include?('nothing to commit (working directory clean)')
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -1,45 +1,50 @@
1
1
  module Space
2
- class Repos < Array
3
- class << self
2
+ module Models
3
+ class Repos
4
+ include Events
5
+
6
+ autoload :Collection, 'space/models/repos/collection'
7
+
8
+ attr_accessor :project, :paths, :scope
9
+
10
+ def initialize(project, paths)
11
+ @project = project
12
+ @paths = paths
13
+ end
14
+
4
15
  def all
5
- @all ||= create(App.config.paths)
16
+ @all ||= Collection.new(self, paths.map { |path| Repo.new(project, path) })
6
17
  end
7
18
 
8
19
  def names
9
20
  @names ||= all.map(&:name)
10
21
  end
11
22
 
12
- def select(names)
13
- new(all.select { |repo| names.include?(repo.name) })
23
+ def scope=(scope)
24
+ @scope = scope
25
+ notify(:update, nil)
14
26
  end
15
27
 
16
- def find_by_name(name)
17
- all.find_by_name(name)
28
+ def scope
29
+ @scope || all
18
30
  end
19
31
 
20
- def create(paths)
21
- new(paths.map_with_index { |path, ix| Repo.new(ix + 1, path) })
32
+ def scoped?
33
+ !!@scope
22
34
  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
35
 
33
- # def name
34
- # "#{App.name}-(#{names.join('|').gsub("#{App.config.name}-", '')})"
35
- # end
36
+ def find_by_name(name)
37
+ all.detect { |repo| repo.name == name } || raise("cannot find repo #{name.inspect}")
38
+ end
36
39
 
37
- def find_by_name(name)
38
- detect { |repo| repo.name == name }
39
- end
40
+ def select_by_names(names)
41
+ Collection.new(self, all.select { |repo| names.include?(repo.name) })
42
+ end
40
43
 
41
- def self_and_dependencies
42
- self.class.new(map { |repo| [repo] + repo.dependencies }.flatten.uniq)
44
+ def subscribe(*args)
45
+ super
46
+ all.each { |repo| repo.subscribe(*args) }
47
+ end
43
48
  end
44
49
  end
45
50
  end
@@ -0,0 +1,26 @@
1
+ module Space
2
+ module Models
3
+ class Repos
4
+ class Collection < Array
5
+ attr_reader :repos
6
+
7
+ def initialize(repos, elements)
8
+ @repos = repos
9
+ super(elements)
10
+ end
11
+
12
+ def names
13
+ map(&:name)
14
+ end
15
+
16
+ def self_and_deps
17
+ Collection.new(repos, (self + deps).uniq)
18
+ end
19
+
20
+ def deps
21
+ map(&:deps).flatten.map(&:repo).compact
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,18 @@
1
+ module Space
2
+ module Models
3
+ class Repos
4
+ class Collection < Array
5
+ attr_reader :repos
6
+
7
+ def initialize(repos, elements)
8
+ @repos = repos
9
+ super(elements)
10
+ end
11
+
12
+ def self_and_dependencies
13
+ Collection.new(repos, (self + map(&:dependencies)).flatten.uniq)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
data/lib/space/screen.rb CHANGED
@@ -1,28 +1,32 @@
1
1
  module Space
2
2
  class Screen
3
- attr_reader :app
3
+ autoload :Progress, 'space/screen/progress'
4
+ autoload :Dashboard, 'space/screen/dashboard'
5
+ autoload :View, 'space/screen/view'
4
6
 
5
- def initialize(app)
6
- @app = app
7
+ attr_reader :project, :view
8
+
9
+ def initialize(project)
10
+ @project = project
11
+ end
12
+
13
+ def display(name)
14
+ @view = create(name)
15
+ render
7
16
  end
8
17
 
9
18
  def render
10
- system 'clear'
11
- puts render_project
12
- repos = app.repos.scoped? ? app.repos.self_and_dependencies : app.repos
13
- repos.each do |repo|
14
- puts render_repo(repo)
15
- end
19
+ view.render
16
20
  end
17
21
 
18
- private
22
+ def notify(event)
23
+ view.notify(event)
24
+ end
19
25
 
20
- def render_project
21
- View.new.render(:project, :app => app, :bundle => app.bundle)
22
- end
26
+ private
23
27
 
24
- def render_repo(repo)
25
- View.new.render(:repo, :app => app, :repo => repo, :git => repo.git, :bundle => repo.bundle)
28
+ def create(screen)
29
+ self.class.const_get(screen.to_s.capitalize).new(project)
26
30
  end
27
31
  end
28
32
  end
@@ -0,0 +1,28 @@
1
+ module Space
2
+ class Screen
3
+ class Dashboard < View
4
+ def render
5
+ App.logger.debug('RENDER dashboard')
6
+ clear
7
+ render_header
8
+ render_repos
9
+ end
10
+
11
+ def notify(event)
12
+ render
13
+ end
14
+
15
+ private
16
+
17
+ def render_repos
18
+ project.repos.scope.self_and_deps.each do |repo|
19
+ render_template(:repo, assigns(repo))
20
+ end
21
+ end
22
+
23
+ def assigns(repo)
24
+ { repos: project.repos, repo: repo, git: repo.git, bundle: repo.bundle }
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,15 @@
1
+ module Space
2
+ class Screen
3
+ class Progress < View
4
+ def render
5
+ App.logger.debug('RENDER dashboard')
6
+ clear
7
+ render_header
8
+ end
9
+
10
+ def notify(event)
11
+ print '.'
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,43 @@
1
+ require 'erb'
2
+
3
+ module Space
4
+ class Screen
5
+ class View
6
+ include Helpers
7
+
8
+ attr_reader :project
9
+
10
+ def initialize(project)
11
+ @project = project
12
+ end
13
+
14
+ private
15
+
16
+ def clear
17
+ print "\e[2J\e[0;0H" # clear screen, move cursor to home
18
+ end
19
+
20
+ def render_header
21
+ puts "Project #{project.name}\n\n"
22
+ end
23
+
24
+ def render_template(name, assigns)
25
+ assigns.each { |name, value| assign(name, value) }
26
+ print template(name).result(binding)
27
+ end
28
+
29
+ def assign(key, value)
30
+ instance_variable_set(:"@#{key}", value)
31
+ (class << self; self; end).send(:attr_reader, key)
32
+ end
33
+
34
+ def templates
35
+ @templates ||= {}
36
+ end
37
+
38
+ def template(name)
39
+ templates[name] ||= ERB.new(File.read("#{project.config.template_dir}/#{name}.erb"), nil, '%<>-')
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,56 @@
1
+ module Space
2
+ module Shell
3
+ autoload :Command, 'space/shell/command'
4
+ autoload :Watch, 'space/shell/watch'
5
+ autoload :Watcher, 'space/shell/watcher'
6
+
7
+ module ClassMethods
8
+ def commands(commands = nil)
9
+ commands ? @commands = commands : @commands
10
+ end
11
+
12
+ def watch(*paths)
13
+ paths.empty? ? (@paths || []) : (@paths = paths)
14
+ end
15
+ end
16
+
17
+ include Watcher
18
+
19
+ class << self
20
+ def included(base)
21
+ base.extend(ClassMethods)
22
+ end
23
+
24
+ def all
25
+ @all ||= []
26
+ end
27
+
28
+ def refresh
29
+ all.map(&:refresh)
30
+ end
31
+ end
32
+
33
+ attr_reader :path
34
+
35
+ def initialize(path = '.')
36
+ @path = File.expand_path(path)
37
+ Shell.all << self
38
+ super
39
+ end
40
+
41
+ def result(command)
42
+ commands[command].result
43
+ end
44
+
45
+ def commands
46
+ @commands ||= self.class.commands.inject({}) do |commands, (key, command)|
47
+ commands.merge(key => Command.new(self, command))
48
+ end
49
+ end
50
+
51
+ def refresh
52
+ commands.each { |key, command| command.run }
53
+ # notify(:refresh, self.class.name)
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,63 @@
1
+ require 'ansi/code'
2
+
3
+ module Space
4
+ module Shell
5
+ class Command
6
+ class << self
7
+ def execute(command)
8
+ `#{command}`
9
+ end
10
+ end
11
+
12
+ attr_reader :context, :command, :result
13
+
14
+ def initialize(context, command)
15
+ @context = context
16
+ @command = command
17
+ end
18
+
19
+ def run
20
+ Watcher.suspend do
21
+ App.logger.debug "RUNNING #{command} [#{context.path}]"
22
+ notifying do
23
+ @result = chain.call
24
+ end
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def notifying(&block)
31
+ old = result
32
+ yield.tap do |new|
33
+ notify unless old == new
34
+ end
35
+ end
36
+
37
+ def notify
38
+ context.notify(command, result)
39
+ end
40
+
41
+ def chain
42
+ runner = -> { clean(self.class.execute(command)) }
43
+ filters.reverse.inject(runner) { |runner, filter| -> { filter.call(&runner) } }
44
+ end
45
+
46
+ def filters
47
+ [method(:chdir), ::Bundler.method(:with_clean_env) ]
48
+ end
49
+
50
+ def chdir(&block)
51
+ Dir.chdir(context.path) { |path| block.call }
52
+ end
53
+
54
+ def clean(string)
55
+ strip_ansi(string.chomp)
56
+ end
57
+
58
+ def strip_ansi(string)
59
+ string.gsub(ANSI::Code::PATTERN, '')
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,50 @@
1
+ require 'rb-fsevent'
2
+
3
+ module Space
4
+ module Shell
5
+ class Watch
6
+ LATENCY = 0
7
+ NO_DEFER = FALSE
8
+
9
+ attr_reader :path, :callback, :mutex
10
+
11
+ def initialize(path, &block)
12
+ @path = File.expand_path(path)
13
+ @callback = block
14
+ @mutex = Mutex.new
15
+ self
16
+ end
17
+
18
+ def run
19
+ Thread.new do
20
+ App.logger.debug("WATCHING #{path}")
21
+ watch
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def watch
28
+ fsevent.watch(path, file: file?, latency: LATENCY, no_defer: NO_DEFER) do |paths|
29
+ App.logger.debug("WATCH triggered: #{paths.inspect}")
30
+ fsevent.stop
31
+ mutex.synchronize do
32
+ callback.call(paths)
33
+ end
34
+ fsevent.run
35
+ end
36
+ fsevent.run
37
+ rescue Exception => e
38
+ puts e.message, e.backtrace
39
+ end
40
+
41
+ def file?
42
+ File.file?(path)
43
+ end
44
+
45
+ def fsevent
46
+ @fsevent ||= FSEvent.new
47
+ end
48
+ end
49
+ end
50
+ end