consular 1.0.0.rc1

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