man_parser 0.1.0 → 0.1.1
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/README.markdown +1 -1
- data/VERSION +1 -1
- data/lib/man_parser.rb +67 -26
- data/man_parser.gemspec +2 -1
- data/spec/call +4 -0
- data/spec/man_parser_spec.rb +41 -6
- metadata +2 -1
data/README.markdown
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.1
|
data/lib/man_parser.rb
CHANGED
@@ -9,52 +9,67 @@ class ManParser
|
|
9
9
|
|
10
10
|
def self.parse(cmd)
|
11
11
|
sections = sections(source(cmd))
|
12
|
-
description, options =
|
13
|
-
|
12
|
+
description, options = find_options(sections['OPTIONS']||sections['DESCRIPTION'])
|
13
|
+
description ||= sections['DESCRIPTION']
|
14
|
+
options = parse_options(options)
|
14
15
|
{:description => description.map{|l|l.strip}.join(''), :options=>options, :sections=>sections}
|
15
16
|
end
|
16
17
|
|
17
18
|
private
|
18
19
|
|
20
|
+
# remove common prefix from options
|
21
|
+
def self.parse_options(options)
|
22
|
+
options = options.map{|option| parse_option(option*' ') }.reject{|o| o.empty?}
|
23
|
+
common = common_prefix(options.map{|o| o[:description]})
|
24
|
+
options.each{|o| o[:description] = o[:description].split(//)[common..-1].to_s}
|
25
|
+
options
|
26
|
+
end
|
27
|
+
|
19
28
|
def self.root
|
20
29
|
'/usr/share/man/man1'
|
21
30
|
end
|
22
31
|
|
23
32
|
def self.parse_option(option)
|
24
|
-
option = option
|
25
|
-
|
26
|
-
found = if option =~ /^-(\w+), --([-\w]+)(=(\w+))?(.*)/
|
27
|
-
{:alias=>$1, :name=>$2, :argument=>$4, :description=>$5}
|
28
|
-
elsif option =~ /^--([-\w]+)(=(\w+))?(.*)/
|
29
|
-
{:name=>$1, :argument=>$3, :description=>$4}
|
30
|
-
elsif option =~ /^-([-\w]+)(.*)/
|
31
|
-
{:alias=>$1, :description=>$2}
|
32
|
-
end
|
33
|
-
|
33
|
+
option = without_markup(option)
|
34
|
+
found = option_parts(option)
|
34
35
|
if not found
|
35
|
-
puts "#{option} <->
|
36
|
-
return
|
36
|
+
puts "#{option} <-> nothing found !"
|
37
|
+
return {}
|
37
38
|
end
|
38
39
|
|
39
|
-
|
40
|
+
#remove ending .TP and any unnecessary whitespace
|
41
|
+
found[:description] = found[:description].to_s.strip.sub(/\s*\.TP$/,'').gsub(/\s{2,}/,' ')
|
40
42
|
found.delete(:argument) unless found[:argument]
|
41
43
|
|
42
44
|
found
|
43
45
|
end
|
44
46
|
|
47
|
+
def self.common_prefix(texts)
|
48
|
+
shortest = texts.map{|t| t.size}.min || 0
|
49
|
+
shortest.downto(1) do |i|
|
50
|
+
common = texts.map{|t| t[0...i]}.uniq
|
51
|
+
next if common.size != 1
|
52
|
+
next if common.first =~ /^\w+$/ # ['\d hello world','\d hell is hot'] -> '\d '
|
53
|
+
return i
|
54
|
+
end
|
55
|
+
return 0
|
56
|
+
end
|
57
|
+
|
45
58
|
# description can be split like "description, options, descriptions"
|
46
|
-
# so we remove the options part, and
|
47
|
-
def self.
|
59
|
+
# so we remove the options part, and combine the 2 descriptions parts
|
60
|
+
def self.find_options(text)
|
48
61
|
in_option = false
|
49
62
|
already_switched = false
|
50
63
|
options = []
|
64
|
+
known_names = []
|
51
65
|
description = []
|
52
66
|
|
53
|
-
text.split("\n")
|
67
|
+
text.split("\n").each do |line|
|
54
68
|
|
55
|
-
if
|
69
|
+
if is_unknown_option?(line, known_names) and not already_switched
|
56
70
|
in_option = true
|
57
71
|
options << [] #new option
|
72
|
+
known_names << parse_option(line)[:name]
|
58
73
|
elsif line =~ /^\.PP/ and in_option
|
59
74
|
already_switched = true
|
60
75
|
in_option = false
|
@@ -74,22 +89,48 @@ class ManParser
|
|
74
89
|
|
75
90
|
# split into sections according to "SectionHead" aka .SH
|
76
91
|
def self.sections(text)
|
92
|
+
lines = text.split("\n")+[".SH END"]
|
77
93
|
name = 'OUT_OF_SECTION'
|
78
|
-
sections =
|
94
|
+
sections = {}
|
95
|
+
temp = []
|
79
96
|
|
80
|
-
|
97
|
+
lines.each do |line|
|
81
98
|
if line =~ /^\.SH (.*)$/
|
82
|
-
name =
|
99
|
+
sections[name] = temp * "\n"
|
100
|
+
temp = []
|
101
|
+
name = $1.gsub('"','').strip
|
83
102
|
else
|
84
|
-
|
103
|
+
temp << line
|
85
104
|
end
|
86
105
|
end
|
87
106
|
|
88
|
-
sections.each{|k,v| sections[k] = v*"\n"}
|
89
107
|
sections
|
90
108
|
end
|
91
109
|
|
92
|
-
def self.
|
93
|
-
|
110
|
+
def self.without_markup(text)
|
111
|
+
text = text.gsub(/\\f[IB](.*?)\\f[RP]/,"\\1").gsub('\\','') # remove styles
|
112
|
+
text = text.gsub(/-\^-(\w)/,"--\\1") # remove weird ^ in e.g. grep options
|
113
|
+
end
|
114
|
+
|
115
|
+
# duplicate prevention, e.g. many lines in grep start with --mmap
|
116
|
+
def self.is_unknown_option?(line, known_names)
|
117
|
+
return if not is_option?(line)
|
118
|
+
name = parse_option(line)[:name]
|
119
|
+
name == nil or not known_names.include?(name)
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.is_option?(line)
|
123
|
+
!! option_parts(line)
|
124
|
+
end
|
125
|
+
|
126
|
+
def self.option_parts(text)
|
127
|
+
text = without_markup(text).sub(/\.[A-Z]+ (")?/,'') # remove any initial markup
|
128
|
+
if text =~ /^-(\w+)[,"| ]+--(\w[-\w]*)(=(\w+)| <(\w+)>)?(.*)/
|
129
|
+
{:alias=>$1, :name=>$2, :argument=>$4||$5, :description=>$6}
|
130
|
+
elsif text =~ /^--(\w[-\w]*)(=(\w+))?(.*)/
|
131
|
+
{:name=>$1, :argument=>$3, :description=>$4}
|
132
|
+
elsif text =~ /^-(\w[-\w]*)(.*)/
|
133
|
+
{:alias=>$1, :description=>$2}
|
134
|
+
end
|
94
135
|
end
|
95
136
|
end
|
data/man_parser.gemspec
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{man_parser}
|
8
|
-
s.version = "0.1.
|
8
|
+
s.version = "0.1.1"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Michael Grosser"]
|
@@ -20,6 +20,7 @@ Gem::Specification.new do |s|
|
|
20
20
|
"VERSION",
|
21
21
|
"lib/man_parser.rb",
|
22
22
|
"man_parser.gemspec",
|
23
|
+
"spec/call",
|
23
24
|
"spec/man_parser_spec.rb",
|
24
25
|
"spec/spec_helper.rb"
|
25
26
|
]
|
data/spec/call
ADDED
data/spec/man_parser_spec.rb
CHANGED
@@ -4,20 +4,20 @@ describe ManParser do
|
|
4
4
|
describe :parse do
|
5
5
|
it "finds the description" do
|
6
6
|
d = ManParser.parse('ls')[:description]
|
7
|
-
d.should =~
|
7
|
+
d.should =~ /^\.\\\" Add any additional description here\.PPList infor/
|
8
8
|
d.should =~ /problems, 2 if serious trouble\.$/
|
9
9
|
d.should_not include('\\-\\-all')
|
10
10
|
d.should_not include('do not ignore entries starting with')
|
11
11
|
d.should_not include(" ")
|
12
12
|
end
|
13
13
|
|
14
|
-
describe 'options' do
|
14
|
+
describe 'options in description' do
|
15
15
|
def options
|
16
16
|
ManParser.parse('ls')[:options]
|
17
17
|
end
|
18
18
|
|
19
19
|
it "finds all options" do
|
20
|
-
options.size.should ==
|
20
|
+
options.size.should == 57
|
21
21
|
end
|
22
22
|
|
23
23
|
it "extracts the name" do
|
@@ -36,6 +36,34 @@ describe ManParser do
|
|
36
36
|
options[2].should == {:name=>'author', :description=>'with -l, print the author of each file'}
|
37
37
|
end
|
38
38
|
end
|
39
|
+
|
40
|
+
describe 'options in OPTIONS section' do
|
41
|
+
def options
|
42
|
+
ManParser.parse('acpi')[:options]
|
43
|
+
end
|
44
|
+
|
45
|
+
it "finds all options x" do
|
46
|
+
options.size.should == 18
|
47
|
+
end
|
48
|
+
|
49
|
+
it "extracts the name" do
|
50
|
+
options.first[:name].should == 'battery'
|
51
|
+
end
|
52
|
+
|
53
|
+
it "extracts the alias" do
|
54
|
+
options.first[:alias].should == 'b'
|
55
|
+
end
|
56
|
+
|
57
|
+
it "extracts the description" do
|
58
|
+
options.first[:description].should == 'show battery information'
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe 'for grep' do
|
63
|
+
it "finds some options" do
|
64
|
+
ManParser.parse('grep')[:options].size.should == 70
|
65
|
+
end
|
66
|
+
end
|
39
67
|
end
|
40
68
|
|
41
69
|
describe :source do
|
@@ -80,15 +108,22 @@ describe ManParser do
|
|
80
108
|
x.should == {:alias=>"T", :name => 'tabsize', :argument=>'COLS', :description=>"assume tab stops at each COLS instead of 8"}
|
81
109
|
end
|
82
110
|
|
111
|
+
it "parses - and -- with <dir>" do
|
112
|
+
x = parse('.IP "\fB-d | --directory <dir>\fP " 10 bla bla')
|
113
|
+
x.should == {:alias=>'d', :name=>'directory', :argument=>'dir', :description=>'" 10 bla bla'}
|
114
|
+
end
|
115
|
+
|
83
116
|
it "does not parse random stuff" do
|
84
117
|
ManParser.stub!(:puts)
|
85
118
|
x = parse('as we say: \fB\-T\fR, \fB\-\-tabsize\fR=\fICOLS\fR assume tab stops at each COLS instead of 8')
|
86
|
-
x.should ==
|
119
|
+
x.should == {}
|
87
120
|
end
|
88
121
|
end
|
89
122
|
|
90
|
-
describe :
|
123
|
+
describe :is_option? do
|
91
124
|
{
|
125
|
+
'.BR \-P ", " \-\^\-perl\-regexp' => true,
|
126
|
+
'.IP "\fB-c | --cooling\fP " 10' => true,
|
92
127
|
'\fB\-\-version\fR'=>true,
|
93
128
|
'\fB\-1\fR'=>true,
|
94
129
|
'\fB\-\-color\fR=\fIauto\fR'=>true,
|
@@ -98,7 +133,7 @@ describe ManParser do
|
|
98
133
|
' asdadas'=>false
|
99
134
|
}.each do |line, success|
|
100
135
|
it "recognises #{line} -- #{success}" do
|
101
|
-
ManParser.send(:
|
136
|
+
ManParser.send(:is_option?, line).should == success
|
102
137
|
end
|
103
138
|
end
|
104
139
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: man_parser
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Grosser
|
@@ -27,6 +27,7 @@ files:
|
|
27
27
|
- VERSION
|
28
28
|
- lib/man_parser.rb
|
29
29
|
- man_parser.gemspec
|
30
|
+
- spec/call
|
30
31
|
- spec/man_parser_spec.rb
|
31
32
|
- spec/spec_helper.rb
|
32
33
|
has_rdoc: true
|