dotopts 0.1.3 → 0.2.0
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/.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
|