dotopts 0.1.3 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.index +2 -2
- data/HISTORY.md +17 -0
- data/README.md +38 -29
- data/demo/applique/battery.rb +8 -3
- data/lib/dotopts/api.rb +60 -24
- data/lib/dotopts/command.rb +149 -0
- data/lib/dotopts/parser.rb +107 -45
- data/spec/helper.rb +13 -0
- data/spec/spec_api.rb +14 -2
- data/spec/spec_command.rb +0 -0
- data/spec/spec_parser.rb +50 -24
- metadata +11 -9
- data/Gemfile.lock +0 -30
data/.index
CHANGED
@@ -49,7 +49,7 @@ paths:
|
|
49
49
|
created: '2013-01-23'
|
50
50
|
summary: Automatic Arguments for Ruby
|
51
51
|
title: DotOpts
|
52
|
-
version: 0.
|
52
|
+
version: 0.2.0
|
53
53
|
name: dotopts
|
54
54
|
description: DotOpts is an automatic commandline argument augmenter for Ruby tools.
|
55
|
-
date: '2013-
|
55
|
+
date: '2013-02-16'
|
data/HISTORY.md
CHANGED
@@ -1,5 +1,22 @@
|
|
1
1
|
# RELEASE HISTORY
|
2
2
|
|
3
|
+
## 0.2.0 / 2013-02-16
|
4
|
+
|
5
|
+
Rethought overall design and reimplemented parser. The syntax of option
|
6
|
+
files is essentially the same, but now arguments are only applied if
|
7
|
+
there are no arguments provided on the command line. In other words,
|
8
|
+
it is assumed that if the user supplies their own arguments, then
|
9
|
+
they don't require any from DotOpts. This redesign prevents a lot of
|
10
|
+
potential headaches with how prepend/append options can interact
|
11
|
+
with options proved by the end user. In the future, we might adds some
|
12
|
+
additional flexibility, of argument substitutions.
|
13
|
+
|
14
|
+
Changes:
|
15
|
+
|
16
|
+
* Rethink when arguments are applied.
|
17
|
+
* Reimplemented parser.
|
18
|
+
|
19
|
+
|
3
20
|
## 0.1.3 / 2013-01-30
|
4
21
|
|
5
22
|
Environment settings were not being applied. This release improves the code
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# DotOpts
|
2
2
|
|
3
|
-
**Automated
|
3
|
+
**Automated Command-line Options (for Ruby Executables)**
|
4
4
|
|
5
5
|
[Website](http://rubyworks.github.com/dotopts) /
|
6
6
|
[Report Issue](http://github.com/rubyworks/dotopts/issues) /
|
@@ -10,9 +10,9 @@
|
|
10
10
|
|
11
11
|
## About
|
12
12
|
|
13
|
-
DotOpts is an automatic
|
14
|
-
project's local `.option` configuration file and applies the
|
15
|
-
arguments when a matching command is invoked.
|
13
|
+
DotOpts is an automatic command-line argument augmenter. It looks for a
|
14
|
+
project's local `.option` (or `.opts`) configuration file and applies the
|
15
|
+
appropriate arguments when a matching command is invoked.
|
16
16
|
|
17
17
|
|
18
18
|
## Features
|
@@ -21,28 +21,37 @@ arguments when a matching command is invoked.
|
|
21
21
|
* Can be used to set environment variables in addition to arguments.
|
22
22
|
* Supports environment variable substitution.
|
23
23
|
* Supports conditional augmentation using environment settings.
|
24
|
-
* Simple and easy to
|
24
|
+
* Simple and easy to understand plain-text configuration format.
|
25
25
|
|
26
26
|
|
27
27
|
## Install
|
28
28
|
|
29
|
-
|
29
|
+
If you are using an application that depends on DotOpts for configuration,
|
30
|
+
there is nothing you have to do. Installing the said application via
|
31
|
+
RubyGems should also install DotOpts and require it as needed.
|
32
|
+
|
33
|
+
### General Setup
|
34
|
+
|
35
|
+
To use DotOpts universally, even for command-line applications that do not
|
36
|
+
directly utilize it, you can install DotOpts via RubyGems:
|
30
37
|
|
31
38
|
gem install dotopts
|
32
39
|
|
33
|
-
|
34
|
-
of whether they have built-in support for DotOpts or not). The most
|
35
|
-
univeral approach is to add `-rdotopts` to you `RUBYOPT` environment
|
36
|
-
variable.
|
40
|
+
Then add `-rdotopts` to your `RUBYOPT` environment variable.
|
37
41
|
|
38
42
|
export RUBYOPT="-rdotopts"
|
39
43
|
|
40
|
-
This ensures DotOpts is
|
41
|
-
|
44
|
+
This ensures DotOpts is used whenever Ruby is used.
|
45
|
+
|
46
|
+
### Special Setup
|
47
|
+
|
48
|
+
Another approach is to use DotOpts per-project development project using
|
49
|
+
via Bundler, adding DotOpts to your project's Gemfile.
|
42
50
|
|
43
51
|
gem 'dotopts'
|
44
52
|
|
45
|
-
This will allow
|
53
|
+
This will allow DotOpts to work whenever using `bundle exec` or Bundler
|
54
|
+
created binstub.
|
46
55
|
|
47
56
|
|
48
57
|
## Usage
|
@@ -55,9 +64,9 @@ A simple example of a projects `.option` file:
|
|
55
64
|
yard doc
|
56
65
|
--title="Bad Ass Program"
|
57
66
|
|
58
|
-
This simply says, that whenever `yardoc` or `yard doc` is executed
|
59
|
-
add the `--title="Bad Ass Program"`
|
60
|
-
|
67
|
+
This simply says, that whenever `yardoc` or `yard doc` is executed, and
|
68
|
+
no other arguments are given, then add the `--title="Bad Ass Program"`
|
69
|
+
argument to the end of the command's arguments (internally `ARGV`).
|
61
70
|
|
62
71
|
|
63
72
|
### Setting Environment Variables
|
@@ -67,7 +76,7 @@ lines with `$ `.
|
|
67
76
|
|
68
77
|
yardoc
|
69
78
|
yard doc
|
70
|
-
$
|
79
|
+
$ RUBYOPT="-rbadass"
|
71
80
|
--title="Bad Ass Program"
|
72
81
|
|
73
82
|
The space after the cash sign is important! Otherwise it will be interpreted
|
@@ -76,16 +85,16 @@ as a variable substitution.
|
|
76
85
|
|
77
86
|
### Conditional Profiles
|
78
87
|
|
79
|
-
The `.option` configuration file
|
88
|
+
The `.option` configuration file supports profiles via the square brackets.
|
80
89
|
Profiles are chosen via the `$profile` or `$p` environment variable.
|
81
90
|
|
82
91
|
```
|
83
92
|
[coverage]
|
84
93
|
rubytest
|
85
|
-
-r
|
94
|
+
-r microtest
|
86
95
|
```
|
87
96
|
|
88
|
-
So the above means that `-r
|
97
|
+
So the above means that `-r micortest` should be added the argument list when
|
89
98
|
`rubytest` is executed, but only if `$profile` or `$p` is equal to `"coverage"`.
|
90
99
|
|
91
100
|
Square brackets can also be used to match against any environment variable
|
@@ -104,38 +113,38 @@ to the square brackets separated by a space.
|
|
104
113
|
[coverage RUBY_ENGINE=jruby]
|
105
114
|
rubytest
|
106
115
|
-r jruby-sandbox
|
107
|
-
-r
|
116
|
+
-r microtest
|
108
117
|
```
|
109
118
|
|
110
119
|
Finally, environment values can be matched against simple regular expressions
|
111
|
-
using a tilde (`~`) before the value. Be sure to
|
120
|
+
using a tilde (`~`) before the value. Be sure to put the value in quotes when
|
112
121
|
using regular expressions.
|
113
122
|
|
114
123
|
```
|
115
124
|
[~"cov(erage)?" RUBY_ENGINE=~"jruby|rubinius"]
|
116
125
|
rubytest
|
117
126
|
-r jruby-sandbox
|
118
|
-
-r
|
127
|
+
-r microtest
|
119
128
|
```
|
120
129
|
|
121
130
|
### Tool Support
|
122
131
|
|
123
|
-
Ruby tool developers can support
|
124
|
-
`require 'dotopts'` in their program before parsing
|
125
|
-
simply injects arguments into `ARGV` so it can work with any
|
126
|
-
|
132
|
+
Ruby tool developers can support DotOpts out-of-the-box simple by running
|
133
|
+
`require 'dotopts'` in their program before parsing ARGV. DotOpts
|
134
|
+
simply injects arguments into `ARGV` so it can work with any command-line
|
135
|
+
options parser.
|
127
136
|
|
128
137
|
|
129
138
|
## Development
|
130
139
|
|
131
140
|
### Suggestions & Contributions
|
132
141
|
|
133
|
-
DotOpts is a brand new application, and still rather wet behind the
|
142
|
+
DotOpts is a brand new application, and still rather wet behind the ears, so to
|
134
143
|
speak. So your input is critical to making it better. Any and all suggestions and
|
135
144
|
contributions are much appreciated. If you have any ideas on how to improve DotOpts,
|
136
145
|
or find any flaws in its design that need address, please drop a comment on the
|
137
146
|
[Issues](http://github.com/rubyworks/dotopts/issues) page. Or even better, be proactive!
|
138
|
-
Fork the project submit a pull request. Thanks.
|
147
|
+
Fork the project and submit a pull request. Thanks.
|
139
148
|
|
140
149
|
### Universal Solution?
|
141
150
|
|
data/demo/applique/battery.rb
CHANGED
@@ -15,14 +15,19 @@ When 'we run `/(.*?)/`' do |command|
|
|
15
15
|
end
|
16
16
|
|
17
17
|
ENV['cmd'] = args.shift
|
18
|
-
|
19
18
|
ARGV.replace(args)
|
20
19
|
|
21
|
-
|
20
|
+
DotOpts.configure!(@opts_text)
|
22
21
|
end
|
23
22
|
|
24
23
|
When 'we should get the arguments' do |text|
|
24
|
+
#opts_args = []
|
25
|
+
#@opts_cmds.each do |c|
|
26
|
+
# next unless c.current?
|
27
|
+
# opts_args += c.arguments
|
28
|
+
#end
|
29
|
+
|
25
30
|
args = text.split("\n").map{ |x| x.strip }
|
26
|
-
args.assert ==
|
31
|
+
args.assert == ARGV #opts_args
|
27
32
|
end
|
28
33
|
|
data/lib/dotopts/api.rb
CHANGED
@@ -1,36 +1,46 @@
|
|
1
1
|
module DotOpts
|
2
2
|
require 'dotopts/parser'
|
3
3
|
|
4
|
-
# Configuration file
|
5
|
-
|
4
|
+
# Configuration file names.
|
5
|
+
#
|
6
|
+
# @note Sorry, I could not decide between these two.
|
7
|
+
OPTIONS_FILES = ['.opts', '.option']
|
6
8
|
|
7
9
|
# Configure
|
8
10
|
#
|
9
11
|
# @param [String] file
|
10
12
|
# The configuration file to load. (optional)
|
11
13
|
#
|
12
|
-
# @return
|
13
|
-
def self.configure!(
|
14
|
-
file =
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
14
|
+
# @return [void]
|
15
|
+
def self.configure!(io=nil)
|
16
|
+
text, file = read_config(io)
|
17
|
+
|
18
|
+
if text
|
19
|
+
cmds = Parser.parse(text)
|
20
|
+
|
21
|
+
applicable = cmds.select do |c|
|
22
|
+
c.current?
|
23
|
+
end
|
24
|
+
|
25
|
+
applicable.each do |c|
|
26
|
+
argv = c.arguments
|
27
|
+
env = c.environment
|
28
|
+
|
29
|
+
debug(file, argv, env)
|
30
|
+
apply(argv, env)
|
31
|
+
end
|
24
32
|
end
|
25
33
|
end
|
26
34
|
|
27
35
|
# Returns the options file of the current project.
|
28
36
|
#
|
29
|
-
# @return
|
37
|
+
# @return [String] The options file of the project.
|
30
38
|
def self.options_file
|
31
39
|
if project_root
|
32
|
-
|
33
|
-
|
40
|
+
OPTIONS_FILES.each do |optfile|
|
41
|
+
file = File.join(project_root, optfile)
|
42
|
+
return file if File.exist?(file)
|
43
|
+
end
|
34
44
|
end
|
35
45
|
end
|
36
46
|
|
@@ -41,8 +51,10 @@ module DotOpts
|
|
41
51
|
dir = start_dir
|
42
52
|
home = File.expand_path('~')
|
43
53
|
until dir == home || dir == '/'
|
44
|
-
|
45
|
-
|
54
|
+
OPTIONS_FILES.each do |optfile|
|
55
|
+
if File.exist?(File.join(dir, optfile))
|
56
|
+
return dir
|
57
|
+
end
|
46
58
|
end
|
47
59
|
dir = File.dirname(dir)
|
48
60
|
end
|
@@ -51,17 +63,16 @@ module DotOpts
|
|
51
63
|
|
52
64
|
# Apply arguments and environment options.
|
53
65
|
#
|
54
|
-
#
|
55
|
-
#
|
56
|
-
# @return nothing
|
66
|
+
# @return [void]
|
57
67
|
def self.apply(argv, env={})
|
58
|
-
env.each{ |k,v|
|
68
|
+
env.each{ |k,v| ENV[k.to_s] = v.to_s }
|
59
69
|
ARGV.concat(argv)
|
70
|
+
#ARGV.replace(argv + ARGV)
|
60
71
|
end
|
61
72
|
|
62
73
|
# Print message to stderr if dopts_debug flag it set.
|
63
74
|
#
|
64
|
-
# @return
|
75
|
+
# @return [void]
|
65
76
|
def self.debug(file, argv, env)
|
66
77
|
return unless ENV['dotopts_debug']
|
67
78
|
|
@@ -78,4 +89,29 @@ module DotOpts
|
|
78
89
|
end
|
79
90
|
end
|
80
91
|
|
92
|
+
# Take an IO object and read it in. If it is a File
|
93
|
+
# object also return the file name. Strings are passed
|
94
|
+
# through untouched.
|
95
|
+
#
|
96
|
+
# @return [Array<String>]
|
97
|
+
def self.read_config(io)
|
98
|
+
text, file = nil, '(io)'
|
99
|
+
|
100
|
+
case io
|
101
|
+
when String
|
102
|
+
text = io
|
103
|
+
when File
|
104
|
+
text = io.read
|
105
|
+
file = io.path
|
106
|
+
when nil
|
107
|
+
if file = options_file
|
108
|
+
text = File.read(file)
|
109
|
+
end
|
110
|
+
else
|
111
|
+
text = io.read
|
112
|
+
end
|
113
|
+
|
114
|
+
return text, file
|
115
|
+
end
|
116
|
+
|
81
117
|
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
module DotOpts
|
2
|
+
|
3
|
+
# Get the current command.
|
4
|
+
#
|
5
|
+
# @note This is take from basename of `$0`. In the future, we may
|
6
|
+
# need to find a way to tweak this to somehow include parrent
|
7
|
+
# directories.
|
8
|
+
#
|
9
|
+
# @todo Is ENV['cmd'] okay? Maybe ['dotopts_command'] would be better?
|
10
|
+
def self.command
|
11
|
+
ENV['cmd'] || File.basename($0)
|
12
|
+
end
|
13
|
+
|
14
|
+
##
|
15
|
+
# Command class encapsulate a configuration for a given command and
|
16
|
+
# a given profile.
|
17
|
+
#
|
18
|
+
class Command
|
19
|
+
|
20
|
+
# Initialize new instance of Command.
|
21
|
+
#
|
22
|
+
# @param [String] name
|
23
|
+
# The name of the command. Can include subcommand, e.g. `yard doc`.
|
24
|
+
#
|
25
|
+
# @option settings [String,nil] :profile
|
26
|
+
# The profile for which this command configuation would be applicable.
|
27
|
+
#
|
28
|
+
# @return [void]
|
29
|
+
def initialize(name, settings={})
|
30
|
+
@name = name
|
31
|
+
|
32
|
+
self.profile = settings[:profile]
|
33
|
+
|
34
|
+
@arguments = []
|
35
|
+
@environment = {}
|
36
|
+
end
|
37
|
+
|
38
|
+
# Command name. [String]
|
39
|
+
attr :name
|
40
|
+
|
41
|
+
# Profile designation.
|
42
|
+
#
|
43
|
+
# @return [String,nil]
|
44
|
+
def profile
|
45
|
+
@profile
|
46
|
+
end
|
47
|
+
|
48
|
+
# Set profile designation.
|
49
|
+
#
|
50
|
+
# @param [String,nil]
|
51
|
+
# The profile designation.
|
52
|
+
#
|
53
|
+
# @return [String,nil]
|
54
|
+
def profile=(profile)
|
55
|
+
@profile = profile ? profile.to_str : nil #? shellwords(profile).first : nil
|
56
|
+
end
|
57
|
+
|
58
|
+
# Environment settings.
|
59
|
+
#
|
60
|
+
# @return [Hash]
|
61
|
+
def environment
|
62
|
+
@environment
|
63
|
+
end
|
64
|
+
|
65
|
+
# Arguments.
|
66
|
+
#
|
67
|
+
# @return [Array]
|
68
|
+
def arguments
|
69
|
+
@arguments
|
70
|
+
end
|
71
|
+
|
72
|
+
# Add argument or environment entries to command.
|
73
|
+
#
|
74
|
+
# TODO: Is there too much "parsing" going on here?
|
75
|
+
# Should some of this be in Parser instead?
|
76
|
+
def <<(entry)
|
77
|
+
entry = entry.strip
|
78
|
+
if entry.start_with?('$ ')
|
79
|
+
# environment
|
80
|
+
entry.sub!(/\$\s+/, '')
|
81
|
+
shellwords(entry).each do |s|
|
82
|
+
name, value = s.split('=')
|
83
|
+
@environment[name] = subenv(value) unless name.empty?
|
84
|
+
end
|
85
|
+
else
|
86
|
+
# argument
|
87
|
+
shellwords(entry).each do |s|
|
88
|
+
@arguments << subenv(s) unless s.empty?
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Is the command applicable to the current command line?
|
94
|
+
#
|
95
|
+
# @return [Boolean]
|
96
|
+
def current?
|
97
|
+
command? && profile?
|
98
|
+
end
|
99
|
+
|
100
|
+
# Does the command's name match the current command?
|
101
|
+
#
|
102
|
+
# @return [Boolean]
|
103
|
+
def command?
|
104
|
+
this = @name.split(' ')
|
105
|
+
real = [DotOpts.command, *ARGV][0,this.size]
|
106
|
+
this == real && ARGV.size < this.size # no other arguments
|
107
|
+
end
|
108
|
+
|
109
|
+
# Does the command's profile match the current environment?
|
110
|
+
#
|
111
|
+
# @return [Boolean]
|
112
|
+
def profile?
|
113
|
+
shellwords(profile || "").all? do |sw|
|
114
|
+
case sw
|
115
|
+
when /^\~/
|
116
|
+
true if Regexp.new(sw.sub('~','')) === (ENV['profile'] || ENV['p']).to_s
|
117
|
+
when /=~/
|
118
|
+
name, value = sw.split('=~')
|
119
|
+
#name = 'profile' if name.empty?
|
120
|
+
true if Regexp.new(value) === ENV[name]
|
121
|
+
when /=/
|
122
|
+
name, value = sw.split('=')
|
123
|
+
#name = 'profile' if name.empty?
|
124
|
+
true if subenv(value) == ENV[name]
|
125
|
+
else
|
126
|
+
true if sw.to_s == (ENV['profile'] || ENV['p']).to_s
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
# Split a string up into shellwords.
|
134
|
+
#
|
135
|
+
# @return [Array]
|
136
|
+
def shellwords(value)
|
137
|
+
Shellwords.shellwords(value)
|
138
|
+
end
|
139
|
+
|
140
|
+
# Substitute environment variables.
|
141
|
+
#
|
142
|
+
# @return [String]
|
143
|
+
def subenv(value)
|
144
|
+
value.gsub(/\$(\w+)/){ |m| ENV[$1] }
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
data/lib/dotopts/parser.rb
CHANGED
@@ -2,15 +2,22 @@ module DotOpts
|
|
2
2
|
|
3
3
|
class Parser
|
4
4
|
require 'shellwords'
|
5
|
+
require_relative 'command'
|
5
6
|
|
6
7
|
# Regular expression to match profile headers.
|
7
|
-
|
8
|
+
RE_PROFILE = /^\[(.*)\]/
|
8
9
|
|
9
10
|
# Regular expression to match command headers.
|
10
|
-
|
11
|
+
RE_COMMAND = /^\w/
|
12
|
+
|
13
|
+
# Regular expression to match arguments.
|
14
|
+
RE_ARGUMENT = /^\s+\S+/
|
15
|
+
|
16
|
+
# Regular expression to match environment setting.
|
17
|
+
RE_ENVIRONMENT = /^\s+\$/
|
11
18
|
|
12
19
|
# Regular expression to match blank strings.
|
13
|
-
|
20
|
+
RE_BLANK = /^\s*$/
|
14
21
|
|
15
22
|
# Convenience constructor for `new(text).parse`.
|
16
23
|
#
|
@@ -18,7 +25,7 @@ module DotOpts
|
|
18
25
|
def self.parse(text)
|
19
26
|
parser = new(text)
|
20
27
|
parser.parse
|
21
|
-
parser
|
28
|
+
parser.commands
|
22
29
|
end
|
23
30
|
|
24
31
|
# Initialize new instance.
|
@@ -27,43 +34,120 @@ module DotOpts
|
|
27
34
|
#
|
28
35
|
def initialize(text)
|
29
36
|
@text = text.to_s
|
30
|
-
@arguments = []
|
31
|
-
@environment = {}
|
32
|
-
end
|
33
37
|
|
34
|
-
|
35
|
-
|
38
|
+
#
|
39
|
+
@commands = []
|
36
40
|
|
37
|
-
|
38
|
-
|
41
|
+
# Holds the current commands being parsed.
|
42
|
+
@_commands = []
|
43
|
+
@_profiles = []
|
44
|
+
end
|
45
|
+
|
46
|
+
#
|
47
|
+
attr :commands
|
39
48
|
|
40
|
-
# The
|
41
|
-
attr :
|
49
|
+
# The configuration document text. [String]
|
50
|
+
attr :text
|
42
51
|
|
43
52
|
# Parse the configuration text.
|
53
|
+
#
|
54
|
+
#
|
44
55
|
def parse
|
45
56
|
lines = @text.lines.to_a
|
46
57
|
|
47
|
-
|
58
|
+
remove_blanks(lines)
|
48
59
|
|
49
60
|
# put initial non-profiled settings last
|
50
|
-
if lines.first !~
|
51
|
-
|
52
|
-
|
53
|
-
|
61
|
+
#if lines.first !~ RE_PROFILE
|
62
|
+
# index = lines.index{ |line| line =~ RE_PROFILE }
|
63
|
+
# if index
|
64
|
+
# lines = lines[index..-1] + ['[]'] + lines[0...index]
|
65
|
+
# else
|
66
|
+
# #lines = ['[]'] + lines
|
67
|
+
# end
|
68
|
+
#end
|
69
|
+
|
70
|
+
parse_profiles(lines)
|
71
|
+
end
|
72
|
+
|
73
|
+
#
|
74
|
+
#
|
75
|
+
#
|
76
|
+
def parse_profiles(lines)
|
77
|
+
@_profiles = []
|
78
|
+
until lines.empty?
|
79
|
+
line = lines.first.rstrip
|
80
|
+
case line
|
81
|
+
when RE_BLANK
|
82
|
+
lines.shift
|
83
|
+
when RE_PROFILE
|
84
|
+
@_profiles << $1
|
85
|
+
lines.shift
|
54
86
|
else
|
55
|
-
|
87
|
+
#@_commands = []
|
88
|
+
parse_command(lines)
|
56
89
|
end
|
57
90
|
end
|
91
|
+
end
|
58
92
|
|
59
|
-
|
93
|
+
# Parse lines from command onward until another profile
|
94
|
+
# or end of document is reached.
|
95
|
+
#
|
96
|
+
# @return [void]
|
97
|
+
def parse_command(lines)
|
98
|
+
previous = nil
|
99
|
+
while line = lines.first
|
100
|
+
case line
|
101
|
+
when RE_BLANK
|
102
|
+
when RE_COMMAND
|
103
|
+
if previous != :command
|
104
|
+
@commands.concat @_commands
|
105
|
+
@_commands = []
|
106
|
+
end
|
107
|
+
if @_profiles.empty?
|
108
|
+
@_commands << Command.new(line.strip, :profile=>nil)
|
109
|
+
else
|
110
|
+
@_profiles.each do |profile|
|
111
|
+
@_commands << Command.new(line.strip, :profile=>profile)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
previous = :command
|
115
|
+
when RE_ARGUMENT, RE_ENVIRONMENT
|
116
|
+
if @_commands.empty?
|
117
|
+
raise SyntaxError, "no command before arguments\n@ #{line}"
|
118
|
+
end
|
119
|
+
@_commands.each{ |c| c << line }
|
120
|
+
previous = :argument
|
121
|
+
when RE_PROFILE
|
122
|
+
@commands.concat @_commands
|
123
|
+
@_commands = []
|
124
|
+
@_profiles = []
|
125
|
+
return
|
126
|
+
end
|
127
|
+
lines.shift
|
128
|
+
end
|
129
|
+
@commands.concat @_commands
|
60
130
|
end
|
61
131
|
|
132
|
+
# Remove intialize blank lines for an array of strings.
|
133
|
+
#
|
134
|
+
# @param [Array<String>] lines
|
135
|
+
#
|
136
|
+
# @return [Array<String>]
|
137
|
+
def remove_blanks(lines)
|
138
|
+
lines.shift while RE_BLANK =~ lines.first
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
=begin
|
62
146
|
# Parse profiles.
|
63
147
|
def parse_profiles(lines)
|
64
148
|
until lines.empty?
|
65
149
|
line = lines.first.rstrip
|
66
|
-
if md = RE_PROFILE_HEADER.match(line)
|
150
|
+
if md = RE_PROFILE_HEADER.match(line) # TODO: this is a bit wanky
|
67
151
|
profile = md.post_match.chomp(']')
|
68
152
|
matches = shellwords(profile).all? do |shellword|
|
69
153
|
case shellword
|
@@ -146,30 +230,8 @@ module DotOpts
|
|
146
230
|
def current_command
|
147
231
|
ENV['cmd'] || File.basename($0)
|
148
232
|
end
|
149
|
-
|
150
|
-
# Substitute environment variables.
|
151
|
-
#
|
152
|
-
# @return [String]
|
153
|
-
def subenv(value)
|
154
|
-
value.gsub(/\$(\w+)/){ |m| ENV[$1] }
|
155
|
-
end
|
156
|
-
|
157
|
-
# Split a string up into shellwords.
|
158
|
-
#
|
159
|
-
# @return [Array]
|
160
|
-
def shellwords(value)
|
161
|
-
Shellwords.shellwords(value)
|
162
|
-
end
|
163
|
-
|
164
|
-
# Remove intialize blank lines for an array of strings.
|
165
|
-
#
|
166
|
-
# @param [Array<String>] lines
|
167
|
-
#
|
168
|
-
# @return [Array<String>]
|
169
|
-
def remove_initial_blank_lines(lines)
|
170
|
-
lines.shift while RE_BLANK_STRING =~ lines.first
|
171
|
-
end
|
172
|
-
|
173
233
|
end
|
174
234
|
|
175
235
|
end
|
236
|
+
|
237
|
+
=end
|
data/spec/helper.rb
CHANGED
@@ -1,6 +1,19 @@
|
|
1
|
+
if ENV['RUBYOPT'].to_s.include?('-rdotopts')
|
2
|
+
abort "Remove `-rdotopts` from RUBYOPT environment first."
|
3
|
+
end
|
4
|
+
|
1
5
|
require 'spectroscope'
|
2
6
|
require 'ae'
|
3
7
|
|
8
|
+
#if ENV['cov']
|
9
|
+
# require 'simplecov'
|
10
|
+
# SimpleCov.command_name File.basename($0)
|
11
|
+
# SimpleCov.start do
|
12
|
+
# add_filter "spec/"
|
13
|
+
# coverage_dir 'log/coverage'
|
14
|
+
# end
|
15
|
+
#end
|
16
|
+
|
4
17
|
require 'dotopts/api'
|
5
18
|
|
6
19
|
module Kernel
|
data/spec/spec_api.rb
CHANGED
@@ -3,15 +3,27 @@ require_relative "helper"
|
|
3
3
|
describe DotOpts do
|
4
4
|
|
5
5
|
it "can configure!" do
|
6
|
-
ENV['cmd'] = 'yard'
|
6
|
+
ENV['cmd'] = 'yard'
|
7
|
+
ARGV.replace([])
|
7
8
|
|
8
9
|
example_avex_file = File.dirname(__FILE__) + '/fixtures/yard.opts'
|
9
10
|
|
10
|
-
DotOpts.configure!(example_avex_file)
|
11
|
+
DotOpts.configure!(File.new(example_avex_file))
|
11
12
|
|
12
13
|
ARGV[-2].assert == '--title'
|
13
14
|
ARGV[-1].assert == 'Big Title'
|
14
15
|
end
|
15
16
|
|
17
|
+
it "can should not configure! when arguments are given" do
|
18
|
+
ENV['cmd'] = 'yard'
|
19
|
+
ARGV.replace(['--private'])
|
20
|
+
|
21
|
+
example_avex_file = File.dirname(__FILE__) + '/fixtures/yard.opts'
|
22
|
+
|
23
|
+
DotOpts.configure!(File.new(example_avex_file))
|
24
|
+
|
25
|
+
ARGV[-1].assert == '--private'
|
26
|
+
end
|
27
|
+
|
16
28
|
end
|
17
29
|
|
File without changes
|
data/spec/spec_parser.rb
CHANGED
@@ -2,47 +2,68 @@ require_relative "helper"
|
|
2
2
|
|
3
3
|
describe DotOpts::Parser do
|
4
4
|
|
5
|
-
before do
|
6
|
-
|
7
|
-
end
|
5
|
+
#before do
|
6
|
+
# ENV['cmd'] = 'yard' # FIXME: try `foo` and watch what happens
|
7
|
+
#end
|
8
8
|
|
9
9
|
describe "without profiles" do
|
10
|
-
|
11
10
|
it "can parse configuration" do
|
12
11
|
text = "yard\n" +
|
13
12
|
" --title Super"
|
14
|
-
|
13
|
+
cmds = DotOpts::Parser.parse(text)
|
15
14
|
|
16
|
-
|
15
|
+
cmds.size.assert == 1
|
16
|
+
cmds[0].name.assert == 'yard'
|
17
|
+
cmds[0].arguments.assert == ['--title', 'Super']
|
18
|
+
cmds[0].profile.assert.nil?
|
17
19
|
end
|
18
20
|
|
19
21
|
it "can parse configuration with multiple arguments" do
|
20
22
|
text = "yard\n" +
|
21
23
|
" --title Super\n" +
|
22
24
|
" --private\n"
|
25
|
+
cmds = DotOpts::Parser.parse(text)
|
23
26
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
+
cmds.size.assert == 1
|
28
|
+
cmds[0].name.assert == 'yard'
|
29
|
+
cmds[0].arguments.assert == ['--title', 'Super', '--private']
|
30
|
+
cmds[0].profile.assert.nil?
|
27
31
|
end
|
28
32
|
|
33
|
+
it "can parse configuration with multiple commands" do
|
34
|
+
text = "yard\n" +
|
35
|
+
" --title Super\n" +
|
36
|
+
" --private\n" +
|
37
|
+
"rdoc\n" +
|
38
|
+
" --private\n"
|
39
|
+
cmds = DotOpts::Parser.parse(text)
|
40
|
+
|
41
|
+
cmds.size.assert == 2
|
42
|
+
|
43
|
+
cmds[0].name.assert == 'yard'
|
44
|
+
cmds[0].arguments.assert == ['--title', 'Super', '--private']
|
45
|
+
cmds[0].profile.assert.nil?
|
46
|
+
|
47
|
+
cmds[1].name.assert == 'rdoc'
|
48
|
+
cmds[1].arguments.assert == ['--private']
|
49
|
+
cmds[1].profile.assert.nil?
|
50
|
+
end
|
29
51
|
end
|
30
52
|
|
31
53
|
describe "with profiles" do
|
32
|
-
|
33
54
|
it "can parse configuration with initial profile" do
|
34
55
|
text = "[example]\n" +
|
35
56
|
"yard\n" +
|
36
57
|
" --title Super"
|
58
|
+
cmds = DotOpts::Parser.parse(text)
|
37
59
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
parser.arguments.assert == ['--title', 'Super']
|
60
|
+
cmds.size.assert == 1
|
61
|
+
cmds.first.name.assert == 'yard'
|
62
|
+
cmds.first.profile.assert == 'example'
|
63
|
+
cmds.first.arguments.assert == ['--title', 'Super']
|
43
64
|
end
|
44
65
|
|
45
|
-
it "can parse configuration with
|
66
|
+
it "can parse configuration with multiple profiles" do
|
46
67
|
text = "[something]\n" +
|
47
68
|
"yard\n" +
|
48
69
|
" --title Sucky\n" +
|
@@ -51,18 +72,23 @@ describe DotOpts::Parser do
|
|
51
72
|
"yard\n" +
|
52
73
|
" --title Super"
|
53
74
|
|
54
|
-
|
55
|
-
DotOpts::Parser.parse(text)
|
56
|
-
end
|
75
|
+
cmds = DotOpts::Parser.parse(text)
|
57
76
|
|
58
|
-
|
59
|
-
end
|
77
|
+
cmds.size.assert == 2
|
60
78
|
|
61
|
-
|
79
|
+
cmds[0].name.assert == 'yard'
|
80
|
+
cmds[0].profile.assert == 'something'
|
81
|
+
cmds[0].arguments.assert == ['--title', 'Sucky']
|
62
82
|
|
63
|
-
|
64
|
-
|
83
|
+
cmds[1].name.assert == 'yard'
|
84
|
+
cmds[1].profile.assert == 'example'
|
85
|
+
cmds[1].arguments.assert == ['--title', 'Super']
|
86
|
+
end
|
65
87
|
end
|
66
88
|
|
89
|
+
#after do
|
90
|
+
# ENV['cmd'] = nil
|
91
|
+
#end
|
92
|
+
|
67
93
|
end
|
68
94
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dotopts
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-02-
|
12
|
+
date: 2013-02-16 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: qed
|
@@ -70,24 +70,25 @@ extra_rdoc_files:
|
|
70
70
|
- README.md
|
71
71
|
files:
|
72
72
|
- .index
|
73
|
-
- demo/09_battery/profiles_basic.md
|
74
73
|
- demo/09_battery/commands_only.md
|
75
|
-
- demo/09_battery/
|
74
|
+
- demo/09_battery/profiles_basic.md
|
76
75
|
- demo/09_battery/profiles_complex.md
|
76
|
+
- demo/09_battery/profiles_regex.md
|
77
77
|
- demo/09_battery/substitution.md
|
78
|
-
- demo/applique/require.rb
|
79
78
|
- demo/applique/battery.rb
|
79
|
+
- demo/applique/require.rb
|
80
80
|
- lib/dotopts/api.rb
|
81
|
+
- lib/dotopts/command.rb
|
81
82
|
- lib/dotopts/parser.rb
|
82
83
|
- lib/dotopts.rb
|
83
|
-
- spec/
|
84
|
+
- spec/fixtures/yard.opts
|
84
85
|
- spec/helper.rb
|
86
|
+
- spec/spec_api.rb
|
87
|
+
- spec/spec_command.rb
|
85
88
|
- spec/spec_parser.rb
|
86
|
-
- spec/fixtures/yard.opts
|
87
|
-
- Gemfile.lock
|
88
|
-
- LICENSE.txt
|
89
89
|
- HISTORY.md
|
90
90
|
- README.md
|
91
|
+
- LICENSE.txt
|
91
92
|
homepage: http://rubyworks.github.com/dotopts
|
92
93
|
licenses:
|
93
94
|
- BSD-2-Clause
|
@@ -117,4 +118,5 @@ test_files:
|
|
117
118
|
- spec/spec_api.rb
|
118
119
|
- spec/helper.rb
|
119
120
|
- spec/spec_parser.rb
|
121
|
+
- spec/spec_command.rb
|
120
122
|
has_rdoc:
|
data/Gemfile.lock
DELETED
@@ -1,30 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
dotopts (0.1.3)
|
5
|
-
|
6
|
-
GEM
|
7
|
-
remote: http://rubygems.org/
|
8
|
-
specs:
|
9
|
-
ae (1.8.1)
|
10
|
-
ansi
|
11
|
-
ansi (1.4.3)
|
12
|
-
brass (1.2.1)
|
13
|
-
facets (2.9.3)
|
14
|
-
qed (2.9.1)
|
15
|
-
ansi
|
16
|
-
brass
|
17
|
-
facets (>= 2.8)
|
18
|
-
rubytest (0.6.0)
|
19
|
-
ansi
|
20
|
-
spectroscope (0.1.0)
|
21
|
-
rubytest
|
22
|
-
|
23
|
-
PLATFORMS
|
24
|
-
ruby
|
25
|
-
|
26
|
-
DEPENDENCIES
|
27
|
-
ae
|
28
|
-
dotopts!
|
29
|
-
qed
|
30
|
-
spectroscope
|