scide 0.0.5 → 0.0.6

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