scide 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.5
1
+ 0.0.6
data/lib/scide/command.rb CHANGED
@@ -1,69 +1,124 @@
1
1
  module Scide
2
2
 
3
+ # A command to be used in a GNU Screen window. There are several
4
+ # command implementations (show command, run command, tail file, etc).
5
+ # See under Scide::Commands.
3
6
  class Command
4
7
 
5
- def self.resolve contents, properties = {}, options = {}
8
+ # The options given to this command. These are built by merging
9
+ # global options, project options and window options.
10
+ attr_reader :options
6
11
 
12
+ # Returns a new command for the given window.
13
+ #
14
+ # ==== Arguments
15
+ # * <tt>window</tt> - The window in which the command will be used.
16
+ # Command options are retrieved from Scide::Window#options. See
17
+ # #initialize.
18
+ # * <tt>contents</tt> - The command configuration (String or Hash).
19
+ #
20
+ # ==== String Initialization
21
+ # The string must be in the format <tt>COMMAND [CONTENTS]</tt>.
22
+ #
23
+ # <tt>TYPE</tt> is the name of the command class under
24
+ # Scide::Commands, in uppercase camelcase. For example, <tt>TAIL</tt>
25
+ # corresponds to Scide::Commands::Tail, <tt>MY_COMMAND</tt> would
26
+ # correspond to Scide::Commands::MyCommand.
27
+ #
28
+ # <tt>CONTENTS</tt> is the contents of the command.
29
+ #
30
+ # ==== Hash Initialization
31
+ # The following options can be given:
32
+ # * <tt>:command => string</tt> is the same <tt>COMMAND</tt> as
33
+ # for string initialization above.
34
+ # * <tt>:contents => string or other</tt> is the same <tt>CONTENTS</tt>
35
+ # as for string initialization above. Typically this is only a
36
+ # string, but more advanced commands might be initialized with
37
+ # arrays or hashes.
38
+ def self.resolve window, contents
7
39
  if contents.kind_of? Hash
8
- options = options.merge contents[:options] if contents.key? :options
9
- properties = properties.merge contents[:properties] if contents.key? :properties
10
- end
11
-
12
- klass, contents = if contents.kind_of? Hash
13
- resolve_from_hash contents, properties, options
40
+ resolve_from_hash window, contents
14
41
  elsif contents.kind_of? String
15
- resolve_from_string contents, properties, options
42
+ resolve_from_string window, contents
43
+ else
44
+ raise ArgumentError, 'command must be a string or a hash'
16
45
  end
17
-
18
- klass.new contents, properties, build_options(options, klass)
19
46
  end
20
47
 
21
- def initialize contents, properties = {}, options = nil
22
- @text ||= contents
23
- @properties, @options = properties, options
48
+ # Returns a new command with the given options.
49
+ #
50
+ # ==== Arguments
51
+ # * <tt>contents</tt> - The contents of the command. Typically this
52
+ # is only a string, but more advanced commands might be initialized
53
+ # with arrays or hashes. By default, the contents can be retrieved
54
+ # as a string with #text_with_options.
55
+ # * <tt>options</tt> - Options that can be used in the string contents
56
+ # of the command. See #text_with_options.
57
+ def initialize contents, options = {}
58
+
59
+ # fill text only if it's not already there, in case a subclass does
60
+ # some initialization work before calling super
61
+ @text ||= contents.to_s
62
+
63
+ # merge given options to the already initialized ones, if any
64
+ @options = (@options || {}).merge options
24
65
  end
25
66
 
67
+ # Returns a representation of this command as a GNU Screen
68
+ # configuration fragment.
69
+ #
70
+ # This default implementation raises an error and must be
71
+ # overriden by subclasses.
26
72
  def to_screen
27
73
  raise 'Use a subclass'
28
74
  end
29
75
 
30
- def invalid_config err
31
- Scide.fail :invalid_config, "ERROR: #{self.class.name} configuration is invalid.\n #{err}"
76
+ # Returns the text of this command with filtered option placeholders.
77
+ #
78
+ # ==== Examples
79
+ # com_text = 'tail %{tail} -f file.txt -c %{foo}'
80
+ # com = Scide::Command.new com_text, :tail => '-n 1000', :foo => 400
81
+ #
82
+ # com.text_with_options #=> 'tail -n 1000 -f file.txt -c 400'
83
+ def text_with_options
84
+ @text.dup.tap do |s|
85
+ @options.each_pair do |key, value|
86
+ s.gsub! /\%\{#{Regexp.escape key}\}/, value.to_s
87
+ end
88
+ end
32
89
  end
33
90
 
34
91
  private
35
92
 
36
- def self.resolve_from_hash contents, properties = {}, options = {}
37
- klass = Scide::Commands.const_get contents[:command].downcase.capitalize
38
- [ klass, contents[:contents] ]
93
+ # Returns a new command for the given window. The given
94
+ # contents are a hash. See Scide::Command.resolve.
95
+ def self.resolve_from_hash window, contents
96
+ begin
97
+ klass = Scide::Commands.const_get contents[:command].downcase.camelize
98
+ klass.new contents[:contents], window.options.dup
99
+ rescue NameError => err
100
+ raise ArgumentError, "unknown '#{contents[:command]}' command type"
101
+ end
39
102
  end
40
103
 
41
- def self.resolve_from_string contents, properties = {}, options = {}
104
+ # Returns a new command for the given window. The given
105
+ # contents are a string. See Scide::Command.resolve.
106
+ def self.resolve_from_string window, contents
42
107
  klass_name, text = contents.split /\s+/, 2
43
- klass = Scide::Commands.const_get klass_name.downcase.capitalize
44
- [ klass, text ]
45
- end
46
-
47
- def self.build_options options, klass
48
- klass_name = klass.name.demodulize.downcase
49
- current_options = options.try(:[], klass_name)
50
- current_klass = klass
51
- while current_klass != Scide::Command and current_options.blank?
52
- current_klass = current_klass.superclass
53
- current_options = options.try(:[], current_klass.name.demodulize.downcase)
108
+ begin
109
+ klass = Scide::Commands.const_get klass_name.downcase.camelize
110
+ klass.new text, window.options.dup
111
+ rescue NameError => err
112
+ raise ArgumentError, "unknown '#{klass_name}' command type"
54
113
  end
55
- current_options
56
114
  end
115
+ end
57
116
 
58
- def text_with_properties
59
- @text.dup.tap do |s|
60
- @properties.each_pair do |key, value|
61
- s.gsub! /\%\{#{key}\}/, value
62
- end
63
- end
64
- end
117
+ # Module containing scide command classes.
118
+ module Commands
65
119
  end
66
120
  end
67
121
 
122
+ # load pre-defined commands
68
123
  deps_dir = File.join File.dirname(__FILE__), 'commands'
69
- %w( run tail show edit ).each{ |dep| require File.join(deps_dir, dep) }
124
+ %w( show run tail edit ).each{ |dep| require File.join(deps_dir, dep) }
@@ -2,11 +2,35 @@ module Scide
2
2
 
3
3
  module Commands
4
4
 
5
+ # Edits a file with the default editor (<tt>$EDITOR</tt>).
6
+ #
7
+ # ==== Configuration Example
8
+ # # this YAML configuration,
9
+ # projects:
10
+ # project1:
11
+ # options:
12
+ # edit: '-c MyVimCommand'
13
+ # windows:
14
+ # - "window1 EDIT $HOME/fubar.txt"
15
+ #
16
+ # # will produce the following command in window1:
17
+ # $EDITOR -c MyVimCommand $HOME/fubar.txt
5
18
  class Edit < Scide::Commands::Run
6
19
 
7
- def initialize contents, properties = {}, options = nil
8
- super contents, properties, options
9
- @text = [ '$EDITOR', options.to_s, @text.to_s ].select(&:present?).join(' ')
20
+ # Returns a new edit command.
21
+ #
22
+ # See class definition for examples.
23
+ #
24
+ # ==== Arguments
25
+ # * <tt>contents</tt> - The file to edit.
26
+ # * <tt>options</tt> - Options that can be used in the contents
27
+ # of the command.
28
+ #
29
+ # ==== Options
30
+ # * <tt>:edit => string</tt> - Arguments to the editor.
31
+ def initialize contents, options = {}
32
+ super contents, options
33
+ @text = [ '$EDITOR', options[:edit].to_s, @text.to_s ].select(&:present?).join(' ')
10
34
  end
11
35
  end
12
36
  end
@@ -2,10 +2,23 @@ module Scide
2
2
 
3
3
  module Commands
4
4
 
5
- class Run < Scide::Command
5
+ # Runs a command.
6
+ #
7
+ # ==== Configuration Example
8
+ # # this YAML configuration,
9
+ # projects:
10
+ # project1:
11
+ # windows:
12
+ # - "window1 RUN rails server"
13
+ #
14
+ # # will produce the following command in window1:
15
+ # rails server
16
+ class Run < Scide::Commands::Show
6
17
 
7
- def to_screen
8
- %|stuff "#{text_with_properties}\\012"\n|
18
+ # Appends a carriage return to the command so that
19
+ # it will not only be shown but also executed.
20
+ def text_with_options
21
+ "#{super}\\012"
9
22
  end
10
23
  end
11
24
  end
@@ -2,10 +2,31 @@ module Scide
2
2
 
3
3
  module Commands
4
4
 
5
+ # Prepares and shows a command but do not run it.
6
+ #
7
+ # ==== Configuration Example
8
+ # # this YAML configuration,
9
+ # projects:
10
+ # project1:
11
+ # options:
12
+ # host: 127.0.0.1
13
+ # windows:
14
+ # - "window1 SHOW ssh %{host}"
15
+ #
16
+ # # will produce the following command in window1:
17
+ # ssh 127.0.0.1
5
18
  class Show < Scide::Command
6
19
 
20
+ def initialize contents, options = {}
21
+ super contents, options
22
+ end
23
+
24
+ # Returns a configuration fragment that will put
25
+ # this command GNU \Screen window without running it.
26
+ # This will use screen's <tt>stuff</tt> command to
27
+ # put the text in the window.
7
28
  def to_screen
8
- %|stuff "#{text_with_properties}"\n|
29
+ %|stuff "#{text_with_options}"|
9
30
  end
10
31
  end
11
32
  end
@@ -2,11 +2,35 @@ module Scide
2
2
 
3
3
  module Commands
4
4
 
5
+ # Tails a file.
6
+ #
7
+ # ==== Configuration Example
8
+ # # this YAML configuration,
9
+ # projects:
10
+ # project1:
11
+ # options:
12
+ # tail: '-n 1000'
13
+ # windows:
14
+ # - "window1 TAIL $HOME/fubar.txt"
15
+ #
16
+ # # will produce the following command in window1:
17
+ # tail -n 1000 -f $HOME/fubar.txt
5
18
  class Tail < Scide::Commands::Run
6
19
 
7
- def initialize contents, properties = {}, options = nil
8
- super contents, properties, options
9
- @text = [ 'tail', options.to_s, '-f', @text.to_s ].select(&:present?).join(' ')
20
+ # Returns a new tail command.
21
+ #
22
+ # See class definition for examples.
23
+ #
24
+ # ==== Arguments
25
+ # * <tt>contents</tt> - The file to tail.
26
+ # * <tt>options</tt> - Options that can be used in the
27
+ # contents of the command.
28
+ #
29
+ # ==== Options
30
+ # * <tt>tail => string</tt> - Arguments to tail.
31
+ def initialize contents, options = {}
32
+ super contents, options
33
+ @text = [ 'tail', options[:tail].to_s, '-f', @text.to_s ].select(&:present?).join(' ')
10
34
  end
11
35
  end
12
36
  end
data/lib/scide/config.rb CHANGED
@@ -1,18 +1,47 @@
1
1
  require 'yaml'
2
2
 
3
- CONFIG_FILE = File.join File.expand_path('~'), '.scide', 'config.yml'
4
-
5
3
  module Scide
6
4
 
5
+ # Complete scide configuration as an object graph.
7
6
  class Config
7
+
8
+ # The file from which the configuration is normally loaded.
9
+ # This defaults to <tt>$HOME/.scide/config.yml</tt>.
10
+ DEFAULT_CONFIG_FILE = File.join File.expand_path('~'), '.scide', 'config.yml'
11
+
12
+ # The file from which this configuration will be loaded.
8
13
  attr_accessor :file
9
- attr_reader :global, :projects, :screen
14
+
15
+ # GNU Screen options. Accessible after calling #load!.
16
+ attr_reader :screen
10
17
 
18
+ # The global configuration. Accessible after calling #load!.
19
+ attr_reader :global
20
+
21
+ # The project definitions (windows, option overrides, etc). Accessible
22
+ # after calling #load!.
23
+ attr_reader :projects
24
+
25
+ # Returns an empty configuration.
26
+ #
27
+ # ==== Arguments
28
+ # * <tt>file</tt> - The file from which to load the configuration. If not
29
+ # given, this defaults to DEFAULT_CONFIG_FILE.
11
30
  def initialize file = nil
12
- @file = file.try(:to_s) || CONFIG_FILE
31
+ @file = file.try(:to_s) || DEFAULT_CONFIG_FILE
13
32
  end
14
33
 
15
- def load
34
+ # Loads this configuration. This will read from #file and parse the contents
35
+ # as YAML. Configuration elements can then be retrieved with #global,
36
+ # #projects and #screen.
37
+ #
38
+ # ==== Errors
39
+ # * <tt>config_not_found</tt> - #file does not exist.
40
+ # * <tt>config_not_readable</tt> - #file cannot be read by the user running scide.
41
+ # * <tt>malformed_config</tt> - #file contains malformed YAML.
42
+ # * <tt>invalid_config</tt> - #file contains invalid configuration (see README).
43
+ # * <tt>unexpected</tt> - #file could not be read.
44
+ def load!
16
45
 
17
46
  Scide.fail :config_not_found, "ERROR: expected to find configuration at #{@file}" unless File.exists? @file
18
47
  Scide.fail :config_not_readable, "ERROR: configuration #{@file} is not readable" unless File.readable? @file
@@ -34,26 +63,27 @@ module Scide
34
63
  # laziness
35
64
  @config = HashWithIndifferentAccess.new @config
36
65
 
66
+ invalid_config 'screen configuration must be a hash' unless @config[:screen].nil? or @config[:screen].kind_of?(Hash)
67
+ invalid_config 'projects configuration must be a hash' unless @config[:projects].nil? or @config[:projects].kind_of?(Hash)
68
+
37
69
  begin
38
- validate
39
- rescue StandardError => err
70
+ @screen = @config[:screen]
71
+ @global = Scide::Global.new @config[:global]
72
+ @projects = @config[:projects].inject(HashWithIndifferentAccess.new) do |memo,obj|
73
+ memo[obj[0]] = Scide::Project.new @global, obj[0], obj[1]; memo
74
+ end
75
+ rescue ArgumentError => err
40
76
  invalid_config err
41
77
  end
42
-
43
- @global = Scide::Global.new @config[:global]
44
- @screen = @config[:screen]
45
- @projects = @config[:projects].inject(HashWithIndifferentAccess.new) do |memo,obj|
46
- memo[obj[0].to_sym] = Scide::Project.new obj[1], obj[0], @global; memo
47
- end
48
78
  end
49
79
 
50
- def validate
51
- raise 'global configuration must be a hash' if @config[:global] and !@config[:global].kind_of?(Hash)
52
- raise 'configuration must contain a hash of projects' unless @config[:projects].kind_of? Hash
53
- end
80
+ private
54
81
 
55
- def invalid_config err
56
- Scide.fail :invalid_config, "ERROR: configuration #{@file} is invalid.\n #{err}"
82
+ # Causes scide to fail with an <tt>invalid_config</tt> error (see Scide#fail).
83
+ # Builds a complete error message containing the full path to the
84
+ # configuration file and the given message.
85
+ def invalid_config msg
86
+ Scide.fail :invalid_config, "ERROR: configuration #{@file} is invalid.\n #{msg}"
57
87
  end
58
88
  end
59
89
  end
data/lib/scide/global.rb CHANGED
@@ -1,13 +1,28 @@
1
1
  module Scide
2
2
 
3
+ # Global scide options (base path for all projects,
4
+ # shared options).
3
5
  class Global
4
- attr_accessor :path, :properties, :options
6
+
7
+ # The path under which all projects reside by default.
8
+ # (Can be overriden at the project level.)
9
+ attr_reader :path
10
+
11
+ # Global options shared by all projects.
12
+ attr_reader :options
5
13
 
14
+ # Builds global options.
15
+ #
16
+ # ==== Arguments
17
+ # * <tt>contents</tt> - The global options hash.
6
18
  def initialize contents
7
- @properties = contents[:properties]
8
- @options = contents[:options]
19
+ raise ArgumentError, 'global configuration must be a hash' unless contents.kind_of? Hash
20
+
21
+ @options = contents[:options] || {}
9
22
 
23
+ # default to home directory
10
24
  @path = contents[:path].try(:to_s) || File.expand_path('~')
25
+ # expand from home directory unless absolute
11
26
  @path = File.join File.expand_path('~'), @path unless @path.match /^\//
12
27
  end
13
28
  end
data/lib/scide/opts.rb CHANGED
@@ -1,7 +1,10 @@
1
1
  module Scide
2
2
 
3
+ # Pre-configured scide option parser.
3
4
  class Opts < Upoj::Opts
4
5
 
6
+ # Returns the scide option parser. Run scide with <tt>--usage</tt>
7
+ # to see available options.
5
8
  def initialize
6
9
  super({
7
10
  :banner => {
@@ -17,6 +20,10 @@ module Scide
17
20
  help!.usage!
18
21
  end
19
22
 
23
+ # Parses the given arguments.
24
+ #
25
+ # Causes scide to fail with an <tt>invalid_argument</tt> error (see Scide#fail)
26
+ # if an argument is invalid.
20
27
  def parse! args
21
28
  begin
22
29
  super args
@@ -1,27 +1,51 @@
1
1
  module Scide
2
2
 
3
+ # Utility class to run scide in a script.
3
4
  class Overmind
4
5
 
6
+ # Awakens the overmind.
5
7
  def initialize
6
8
  @cli = Scide::Opts.new
7
9
  @config = Scide::Config.new
8
10
  end
9
11
 
12
+ # Parses command-line arguments and loads the configuration file.
13
+ # Any error will be run through Scide.fail.
10
14
  def brood
11
15
  @cli.parse! ARGV
12
16
  @config.file = @cli.funnel[:config] if @cli.funnel.key? :config
13
- @config.load
17
+ @config.load!
14
18
  @initialized = true
15
19
  self
16
20
  end
17
21
 
22
+ # Runs GNU \Screen with the project given as argument.
23
+ # The <tt>--dry-run</tt> option will cause scide to print the
24
+ # resulting configuration instead of running it.
25
+ #
26
+ # ==== Errors
27
+ # * <tt>not_initialized</tt> - If #brood was not called.
28
+ # * <tt>unknown_project</tt> - If the given project is not found
29
+ # in the configuration file.
30
+ # * <tt>screen_not_found</tt> - If the GNU \Screen binary is not
31
+ # found with <tt>which</tt>.
18
32
  def dominate
19
33
 
20
- Scide.fail :not_initialized, 'ERROR: call #brood to initialize' unless @initialized
34
+ Scide.fail :not_initialized, 'ERROR: call #brood to initialize.' unless @initialized
21
35
 
22
36
  project_key = ARGV.shift
23
- screen = Scide::Screen.new @config, @cli, project_key
24
- screen.validate
37
+
38
+ if project_key.blank?
39
+ available_projects = @config.projects.keys.join(', ')
40
+ Scide.fail :invalid_argument, "You must choose a project. Available projects: #{available_projects}."
41
+ end
42
+
43
+ unless @config.projects.key? project_key
44
+ Scide.fail :unknown_project, "ERROR: there is no project '#{project_key}' in configuration #{@config.file}."
45
+ end
46
+
47
+ screen = Scide::Screen.new @config.projects[project_key], @config.screen
48
+ screen.check_binary
25
49
 
26
50
  if @cli.funnel[:'dry-run']
27
51
  puts
data/lib/scide/project.rb CHANGED
@@ -1,29 +1,64 @@
1
1
  module Scide
2
2
 
3
+ # Scide configuration for one project.
3
4
  class Project
4
- attr_accessor :properties, :options, :path
5
5
 
6
- def initialize contents, key, global
7
- @global = global
6
+ # The project key in the projects configuration hash.
7
+ attr_reader :key
8
8
 
9
+ # The path where the project is located. See #initialize.
10
+ attr_reader :path
11
+
12
+ # Project-specific options. Can be used by commands. See #initialize.
13
+ attr_reader :options
14
+
15
+ # Returns a project configuration.
16
+ #
17
+ # If not given in the project hash, #path is built by joining
18
+ # the global path and <tt>key</tt>.
19
+ #
20
+ # ==== Arguments
21
+ # * <tt>global</tt> - The global configuration.
22
+ # * <tt>key</tt> - The key identifying the project. This is the
23
+ # key in the projects hash.
24
+ # * <tt>contents</tt> - The project hash.
25
+ #
26
+ # ==== Project Options
27
+ #
28
+ # #options is built by merging the options given in the project
29
+ # hash with the global options.
30
+ #
31
+ # The following default options are added if not given:
32
+ # * <tt>name</tt> - Defaults to the project key.
33
+ # * <tt>path</tt> - The path where the project is located.
34
+ def initialize global, key, contents
35
+ raise ArgumentError, "project '#{key}' must be a hash" unless contents.kind_of? Hash
36
+ raise ArgumentError, "windows of project '#{key}' must be an array" unless contents[:windows].nil? or contents[:windows].kind_of?(Array)
37
+ raise ArgumentError, "options of project '#{key}' must be a hash" unless contents[:options].nil? or contents[:options].kind_of?(Hash)
38
+
39
+ @global, @key = global, key
40
+
41
+ # path defaults to project key
9
42
  @path = contents[:path].try(:to_s) || key.to_s
43
+ # expand from home directory if not absolute
10
44
  @path = File.join global.path, @path unless @path.match /^\//
11
45
 
12
- @properties = global.properties.merge(contents[:properties] || {})
13
- @properties['name'] ||= key
14
-
15
- @options = global.options.merge(contents[:options] || {})
46
+ @options = global.options.dup.merge(contents[:options] || {})
47
+ @options[:name] ||= key
48
+ @options[:path] ||= @path
16
49
 
17
- @windows = []
18
- contents[:windows].each do |w|
19
- @windows << Scide::Window.new(w, self)
20
- end
50
+ @windows = contents[:windows].collect{ |w| Scide::Window.new self, w }
21
51
  end
22
52
 
53
+ # Returns a representation of this project as a GNU Screen
54
+ # configuration fragment. Returns nil if this project has
55
+ # no configured windows.
23
56
  def to_screen
57
+ return nil if @windows.blank?
24
58
  String.new.tap do |s|
25
59
  @windows.each_with_index do |w,i|
26
60
  s << w.to_screen(i)
61
+ s << "\n" if i != @windows.length - 1
27
62
  end
28
63
  end
29
64
  end
data/lib/scide/screen.rb CHANGED
@@ -1,57 +1,91 @@
1
1
  require 'tempfile'
2
2
 
3
- DEFAULT_HARDSTATUS = '%{= kG}[ %{G}%H %{g}][%= %{=kw}%?%-Lw%?%{r}(%{W}%n*%f%t%?(%u)%?%{r})%{w}%?%+Lw%?%?%= %{g}][%{B}%Y-%m-%d %{W}%c %{g}]'
4
-
5
3
  module Scide
6
4
 
5
+ # Configuration of a GNU Screen session (windows for a specific project).
6
+ #
7
+ # The configuration will disable the startup message and display a hardstatus line.
8
+ # It will also display the windows of the given project.
7
9
  class Screen
10
+
11
+ # The default screen hardstatus line.
12
+ DEFAULT_HARDSTATUS = '%{= kG}[ %{G}%H %{g}][%= %{=kw}%?%-Lw%?%{r}(%{W}%n*%f%t%?(%u)%?%{r})%{w}%?%+Lw%?%?%= %{g}][%{B}%Y-%m-%d %{W}%c %{g}]'
8
13
 
9
- def initialize config, cli, project_key
10
- @config, @cli = config, cli
14
+ # Returns a screen configuration for the given project.
15
+ #
16
+ # ==== Arguments
17
+ # * <tt>project</tt> - The project.
18
+ # * <tt>options</tt> - Screen-specific options (see below).
19
+ #
20
+ # ==== Options
21
+ # * <tt>binary</tt> - Screen binary (defaults to <tt>screen</tt>).
22
+ # * <tt>args</tt> - Command-line arguments that will be given to screen (e.g. <tt>-U</tt> for unicode).
23
+ # * <tt>hardstatus</tt> - Hardstatus line configuration (defaults to #DEFAULT_HARDSTATUS).
24
+ def initialize project, options
25
+ raise ArgumentError, 'screen configuration must be a hash' unless options.nil? or options.kind_of?(Hash)
11
26
 
12
- unless @config.projects.key? project_key
13
- Scide.fail :unknown_project, "ERROR: there is no project '#{project_key}' in configuration #{@config.file}."
14
- end
15
- @project = config.projects[project_key]
27
+ @project = project
28
+ @options = options || {}
16
29
  end
17
30
 
31
+ # Runs screen with this configuration.
32
+ #
33
+ # The configuration is saved to a temporary file, then removed.
18
34
  def run
19
35
  file = Tempfile.new 'scide'
20
- save file
36
+ save file.path
21
37
  system to_command(file.path)
22
38
  file.unlink
23
39
  end
24
40
 
41
+ # Returns the command that will be used to run screen with this configuration.
42
+ #
43
+ # ==== Arguments
44
+ # * <tt>tmp_file</tt> - The temporary file in which the configuration will be stored.
45
+ # (Optional for dry-run.)
25
46
  def to_command tmp_file = 'TEMPORARY_FILE'
26
- "cd #{@project.path} && #{binary} #{options} -c #{tmp_file}"
47
+ "cd #{@project.path} && #{binary} #{args} -c #{tmp_file}"
27
48
  end
28
49
 
29
- def validate
50
+ # Verifies that the screen binary is there. If not, causes scide
51
+ # to fail with a <tt>screen_not_found</tt> error (see Scide#fail}.
52
+ def check_binary
30
53
  Scide.fail :screen_not_found, "ERROR: #{binary} not found" unless system("which #{binary}", { [ :out, :err ] => :close })
31
54
  end
32
55
 
56
+ # Returns a representation of this configuration as a string.
33
57
  def to_s
34
58
  String.new.tap do |s|
35
59
  s << "startup_message off\n"
36
60
  s << "hardstatus on\n"
37
61
  s << "hardstatus alwayslastline\n"
38
- s << "hardstatus string '#{DEFAULT_HARDSTATUS}'\n\n"
62
+ s << "hardstatus string '#{hardstatus}'\n\n"
39
63
  s << @project.to_screen
40
64
  end
41
65
  end
42
66
 
43
67
  private
44
68
 
69
+ # Returns the screen hardstatus line given as option, or
70
+ # the default #DEFAULT_HARDSTATUS.
71
+ def hardstatus
72
+ @options[:hardstatus] || DEFAULT_HARDSTATUS
73
+ end
74
+
75
+ # Returns the screen binary given as option, or the
76
+ # default (<tt>screen</tt>).
45
77
  def binary
46
- @config.screen.try(:[], :binary) || 'screen'
78
+ @options[:binary] || 'screen'
47
79
  end
48
80
 
49
- def options
50
- @config.screen.try(:[], :options)
81
+ # Returns the screen command-line arguments given as options.
82
+ def args
83
+ @options[:args]
51
84
  end
52
85
 
86
+ # Saves this configuration to the file at the given path.
53
87
  def save file
54
- File.open(file.path, 'w'){ |f| f.write to_s }
88
+ File.open(file, 'w'){ |f| f.write to_s }
55
89
  end
56
90
  end
57
91
  end
data/lib/scide/window.rb CHANGED
@@ -1,35 +1,80 @@
1
1
  module Scide
2
2
 
3
+ # Configuration of a GNU Screen window (name, command).
3
4
  class Window
5
+
6
+ # Window-specific options. Can be used by commands. See #initialize.
7
+ attr_reader :options
4
8
 
5
- def initialize contents, project
9
+ # Returns a window for the given project.
10
+ #
11
+ # ==== Arguments
12
+ # * <tt>project</tt> - The project owning this window.
13
+ # * <tt>contents</tt> - The window configuration (String or Hash).
14
+ #
15
+ # ==== String Initialization
16
+ # The string must be in the format <tt>NAME [COMMAND]</tt> where
17
+ # <tt>NAME</tt> is the window name and <tt>COMMAND</tt> (optional)
18
+ # is the command configuration (see Scide::Command).
19
+ #
20
+ # ==== Hash Initialization
21
+ # The following options can be given:
22
+ # * <tt>:name => string</tt> - The window name.
23
+ # * <tt>:options => hash</tt> - Window-specific options (will be
24
+ # merged to the project options).
25
+ # * <tt>:command => string</tt> - The command to use for this window
26
+ # (will be built using Scide::Command#resolve).
27
+ # * <tt>:string => string</tt> - If given, <tt>:name</tt> and <tt>:command</tt>
28
+ # are ignored and string initialization will be performed with <tt>string</tt>.
29
+ # <tt>:options</tt> can still be used to override project options.
30
+ def initialize project, contents
6
31
  @project = project
32
+
7
33
  if contents.kind_of? Hash
8
- init_from_hash contents
34
+ init_from_hash! contents
9
35
  elsif contents.kind_of? String
10
- init_from_string contents
36
+ init_from_string! contents
37
+ else
38
+ raise ArgumentError, "window '#{contents}' must be a string or a hash"
11
39
  end
12
40
  end
13
41
 
42
+ # Returns a representation of this window as a GNU Screen
43
+ # configuration frament.
44
+ #
45
+ # ==== Arguments
46
+ # * <tt>index</tt> - The position of the window (zero-based).
14
47
  def to_screen index
15
48
  String.new.tap do |s|
16
- s << "screen -t #{@name} #{index}\n"
17
- s << @command.to_screen if @command
49
+ s << "screen -t #{@name} #{index}"
50
+ s << "\n#{@command.to_screen}" if @command
18
51
  end
19
52
  end
20
53
 
21
54
  private
22
55
 
23
- def init_from_hash contents
24
- @name = contents.delete :name
25
- @command = Command.resolve contents, @project.properties, @project.options if contents.key? :command
56
+ # Initializes this window from a hash. See #initialize for options.
57
+ def init_from_hash! contents
58
+ raise ArgumentError, "options of window '#{@name}' must be a hash" unless contents[:options].nil? or contents[:options].kind_of?(Hash)
59
+ @options = @project.options.dup.merge(contents[:options] || {})
60
+
61
+ if contents[:string].present?
62
+ init_from_string! contents[:string]
63
+ else
64
+ raise ArgumentError, "window '#{contents}' must have a name" unless contents[:name].present?
65
+ @name = contents[:name]
66
+ @command = Command.resolve self, contents if contents.key? :command
67
+ end
26
68
  end
27
69
 
28
- def init_from_string contents
70
+ # Initializes this window from a string. See #initialize for format.
71
+ def init_from_string! contents
72
+ raise ArgumentError, "window '#{contents}' must not be an empty string" unless contents.present?
29
73
  content_parts = contents.split /\s+/, 2
30
74
  @name = content_parts[0]
75
+ @options ||= @project.options.dup
31
76
  if content_parts.length == 2
32
- @command = Command.resolve content_parts[1], @project.properties, @project.options
77
+ @command = Command.resolve self, content_parts[1]
33
78
  end
34
79
  end
35
80
  end
data/lib/scide.rb CHANGED
@@ -1,26 +1,74 @@
1
1
  require 'paint'
2
2
  require 'upoj-rb'
3
3
 
4
+ # Generator of GNU Screen configuration files.
4
5
  module Scide
6
+
7
+ # Current version.
5
8
  VERSION = File.open(File.join(File.dirname(__FILE__), '..', 'VERSION'), 'r').read
9
+
10
+ # Exit status codes.
11
+ #
12
+ # ==== Codes
13
+ # * <tt>unexpected</tt> - 1.
14
+ # * <tt>invalid_argument</tt> - 2.
15
+ # * <tt>not_initialized</tt> - 3.
16
+ # * <tt>screen_not_found</tt> - 4.
17
+ # * <tt>config_not_found</tt> - 10.
18
+ # * <tt>config_not_readable</tt> - 11.
19
+ # * <tt>malformed_config</tt> - 12.
20
+ # * <tt>invalid_config</tt> - 13.
21
+ # * <tt>unknown_project</tt> - 14.
6
22
  EXIT = {
7
23
  :unexpected => 1,
8
24
  :invalid_argument => 2,
9
25
  :not_initialized => 3,
10
26
  :screen_not_found => 4,
11
- :config_not_found => 13,
12
- :config_not_readable => 14,
13
- :malformed_config => 15,
14
- :invalid_config => 16,
15
- :unknown_project => 17
27
+ :config_not_found => 10,
28
+ :config_not_readable => 11,
29
+ :malformed_config => 12,
30
+ :invalid_config => 13,
31
+ :unknown_project => 14
16
32
  }
17
33
 
34
+ # Prints a message on <tt>stderr</tt> and exits.
35
+ # If #condition is a key from #EXIT, the corresponding value
36
+ # will be used as the exit code. Otherwise, scide exits with
37
+ # status 1.
18
38
  def self.fail condition, msg
19
- puts
20
- warn Paint[msg, :yellow]
21
- puts
22
- EXIT.key?(condition) ? exit(EXIT[condition]) : exit(1)
39
+ if @@exit_on_fail
40
+ puts
41
+ warn Paint[msg, :yellow]
42
+ puts
43
+ EXIT.key?(condition) ? exit(EXIT[condition]) : exit(1)
44
+ else
45
+ raise Scide::Error.new condition, msg
46
+ end
23
47
  end
48
+
49
+ # By default, scide is meant to be used as a standalone script
50
+ # and exits if an error occurs. If <tt>exit_on_fail</tt> is
51
+ # false, a Scide::Error will be raised instead. Scide can then
52
+ # be used by another script.
53
+ def self.exit_on_fail= exit_on_fail
54
+ @@exit_on_fail = exit_on_fail
55
+ end
56
+
57
+ # Scide error. Can be raised if #exit_on_fail is set to false.
58
+ class Error < StandardError
59
+ attr_reader :condition
60
+
61
+ # Returns a new error.
62
+ def initialize condition, msg
63
+ super msg
64
+ @condition = condition
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ @@exit_on_fail = true
24
71
  end
25
72
 
73
+ # load scide components
26
74
  %w( command config global opts overmind project screen window ).each{ |dep| require File.join(File.dirname(__FILE__), 'scide', dep) }
data/scide.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "scide"
8
- s.version = "0.0.5"
8
+ s.version = "0.0.6"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["AlphaHydrae"]
12
- s.date = "2011-10-01"
12
+ s.date = "2011-10-02"
13
13
  s.description = "Utility to generate GNU screen configuration files."
14
14
  s.email = "hydrae.alpha@gmail.com"
15
15
  s.executables = ["scide"]
@@ -42,6 +42,11 @@ Gem::Specification.new do |s|
42
42
  "lib/scide/screen.rb",
43
43
  "lib/scide/window.rb",
44
44
  "scide.gemspec",
45
+ "spec/command_spec.rb",
46
+ "spec/commands/edit_spec.rb",
47
+ "spec/commands/run_spec.rb",
48
+ "spec/commands/show_spec.rb",
49
+ "spec/commands/tail_spec.rb",
45
50
  "spec/helper.rb"
46
51
  ]
47
52
  s.homepage = "http://github.com/AlphaHydrae/scide"
@@ -0,0 +1,87 @@
1
+ require 'helper'
2
+
3
+ module Scide
4
+ module Commands
5
+ class FuBar < Scide::Commands::Show
6
+ end
7
+ end
8
+ end
9
+
10
+ describe Scide::Command do
11
+
12
+ it "should be abstract" do
13
+ com = Scide::Command.new 'fubar'
14
+ lambda{ com.to_screen }.should raise_error(StandardError)
15
+ end
16
+
17
+ it "should use given contents as text" do
18
+ com = Scide::Command.new 'fubar'
19
+ com.text_with_options.should == 'fubar'
20
+ end
21
+
22
+ it "should take options" do
23
+ com = Scide::Command.new 'fubar %{foo} %{bar}', :foo => 1, :bar => 2
24
+ com.text_with_options.should == 'fubar 1 2'
25
+ end
26
+
27
+ describe 'Class' do
28
+ before :each do
29
+ @options = { :a => 1, :b => true }
30
+ @window = double('window')
31
+ @window.stub(:options){ @options }
32
+ end
33
+
34
+ it "should resolve command with a string" do
35
+ com = Scide::Command.resolve @window, 'SHOW fubar'
36
+ com.should be_a_kind_of(Scide::Commands::Show)
37
+ end
38
+
39
+ it "should use second part of string as contents when resolving a command with a string" do
40
+ com = Scide::Command.resolve @window, 'SHOW fubar'
41
+ com.text_with_options.should == 'fubar'
42
+ end
43
+
44
+ it "should resolve command with a hash" do
45
+ com = Scide::Command.resolve @window, :command => 'SHOW', :contents => 'fubar'
46
+ com.should be_a_kind_of(Scide::Commands::Show)
47
+ end
48
+
49
+ it "should give duplicated window options to the resolved string command" do
50
+ @window.should_receive :options
51
+ com = Scide::Command.resolve @window, 'SHOW fubar'
52
+ com.options.should == @options
53
+ com.options.should_not equal(@options)
54
+ end
55
+
56
+ it "should give duplicated window options to the resolved hash command" do
57
+ @window.should_receive :options
58
+ com = Scide::Command.resolve @window, :command => 'SHOW', :contents => 'fubar'
59
+ com.options.should == @options
60
+ com.options.should_not equal(@options)
61
+ end
62
+
63
+ it "should resolve camel-case command class names with a string" do
64
+ puts Scide::Commands::FuBar
65
+ com = Scide::Command.resolve @window, 'FU_BAR'
66
+ com.should be_a_kind_of(Scide::Commands::FuBar)
67
+ end
68
+
69
+ it "should resolve camel-case command class names with a hash" do
70
+ com = Scide::Command.resolve @window, :command => 'FU_BAR'
71
+ com.should be_a_kind_of(Scide::Commands::FuBar)
72
+ end
73
+
74
+ it "should raise an error when type of contents is unknown" do
75
+ lambda{ Scide::Command.resolve(@window, []) }.should raise_error(ArgumentError)
76
+ end
77
+
78
+ it "should raise an error when trying to resolve a blank string" do
79
+ lambda{ Scide::Command.resolve(@window, ' ') }.should raise_error(ArgumentError)
80
+ end
81
+
82
+ it "should raise an error when trying to resolve an unknown command" do
83
+ lambda{ Scide::Command.resolve(@window, 'SHW fubar') }.should raise_error(ArgumentError)
84
+ lambda{ Scide::Command.resolve(@window, :command => 'SHW', :contents => 'fubar') }.should raise_error(ArgumentError)
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,19 @@
1
+ require 'helper'
2
+
3
+ describe Scide::Commands::Edit do
4
+
5
+ it "should use the preferred editor" do
6
+ com = Scide::Commands::Edit.new nil
7
+ com.text_with_options.should == '$EDITOR\012'
8
+ end
9
+
10
+ it "should use given contents as arguments to the editor" do
11
+ com = Scide::Commands::Edit.new 'fubar'
12
+ com.text_with_options.should == '$EDITOR fubar\012'
13
+ end
14
+
15
+ it "should use the :edit option as arguments to the editor" do
16
+ com = Scide::Commands::Edit.new 'fubar', :edit => '-c MyCommand'
17
+ com.text_with_options.should == '$EDITOR -c MyCommand fubar\012'
18
+ end
19
+ end
@@ -0,0 +1,9 @@
1
+ require 'helper'
2
+
3
+ describe Scide::Commands::Run do
4
+
5
+ it "should use given contents and a carriage return as text" do
6
+ com = Scide::Commands::Run.new 'fubar'
7
+ com.text_with_options.should == 'fubar\012'
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ require 'helper'
2
+
3
+ describe Scide::Commands::Show do
4
+
5
+ it "should produce a GNU Screen stuff command" do
6
+ com = Scide::Commands::Show.new 'fubar'
7
+ com.to_screen.should == 'stuff "fubar"'
8
+ end
9
+
10
+ it "should use #text_with_options as argument to stuff" do
11
+ com = Scide::Commands::Show.new 'fubar'
12
+ text_with_options = com.text_with_options
13
+ com.to_screen.should == %|stuff "#{text_with_options}"|
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ require 'helper'
2
+
3
+ describe Scide::Commands::Tail do
4
+
5
+ it "should use given contents as argument -f of tail" do
6
+ com = Scide::Commands::Tail.new 'fubar'
7
+ com.text_with_options.should == 'tail -f fubar\012'
8
+ end
9
+
10
+ it "should use the :tail option as arguments to tail" do
11
+ com = Scide::Commands::Tail.new 'fubar', :tail => '-n 1000'
12
+ com.text_with_options.should == 'tail -n 1000 -f fubar\012'
13
+ end
14
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scide
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-10-01 00:00:00.000000000Z
12
+ date: 2011-10-02 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: upoj-rb
16
- requirement: &2157494940 !ruby/object:Gem::Requirement
16
+ requirement: &2156558600 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 0.0.4
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *2157494940
24
+ version_requirements: *2156558600
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &2157494400 !ruby/object:Gem::Requirement
27
+ requirement: &2156558080 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *2157494400
35
+ version_requirements: *2156558080
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: shoulda
38
- requirement: &2157493840 !ruby/object:Gem::Requirement
38
+ requirement: &2156557480 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *2157493840
46
+ version_requirements: *2156557480
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: bundler
49
- requirement: &2157493320 !ruby/object:Gem::Requirement
49
+ requirement: &2156556880 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ~>
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: 1.0.0
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *2157493320
57
+ version_requirements: *2156556880
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: jeweler
60
- requirement: &2157486420 !ruby/object:Gem::Requirement
60
+ requirement: &2156556280 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ~>
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: 1.6.4
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *2157486420
68
+ version_requirements: *2156556280
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rcov
71
- requirement: &2157485920 !ruby/object:Gem::Requirement
71
+ requirement: &2156555680 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ! '>='
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: '0'
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *2157485920
79
+ version_requirements: *2156555680
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: rdoc
82
- requirement: &2157485140 !ruby/object:Gem::Requirement
82
+ requirement: &2156555080 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - ! '>='
@@ -87,7 +87,7 @@ dependencies:
87
87
  version: '0'
88
88
  type: :development
89
89
  prerelease: false
90
- version_requirements: *2157485140
90
+ version_requirements: *2156555080
91
91
  description: Utility to generate GNU screen configuration files.
92
92
  email: hydrae.alpha@gmail.com
93
93
  executables:
@@ -121,6 +121,11 @@ files:
121
121
  - lib/scide/screen.rb
122
122
  - lib/scide/window.rb
123
123
  - scide.gemspec
124
+ - spec/command_spec.rb
125
+ - spec/commands/edit_spec.rb
126
+ - spec/commands/run_spec.rb
127
+ - spec/commands/show_spec.rb
128
+ - spec/commands/tail_spec.rb
124
129
  - spec/helper.rb
125
130
  homepage: http://github.com/AlphaHydrae/scide
126
131
  licenses:
@@ -137,7 +142,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
137
142
  version: '0'
138
143
  segments:
139
144
  - 0
140
- hash: 4252834099400613658
145
+ hash: 1145488263121204374
141
146
  required_rubygems_version: !ruby/object:Gem::Requirement
142
147
  none: false
143
148
  requirements: