space 0.0.3 → 0.0.4

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.
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