consular 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,220 @@
1
+ require 'thor'
2
+
3
+ module Consular
4
+
5
+ # The CLI provides the command line interface for Consular. These are
6
+ # interfaced via the consular bin file.
7
+ class CLI < Thor
8
+ include Thor::Actions
9
+
10
+ # Source root for Thor to find templates
11
+ def self.source_root; File.expand_path('../../', __FILE__); end
12
+
13
+ # Run the Termfile or project script.
14
+ #
15
+ # @param [String] project
16
+ # Name of the project script. Otherwise leave blank for Termfile.
17
+ #
18
+ # @example
19
+ #
20
+ # # Executes global script foobar.term
21
+ # Consular::CLI.start ['start', 'foobar']
22
+ #
23
+ # # Executes global script foobar.yml
24
+ # Consular::CLI.start ['start', 'foobar.yml']
25
+ #
26
+ # # Executes the Termfile
27
+ # Consular::CLI.start ['start'] # ./Termfile
28
+ # Consular::CLI.start ['start', '-r=/tmp'] # /tmp/Termfile
29
+ #
30
+ # @api public
31
+ desc 'start [PROJECT]', 'runs the consular script. ex: `consular start` or `consular start foobar`'
32
+ method_option :root, :type => :string, :default => '.', :aliases => '-r'
33
+ def start(project = nil)
34
+ path = termfile_path(project)
35
+ message_unless_file(path) { valid_core.new(path).process! }
36
+ end
37
+
38
+ # Run the Termfile or project script setup.
39
+ #
40
+ # @param [String] project
41
+ # Name of the project script. Otherwise leave blank for Termfile.
42
+ #
43
+ # @example
44
+ #
45
+ # # Executes global script setup for foobar.term
46
+ # Consular::CLI.start ['setup', 'foobar']
47
+ #
48
+ # # Executes global script setup for foobar.yml
49
+ # Consular::CLI.start ['setup', 'foobar.yml']
50
+ #
51
+ # # Executes the Termfile setup
52
+ # Consular::CLI.start ['setup'] # ./Termfile
53
+ # Consular::CLI.start ['setup', '-r=/tmp'] # /tmp/Termfile
54
+ #
55
+ # @api public
56
+ desc 'setup [PROJECT]', 'run the consular script setup. ex: `consular setup` or `consular setup foobar`'
57
+ method_option :root, :type => :string, :default => '.', :aliases => '-r'
58
+ def setup(project = nil)
59
+ path = termfile_path(project)
60
+ message_unless_file(path) { valid_core.new(path).setup! }
61
+ end
62
+
63
+ # Lists all avaiable global scripts
64
+ #
65
+ # @example
66
+ #
67
+ # Consular::CLI.start ['list']
68
+ #
69
+ # @api public
70
+ desc 'list', 'lists all consular scripts'
71
+ def list
72
+ say "Global scripts available: \n"
73
+ Dir.glob("#{Consular.global_path}/*[^~]").each do |file|
74
+ name = File.basename(file, '.term')
75
+ title = file_comment(file)
76
+ say " * #{name} - #{title}"
77
+ end
78
+ end
79
+
80
+ # Create the global script directory for Consular.
81
+ #
82
+ # @example
83
+ #
84
+ # Consular::CLI.start ['init']
85
+ #
86
+ # @api public
87
+ desc 'init', 'create consular directory'
88
+ def init
89
+ empty_directory Consular.global_path
90
+ template 'templates/consularc.tt', File.join(ENV['HOME'],'.consularc'), :skip => true
91
+ end
92
+
93
+ # Edit the specified global script or Termfile.
94
+ #
95
+ # @param [String] project
96
+ # Name of project script.
97
+ #
98
+ # @example
99
+ #
100
+ # # opens foobar for editing
101
+ # Consular::CLI.start ['edit', 'foobar']
102
+ # # opens foobar with specified editor
103
+ # Consular::CLI.start ['edit', 'foobar', '-e=vim']
104
+ # # opens /tmp/Termfile
105
+ # Consular::CLI.start ['edit', '-r=/tmp']
106
+ #
107
+ # @api public
108
+ desc 'edit [PROJECT]', 'opens the Termfile to edit. ex: `consular edit` or `consular edit foobar`'
109
+ method_option :root, :type => :string, :default => '.', :aliases => '-r'
110
+ method_option :editor, :type => :string, :default => nil, :aliases => '-e'
111
+ method_option :capture, :type => :boolean, :default => false, :aliases => '-c'
112
+ def edit(project = nil)
113
+ type = project && project =~ /\.yml$/ ? 'yml' : 'term'
114
+ path = termfile_path project
115
+ template "templates/example.#{type}.tt", path, :skip => true
116
+ open_in_editor path, options[:editor]
117
+ end
118
+
119
+ # Delete the global script or Termfile
120
+ #
121
+ # @param [String] project
122
+ # Name of the project script.
123
+ #
124
+ # @example
125
+ #
126
+ # # deletes global script foobar.term
127
+ # Consular::CLI.start ['delete', 'foobar']
128
+ # # deletes global script foobar.yml
129
+ # Consular::CLI.start ['delete', 'foobaryml']
130
+ # # deletes /tmp/Termfile
131
+ # Consular::CLI.start ['delete', '-r=/tmp']
132
+ #
133
+ # @api public
134
+ desc 'delete [PROJECT]', 'delete the Termfile script. ex: `consular delete` or `consular delete foobar`'
135
+ method_option :root, :type => :string, :default => '.', :aliases => '-r'
136
+ def delete(project = nil)
137
+ path = termfile_path(project)
138
+ message_unless_file(path) { remove_file path }
139
+ end
140
+
141
+ no_tasks do
142
+
143
+ # Returns the first core that matchees the currrent system.
144
+ #
145
+ # @return [Core] Core that matches the system.
146
+ #
147
+ # @api private
148
+ def valid_core
149
+ Consular.cores.detect { |core| core.valid_system? }
150
+ end
151
+ # Returns the first comment in file. This is used
152
+ # as the title when listing out the scripts.
153
+ #
154
+ # @param [String] file
155
+ # path to file
156
+ #
157
+ # @api private
158
+ def file_comment(file)
159
+ first_line = File.readlines(file).first
160
+ first_line =~ /^\s*?#/ ? first_line.gsub('#','') : "\n"
161
+ end
162
+
163
+ # Returns the full pathname of the Termfile
164
+ #
165
+ # @param [String] project
166
+ # designated file/project name
167
+ #
168
+ # @return [String] full path name for Termfile.
169
+ #
170
+ # @example
171
+ # termfile_path #=> ROOT/Termfile
172
+ # termfile_path 'foo' #=> GLOBAL_PATH/foo.term
173
+ # termfile_path 'bar.yml' #=> GLOBAL_PATH/bar.yml
174
+ #
175
+ # @api private
176
+ def termfile_path(project = nil)
177
+ if !project || project.empty?
178
+ File.join(options[:root], 'Termfile')
179
+ else
180
+ path = project =~ /\..*/ ? project : project + '.term'
181
+ Consular.global_path path
182
+ end
183
+ end
184
+
185
+ # Opens Termfile in specified editor.
186
+ #
187
+ # @param [String] path
188
+ # Path to Termfile
189
+ # @param [String] editor
190
+ # Editor to open Termfile with.
191
+ #
192
+ # @example
193
+ # open_in_editor '/path/to/Termfile', 'vim'
194
+ #
195
+ # @api private
196
+ def open_in_editor(path, editor = nil)
197
+ editor = editor || Consular.default_editor || ENV['EDITOR']
198
+ system "#{editor || 'open'} #{path}"
199
+ end
200
+
201
+ # Returns an error message unless the file exists. If it does
202
+ # execute the block
203
+ #
204
+ # @param [String] file
205
+ # Path of file
206
+ # @param [Proc] blk
207
+ # Proc to execute if file exists.
208
+ #
209
+ # @api private
210
+ def message_unless_file(file, &blk)
211
+ if File.exists?(file)
212
+ blk.call
213
+ else
214
+ say "#{file} does not exist. Try running `consular edit` first.", :yellow
215
+ end
216
+ end
217
+ end
218
+
219
+ end
220
+ end
@@ -0,0 +1,65 @@
1
+ module Consular
2
+ # Defines the abstract definition of a core. This needs to be
3
+ # subclassed and have the appropriate methods defined so that
4
+ # the CLI runner knows how to execute the Termfile on
5
+ # each core. You will need to add the core to Consular like so:
6
+ #
7
+ # Consular.add_core self
8
+ #
9
+ class Core
10
+ attr_accessor :termfile
11
+
12
+ # Instantiated the hash from the Termfile into the
13
+ # core.
14
+ #
15
+ # @param [String] path
16
+ # Path to Termfile
17
+ #
18
+ # @api public
19
+ def initialize(path)
20
+ @termfile = Consular::DSL.new(path).to_hash
21
+ end
22
+
23
+
24
+ # Method called by runner to execute the Termfile setup
25
+ # on the core.
26
+ #
27
+ # @api public
28
+ def setup!
29
+ raise NotImplementedError, ".setup! needs to be defined for it to be ran by `terminitor setup`"
30
+ end
31
+
32
+ # Method called by the runner to execute the Termfile
33
+ # on the core.
34
+ #
35
+ # @api public
36
+ def process!
37
+ raise NotImplementedError, ".process! needs to be defined for it to be ran by `terminitor start`"
38
+ end
39
+
40
+ class << self
41
+ # Checks to see if the current system/terminal is the right
42
+ # one to use for this core. This is called by the CLI to check
43
+ # if a particular core should be used.
44
+ #
45
+ # @return [Boolean] Whether the current system is valid.
46
+ #
47
+ # @api public
48
+ def valid_system?
49
+ raise NotImplementedError, ".valid_system? needs to be defined for Consular to determine what system is belongs to."
50
+ end
51
+
52
+ # Captures the current terminal settings for the system. It will
53
+ # return a hash format like that of Consular::DSL so that Consular
54
+ # can write it back out into a Termfile.
55
+ #
56
+ # @return [Hash] Consular style hash.
57
+ #
58
+ # @api public
59
+ def capture!
60
+ raise NotImplementedError, "capture! is currently unavailable for this core."
61
+ end
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,220 @@
1
+ require 'active_support/ordered_hash'
2
+ require 'active_support/core_ext/array/extract_options'
3
+ require 'yaml'
4
+
5
+ module Consular
6
+
7
+ # The DSL class provides the DSL for the Consular scripting file.
8
+ # It provides the basic commands such as:
9
+ # #setup - commands to run when invoked by 'consular setup'
10
+ # #window - commands to run in the context of a window
11
+ # #tab - commands to run in the context of tab
12
+ # #before - commands to run in the context before every tab context
13
+ #
14
+ # The DSL class can be extended to provide additional API's for core specific
15
+ # DSL.
16
+ class DSL
17
+
18
+ attr_reader :_setup, :_windows, :_context
19
+
20
+ # Initializes the DSL library and stores the commands.
21
+ #
22
+ # @param [String] path
23
+ # path to Consular script/ Termfile
24
+ #
25
+ # @example
26
+ # Consular::DSL.new 'foo/bar.term'
27
+ #
28
+ # @api public
29
+ def initialize(path)
30
+ @_setup = []
31
+ @_windows = ActiveSupport::OrderedHash.new
32
+ @_windows['default'] = window_hash
33
+ @_context = @_windows['default']
34
+ file = File.read(path)
35
+ if path =~ /\.yml$/
36
+ @_file = YAML.load file
37
+ extend Yaml
38
+ else
39
+ instance_eval file, __FILE__, __LINE__
40
+ end
41
+ end
42
+
43
+ # Run commands using prior to the workflow using the command `consular setup`.
44
+ # This allows you to perform any command that needs to be ran prior to setup
45
+ # a particular project/script.
46
+ #
47
+ # @param [Array<String>] commands
48
+ # Commands to be executed.
49
+ # @param [Proc] block
50
+ # Proc of commands to run.
51
+ #
52
+ # @example
53
+ # setup 'bundle install', 'brew update'
54
+ # setup { run 'bundle install' }
55
+ #
56
+ # @api public
57
+ def setup(*commands, &block)
58
+ block_given? ? run_context(@_setup, &block) : @_setup.concat(commands)
59
+ end
60
+
61
+ # Run commands prior to each tab context.
62
+ #
63
+ # @param [Array<String>] commands
64
+ # Commands to be executed.
65
+ # @param [Proc] block
66
+ # Proc of commands to run
67
+ #
68
+ # @example
69
+ # # Executes `whoami` before tab with `ls` and `gitx`
70
+ # window do
71
+ # before { run 'whoami' }
72
+ # tab 'ls'
73
+ # tab 'gitx'
74
+ # end
75
+ #
76
+ # @api public
77
+ def before(*commands, &block)
78
+ context = (@_context[:before] ||= [])
79
+ block_given? ? run_context(context, &block) : context.concat(commands)
80
+ end
81
+
82
+ # Run commands in the conext of a window.
83
+ #
84
+ # @param [Array] args
85
+ # Hash to pass options to each context of a window. Each core can
86
+ # implement the desired behavior for the window based on the options set here.
87
+ # Can also pass a string as first parameter which will be set as
88
+ # the :name
89
+ # @param [Proc] block
90
+ # block of commands to run in window context.
91
+ #
92
+ # @example
93
+ # window 'my project', :size => [80, 30] do
94
+ # run 'ps aux'
95
+ # end
96
+ #
97
+ # @api public
98
+ def window(*args, &block)
99
+ key = "window#{@_windows.keys.size}"
100
+ options = args.extract_options!
101
+ options[:name] = args.first unless args.empty?
102
+ context = (@_windows[key] = window_hash.merge(:options => options))
103
+ run_context context, &block
104
+ end
105
+
106
+ # Run commands in the context of a tab.
107
+ #
108
+ # @param [Array] args
109
+ # Accepts either:
110
+ # - an array of string commands
111
+ # - a hash containing options for the tab.
112
+ # @param [Proc] block
113
+ #
114
+ # @example
115
+ # tab 'first tab', :settings => 'Grass' do
116
+ # run 'ps aux'
117
+ # end
118
+ #
119
+ # tab 'ls', 'gitx'
120
+ #
121
+ # @api public
122
+ def tab(*args, &block)
123
+ tabs = @_context[:tabs]
124
+ key = "tab#{tabs.keys.size}"
125
+ return (tabs[key] = { :commands => args }) unless block_given?
126
+
127
+ context = (tabs[key] = {:commands => []})
128
+ options = args.extract_options!
129
+ options[:name] = args.first unless args.empty?
130
+ context[:options] = options
131
+
132
+ run_context context, &block
133
+ @_context = @_windows[@_windows.keys.last] # Jump back out into the context of the last window.
134
+ end
135
+
136
+ # Store commands to run in context.
137
+ #
138
+ # @param [Array<String>] commands
139
+ # Array of commands to be executed.
140
+ #
141
+ # @example
142
+ # run 'brew update', 'gitx'
143
+ #
144
+ # @api public
145
+ def run(*commands)
146
+ context = case
147
+ when @_context.is_a?(Hash) && @_context[:tabs]
148
+ @_context[:tabs]['default'][:commands]
149
+ when @_context.is_a?(Hash)
150
+ @_context[:commands]
151
+ else
152
+ @_context
153
+ end
154
+ context << commands.map { |c| c =~ /&$/ ? "(#{c})" : c }.join(" && ")
155
+ end
156
+
157
+
158
+ # Returns yaml file as Consular formmatted hash
159
+ #
160
+ # @return [Hash] Return hash format of Termfile
161
+ #
162
+ # @api semipublic
163
+ def to_hash
164
+ { :setup => @_setup, :windows => @_windows }
165
+ end
166
+
167
+ private
168
+
169
+ # Execute the context
170
+ #
171
+ # @param [Hash] context
172
+ # hash of current context.
173
+ # @param [Proc] block
174
+ # the context's block to be executed
175
+ #
176
+ # @example
177
+ # run_context @_setup, &block
178
+ # run @tabs['name'], &block
179
+ #
180
+ # @api private
181
+ def run_context(context, &block)
182
+ @_context, @_old_context = context, @_context
183
+ instance_eval &block
184
+ @_context = @_old_context
185
+ end
186
+
187
+ def clean_up_context(context = last_open_window, old_context = nil)
188
+ @_context = context
189
+ @_old_context = old_context
190
+ end
191
+
192
+ def last_open_window
193
+ @_windows[@_windows.keys.last]
194
+ end
195
+
196
+ # Return the default hash format for windows
197
+ #
198
+ # @api private
199
+ def window_hash
200
+ {:tabs => {'default' =>{:commands=>[]}}}.dup
201
+ end
202
+
203
+ module Yaml
204
+ # Returns yaml file as formmatted hash
205
+ #
206
+ # @return [Hash] Hash format of Termfile
207
+ #
208
+ # @api public
209
+ def to_hash
210
+ @_file ||= {}
211
+ combined = @_file.inject({}) do |base, item|
212
+ item = {item.keys.first => {:commands => item.values.first, :options => {}}}
213
+ base.merge!(item)
214
+ base
215
+ end # merge the array of hashes.
216
+ { :setup => nil, :windows => { 'default' => { :tabs => combined } } }
217
+ end
218
+ end
219
+ end
220
+ end
@@ -0,0 +1,3 @@
1
+ module Consular
2
+ VERSION = "1.0.0.rc1"
3
+ end
data/lib/consular.rb ADDED
@@ -0,0 +1,68 @@
1
+ lib_dir = File.expand_path("..", __FILE__)
2
+ $:.unshift( lib_dir ) unless $:.include?( lib_dir )
3
+
4
+ require 'consular/version'
5
+ require 'consular/core'
6
+ require 'consular/dsl'
7
+ require 'consular/cli'
8
+
9
+ module Consular
10
+
11
+ class << self
12
+ attr_accessor :global_path, :default_editor
13
+
14
+ # Returns all avaialble cores.
15
+ #
16
+ # @return [Array<Core>] Consular cores.
17
+ #
18
+ # @api semipublic
19
+ def cores
20
+ @cores ||= []
21
+ end
22
+
23
+ # Add a core to Consular.
24
+ #
25
+ # @param [Core] klass
26
+ # Core to add.
27
+ #
28
+ # @example
29
+ # Consular.add_core Consular::Cores::OSX
30
+ #
31
+ # @api semipublic
32
+ def add_core(klass)
33
+ cores << klass
34
+ end
35
+
36
+ # Returns the global script path. If not set,
37
+ # defaults to ~/.config/consular
38
+ #
39
+ # @param [String] path
40
+ # File name in path
41
+ #
42
+ # @return [String] global script path
43
+ #
44
+ # @api public
45
+ def global_path(path = nil)
46
+ root = @global_path || File.join(ENV['HOME'],'.config','consular')
47
+ File.join root, (path || '')
48
+ end
49
+
50
+ # Configure Consular options.
51
+ #
52
+ # @param [Proc] block
53
+ # Configuration block
54
+ #
55
+ # @example
56
+ #
57
+ # Consular.configure do |c|
58
+ # c.global_path = '~/.consular'
59
+ # c.default_editor = 'vim'
60
+ # end
61
+ #
62
+ # @api public
63
+ def configure(&block)
64
+ yield self
65
+ end
66
+
67
+ end
68
+ end
@@ -0,0 +1,9 @@
1
+ # You can require your additional core gems here.
2
+ # require 'some_core_gem'
3
+
4
+ # You can set specific Consular configurations
5
+ # here.
6
+ Consular.configure do |c|
7
+ # c.global_path = '~/.config/consular'
8
+ # c.default_editor = 'vim'
9
+ end
@@ -0,0 +1,21 @@
1
+ # COMMENT OF SCRIPT HERE
2
+ # you can make as many tabs as you wish...
3
+ # tab names are actually arbitrary at this point too.
4
+
5
+ setup 'echo "setup"'
6
+
7
+ tab "echo 'default'", "echo 'default tab'"
8
+
9
+ window do
10
+
11
+ tab "echo 'first tab'", "echo 'of window'"
12
+
13
+ tab "named tab", :settings => "Ocean" do
14
+ run "echo 'named tab'"
15
+ run "ls"
16
+ end
17
+ end
18
+
19
+ window "autotest" do
20
+ tab "echo 'window and tab without options'"
21
+ end
@@ -0,0 +1,16 @@
1
+ # COMMENT OF SCRIPT HERE
2
+ # you can make as many tabs as you wish...
3
+ # tab names are actually arbitrary at this point too.
4
+ ---
5
+ - tab1:
6
+ - cd ~/foo/bar
7
+ - gitx
8
+ - tab2:
9
+ - mysql -u root
10
+ - use test;
11
+ - show tables;
12
+ - tab3: echo "hello world"
13
+ - tab4: cd ~/baz/ && git pull
14
+ - tab5:
15
+ - cd ~/foo/project
16
+ - autotest