man_parser 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown CHANGED
@@ -2,7 +2,7 @@ Parse man source
2
2
 
3
3
  Install
4
4
  =======
5
- sudo gem install grosser-man_parser -s http://gems.github.com/
5
+ sudo gem install man_parser -s http://gemcutter.org
6
6
 
7
7
  Usage
8
8
  =====
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
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 = parse_description(sections['DESCRIPTION'])
13
- options = options.map{|option| parse_option(option*' ') }
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.gsub(/\\f[IB](.*?)\\fR/,"\\1").gsub('\\','')
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} <-> nil !"
36
- return
36
+ puts "#{option} <-> nothing found !"
37
+ return {}
37
38
  end
38
39
 
39
- found[:description] = found[:description].to_s.strip.sub(/\s*\.TP$/,'')
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 combind the 2 descriptions parts
47
- def self.parse_description(text)
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")[1..-1].each do |line|
67
+ text.split("\n").each do |line|
54
68
 
55
- if start_of_option?(line) and not already_switched
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 = Hash.new([])
94
+ sections = {}
95
+ temp = []
79
96
 
80
- text.split("\n").each do |line|
97
+ lines.each do |line|
81
98
  if line =~ /^\.SH (.*)$/
82
- name = $1
99
+ sections[name] = temp * "\n"
100
+ temp = []
101
+ name = $1.gsub('"','').strip
83
102
  else
84
- sections[name] += [line]
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.start_of_option?(line)
93
- !!( line =~ /^\\fB\\-/)
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.0"
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
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH << 'lib'
3
+ require 'man_parser'
4
+ print ManParser.send(ARGV[0], ARGV[1])
@@ -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 =~ /^\.PPList information about the FILEs \(the current/
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 == 58
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 == nil
119
+ x.should == {}
87
120
  end
88
121
  end
89
122
 
90
- describe :start_of_option? do
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(:start_of_option?, line).should == success
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.0
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