smart_parser 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rubocop.yml +12 -0
- data/Gemfile +21 -0
- data/Gemfile.lock +83 -0
- data/LICENSE.txt +21 -0
- data/README.md +107 -0
- data/Rakefile +8 -0
- data/exe/sparser +77 -0
- data/lib/smart_parser/counters/counter.rb +36 -0
- data/lib/smart_parser/counters/most_visited_counter.rb +42 -0
- data/lib/smart_parser/counters/uniq_visitors_counter.rb +41 -0
- data/lib/smart_parser/presenters/jsonl_presenter.rb +24 -0
- data/lib/smart_parser/presenters/line_presenter.rb +22 -0
- data/lib/smart_parser/presenters/table_presenter.rb +17 -0
- data/lib/smart_parser/version.rb +5 -0
- data/lib/smart_parser.rb +114 -0
- data/smart_parser.gemspec +31 -0
- metadata +67 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7c862e5efea46fdb350e38f6f9062cc23effb6266168987b8362839245dcc921
|
4
|
+
data.tar.gz: 1df7e38eda4d160d4bd79c48c5540b40540a711eecf12afcf693004991dc9f0a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: bf989652e214df38edcf2e25e6bfe38406471d9f6ca3da2e558d68498c62aa54ca45f5bdbc4623bed99a17a2912bcc544041c223a9e08cf786e8a1df9993495b
|
7
|
+
data.tar.gz: da06b601f51e421d0bd4fe6d43bb4f19010cd7b38d6ca4518eac4e1855d6d02d81553fca433c9a857e329bbfa50b4171584a44aa17469eb12858fae630702bf2
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
ruby "~> 2.7.2"
|
4
|
+
|
5
|
+
source 'https://rubygems.org'
|
6
|
+
|
7
|
+
# Specify your gem's dependencies in smart_parser.gemspec
|
8
|
+
gemspec
|
9
|
+
|
10
|
+
gem 'rake', '~> 12.0'
|
11
|
+
gem 'rspec', '~> 3.0'
|
12
|
+
gem 'terminal-table'
|
13
|
+
|
14
|
+
group :development do
|
15
|
+
gem 'rubocop'
|
16
|
+
end
|
17
|
+
|
18
|
+
group :test do
|
19
|
+
gem 'factory_bot'
|
20
|
+
gem 'simplecov'
|
21
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
smart_parser (0.0.1)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
activesupport (7.0.2.2)
|
10
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
11
|
+
i18n (>= 1.6, < 2)
|
12
|
+
minitest (>= 5.1)
|
13
|
+
tzinfo (~> 2.0)
|
14
|
+
ast (2.4.2)
|
15
|
+
concurrent-ruby (1.1.9)
|
16
|
+
diff-lcs (1.5.0)
|
17
|
+
docile (1.4.0)
|
18
|
+
factory_bot (6.2.0)
|
19
|
+
activesupport (>= 5.0.0)
|
20
|
+
i18n (1.10.0)
|
21
|
+
concurrent-ruby (~> 1.0)
|
22
|
+
minitest (5.15.0)
|
23
|
+
parallel (1.21.0)
|
24
|
+
parser (3.1.1.0)
|
25
|
+
ast (~> 2.4.1)
|
26
|
+
rainbow (3.1.1)
|
27
|
+
rake (12.3.3)
|
28
|
+
regexp_parser (2.2.1)
|
29
|
+
rexml (3.2.5)
|
30
|
+
rspec (3.11.0)
|
31
|
+
rspec-core (~> 3.11.0)
|
32
|
+
rspec-expectations (~> 3.11.0)
|
33
|
+
rspec-mocks (~> 3.11.0)
|
34
|
+
rspec-core (3.11.0)
|
35
|
+
rspec-support (~> 3.11.0)
|
36
|
+
rspec-expectations (3.11.0)
|
37
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
38
|
+
rspec-support (~> 3.11.0)
|
39
|
+
rspec-mocks (3.11.0)
|
40
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
41
|
+
rspec-support (~> 3.11.0)
|
42
|
+
rspec-support (3.11.0)
|
43
|
+
rubocop (1.25.1)
|
44
|
+
parallel (~> 1.10)
|
45
|
+
parser (>= 3.1.0.0)
|
46
|
+
rainbow (>= 2.2.2, < 4.0)
|
47
|
+
regexp_parser (>= 1.8, < 3.0)
|
48
|
+
rexml
|
49
|
+
rubocop-ast (>= 1.15.1, < 2.0)
|
50
|
+
ruby-progressbar (~> 1.7)
|
51
|
+
unicode-display_width (>= 1.4.0, < 3.0)
|
52
|
+
rubocop-ast (1.16.0)
|
53
|
+
parser (>= 3.1.1.0)
|
54
|
+
ruby-progressbar (1.11.0)
|
55
|
+
simplecov (0.21.2)
|
56
|
+
docile (~> 1.1)
|
57
|
+
simplecov-html (~> 0.11)
|
58
|
+
simplecov_json_formatter (~> 0.1)
|
59
|
+
simplecov-html (0.12.3)
|
60
|
+
simplecov_json_formatter (0.1.4)
|
61
|
+
terminal-table (3.0.2)
|
62
|
+
unicode-display_width (>= 1.1.1, < 3)
|
63
|
+
tzinfo (2.0.4)
|
64
|
+
concurrent-ruby (~> 1.0)
|
65
|
+
unicode-display_width (2.1.0)
|
66
|
+
|
67
|
+
PLATFORMS
|
68
|
+
ruby
|
69
|
+
|
70
|
+
DEPENDENCIES
|
71
|
+
factory_bot
|
72
|
+
rake (~> 12.0)
|
73
|
+
rspec (~> 3.0)
|
74
|
+
rubocop
|
75
|
+
simplecov
|
76
|
+
smart_parser!
|
77
|
+
terminal-table
|
78
|
+
|
79
|
+
RUBY VERSION
|
80
|
+
ruby 2.7.2p137
|
81
|
+
|
82
|
+
BUNDLED WITH
|
83
|
+
2.1.4
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2022 Joffily Ferreira
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
# SmartParser
|
2
|
+
|
3
|
+
This project aims to provide a _smart_ and easy way to parse any webserver log and print its results in a human-friendly format. It was designed with flexibility in mind. It makes it simple to change parts like Counters and Presenters.
|
4
|
+
|
5
|
+
## Design decisions
|
6
|
+
|
7
|
+
```ascii
|
8
|
+
┌────────────────────────────┐
|
9
|
+
│ ├────┐
|
10
|
+
┌──────┤ The User │ │
|
11
|
+
│ │ │ │
|
12
|
+
│ └─────────────▲──────────────┘ │
|
13
|
+
│ │ │
|
14
|
+
│ │ │
|
15
|
+
│ │ │
|
16
|
+
│ ┌──────────┴─────────┐ │
|
17
|
+
│ │ │ │
|
18
|
+
│ │ │ │
|
19
|
+
│ ┌─►│ Parser │◄─┐ │
|
20
|
+
│ │ │ │ │ │
|
21
|
+
│ │ │ │ │ │
|
22
|
+
│ │ └────────────────────┘ │ │
|
23
|
+
│ │ │ │
|
24
|
+
│ │ │ │
|
25
|
+
│ │ │ │
|
26
|
+
│ │ │ │
|
27
|
+
▼ │ │ ▼
|
28
|
+
┌────────┴───────────┐ ┌─────────┴──────────┐
|
29
|
+
│ │ │ │
|
30
|
+
│ │ │ │
|
31
|
+
│ Counter │ │ Presenter │
|
32
|
+
│ │ │ │
|
33
|
+
│ │ │ │
|
34
|
+
└────────────────────┘ └────────────────────┘
|
35
|
+
|
36
|
+
```
|
37
|
+
|
38
|
+
The above diagram shows how the `Parser` can be used by the user. The `Parser` class requires two moving parts: A list of `Counter` and a `Presenter`. By default, the gem has two counters (`MostVisitedCounter` and `UniqVisitorsCounter`), and many presenters see [here](https://github.com/joffilyfe/smart_parser/tree/main/lib/smart_parser/presenters).
|
39
|
+
|
40
|
+
This design allows the user to change any required parts and create their parts. It's easier to create a new Counter or even a new Presenter, just following the signature for them.
|
41
|
+
|
42
|
+
The default counters use a `Hash` as a container for the parsed lines, it allows us to access the paths with an O(1) access time. Also, the output of counters returns the sorted array with results, it uses the Ruby Quicksort algorithm which in the worst case will do the sort with O(n^2) but on the avg, it sorts using the O(n log n) [not too bad].
|
43
|
+
|
44
|
+
Using the `Presenter` not coupled with the `Parser` or even the `Counter` allows us to implement new parsers easily, it should not take more than five minutes.
|
45
|
+
|
46
|
+
## Installation
|
47
|
+
|
48
|
+
To use it in your application you should add this line to your Gemfile:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
gem 'smart_parser'
|
52
|
+
```
|
53
|
+
|
54
|
+
And then execute:
|
55
|
+
|
56
|
+
```bash
|
57
|
+
$ bundle install
|
58
|
+
```
|
59
|
+
|
60
|
+
Or, you can install it by yourself running this command:
|
61
|
+
|
62
|
+
```bash
|
63
|
+
$ gem install smart_parser
|
64
|
+
```
|
65
|
+
|
66
|
+
## Usage
|
67
|
+
|
68
|
+
You have two ways to use this gem:
|
69
|
+
1. As binary directly in your $PATH
|
70
|
+
2. As library loaded into your project
|
71
|
+
|
72
|
+
### Using as binary
|
73
|
+
|
74
|
+
Consider that you already installed the gem using the `gem install` method and installed it on your `lib` of the Ruby.
|
75
|
+
|
76
|
+
So, you can just run the following command:
|
77
|
+
|
78
|
+
```shell
|
79
|
+
$ sparser webserver.log
|
80
|
+
```
|
81
|
+
|
82
|
+
It includes a binary that has all Counters and Presenters and also includes additional options, to check run:
|
83
|
+
|
84
|
+
```shell
|
85
|
+
$ sparser -h
|
86
|
+
```
|
87
|
+
|
88
|
+
*Note*: You should take a look at the variety of `Presenters`. Try the `JSONL` for instance.
|
89
|
+
|
90
|
+
### Using as library
|
91
|
+
|
92
|
+
This is the second way to use the gem. You can just require (`require 'smart_parser'`) and starts using the classes like:
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
parser = SmartParser::Parser.new(lines: ['/home 127.0.01'])
|
96
|
+
```
|
97
|
+
|
98
|
+
To have more details about this use, please check the code of the binary included in the gem.
|
99
|
+
|
100
|
+
## Contributing
|
101
|
+
|
102
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/joffilyfe/smart_parser.
|
103
|
+
|
104
|
+
|
105
|
+
## License
|
106
|
+
|
107
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/exe/sparser
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
require 'optparse'
|
6
|
+
|
7
|
+
if $PROGRAM_NAME == __FILE__
|
8
|
+
require_relative '../lib/smart_parser'
|
9
|
+
else
|
10
|
+
require 'smart_parser'
|
11
|
+
end
|
12
|
+
require 'benchmark'
|
13
|
+
|
14
|
+
USAGE = "Usage: sparser webserver.log [options]"
|
15
|
+
|
16
|
+
def parse_options
|
17
|
+
counters = {
|
18
|
+
mostvisited: SmartParser::Counters::MostVisitedCounter,
|
19
|
+
uniqvisitors: SmartParser::Counters::UniqVisitorsCounter
|
20
|
+
}
|
21
|
+
presenters = {
|
22
|
+
table: SmartParser::Presenters::TablePresenter,
|
23
|
+
line: SmartParser::Presenters::LinePresenter,
|
24
|
+
jsonl: SmartParser::Presenters::JSONLPresenter,
|
25
|
+
none: nil
|
26
|
+
}
|
27
|
+
options = {
|
28
|
+
counters: [SmartParser::Counters::MostVisitedCounter, SmartParser::Counters::UniqVisitorsCounter],
|
29
|
+
presenter: SmartParser::Presenters::TablePresenter,
|
30
|
+
benchmark: false
|
31
|
+
}
|
32
|
+
|
33
|
+
OptionParser.new do |opts|
|
34
|
+
opts.banner = USAGE
|
35
|
+
opts.on('-c', '--use c1,c2', Array, "Select available counters: (#{counters.keys.join(', ')})") do |selected|
|
36
|
+
unless selected.empty?
|
37
|
+
options[:counters] = selected.select { |c| counters.keys.include?(c.to_sym) }
|
38
|
+
options[:counters] = options[:counters].map { |c| counters[c.to_sym] }.flatten
|
39
|
+
end
|
40
|
+
end
|
41
|
+
opts.on('-p PRESENTER', '--presenter', presenters,
|
42
|
+
"Select the output presenter: (#{presenters.keys.join(', ')})") do |presenter|
|
43
|
+
options[:presenter] = presenter
|
44
|
+
end
|
45
|
+
opts.on('-b', '--benchmark', 'Print the benchmark for the script') do |bench|
|
46
|
+
options[:benchmark] = bench
|
47
|
+
end
|
48
|
+
end.parse!
|
49
|
+
|
50
|
+
options
|
51
|
+
end
|
52
|
+
|
53
|
+
def main
|
54
|
+
options = parse_options
|
55
|
+
logfile = File.open(ARGV[0] || abort(USAGE))
|
56
|
+
|
57
|
+
parser = SmartParser::Parser.new(
|
58
|
+
lines: logfile.readlines,
|
59
|
+
counters: options[:counters].map(&:new),
|
60
|
+
presenter: options[:presenter]
|
61
|
+
)
|
62
|
+
|
63
|
+
time = Benchmark.measure do
|
64
|
+
parser.parse!
|
65
|
+
end
|
66
|
+
|
67
|
+
parser.present
|
68
|
+
puts time if options[:benchmark]
|
69
|
+
rescue Errno::ENOENT
|
70
|
+
abort("File '#{ARGV[0]}' not found, please check if the path exists.")
|
71
|
+
rescue Errno::EISDIR
|
72
|
+
abort("The path '#{ARGV[0]}' is a directory, please use files only.")
|
73
|
+
rescue OptionParser::InvalidArgument, OptionParser::MissingArgument => e
|
74
|
+
abort(e.message)
|
75
|
+
end
|
76
|
+
|
77
|
+
main
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SmartParser
|
4
|
+
module Counters
|
5
|
+
# This is a super class which difines what other counters
|
6
|
+
# must implements. Every counter which inherance from this
|
7
|
+
# class will use the Hash as data container to store information
|
8
|
+
# about parsed lines.
|
9
|
+
#
|
10
|
+
# Every counter must implements the `to_a` method and it should
|
11
|
+
# return an array ordered from the most relevant to the least relevant.
|
12
|
+
#
|
13
|
+
class Counter
|
14
|
+
def initialize(container: {}, line_regex: SmartParser::LINE_REGEX)
|
15
|
+
@container = container
|
16
|
+
@line_regex = line_regex
|
17
|
+
end
|
18
|
+
|
19
|
+
def count(line)
|
20
|
+
raise NotImplementedError
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns an Array containing Arrays with two positions inside
|
24
|
+
def to_a
|
25
|
+
raise NotImplementedError
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns the description for the Counter
|
29
|
+
#
|
30
|
+
# @returns String
|
31
|
+
def description
|
32
|
+
raise NotImplementedError
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'counter'
|
4
|
+
|
5
|
+
module SmartParser
|
6
|
+
module Counters
|
7
|
+
# Count the most visited path
|
8
|
+
class MostVisitedCounter < Counter
|
9
|
+
# Parse the received line using the @line_regex and counts
|
10
|
+
# how many times the parsed `path` was received.
|
11
|
+
#
|
12
|
+
# @returns true - when the line was correctly parsed and counted
|
13
|
+
# @returns false - when the line does not matches with the line regex
|
14
|
+
def count(line)
|
15
|
+
match = @line_regex.match(line.strip)
|
16
|
+
|
17
|
+
return false if match.nil?
|
18
|
+
|
19
|
+
path = match[:path]
|
20
|
+
|
21
|
+
if @container[path].nil?
|
22
|
+
@container[path] = 1
|
23
|
+
else
|
24
|
+
@container[path] += 1
|
25
|
+
end
|
26
|
+
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns an Array ordered by the most accessed path to the least accessed one
|
31
|
+
#
|
32
|
+
# @returns Array
|
33
|
+
def to_a
|
34
|
+
@container.each.sort_by { |_k, v| v }.reverse.to_a
|
35
|
+
end
|
36
|
+
|
37
|
+
def description
|
38
|
+
'Most visited'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
require_relative 'counter'
|
5
|
+
|
6
|
+
module SmartParser
|
7
|
+
module Counters
|
8
|
+
# Count the most visited path considering only uniq visitors
|
9
|
+
class UniqVisitorsCounter < Counter
|
10
|
+
# Parse the received line using the @line_regex and counts
|
11
|
+
# how many times the parsed `path` was received considering only unique
|
12
|
+
# visits.
|
13
|
+
#
|
14
|
+
# @returns true - when the line was correctly parsed and counted
|
15
|
+
# @returns false - when the line does not matches with the line regex
|
16
|
+
def count(line)
|
17
|
+
match = @line_regex.match(line.strip)
|
18
|
+
|
19
|
+
return false if match.nil?
|
20
|
+
|
21
|
+
path = match[:path]
|
22
|
+
|
23
|
+
@container[path] ||= Set.new
|
24
|
+
@container[path] << match[:address]
|
25
|
+
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns an Array ordered by the most accessed path to the least accessed one
|
30
|
+
#
|
31
|
+
# @returns Array
|
32
|
+
def to_a
|
33
|
+
@container.each.sort_by { |_k, v| v.size }.map { |k, v| [k, v.size] }.reverse
|
34
|
+
end
|
35
|
+
|
36
|
+
def description
|
37
|
+
'Unique visitors'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module SmartParser
|
6
|
+
module Presenters
|
7
|
+
# The JSONLPresenter implements a way to represent the output of the
|
8
|
+
# Counter.to_a as JSONL.
|
9
|
+
class JSONLPresenter
|
10
|
+
class << self
|
11
|
+
def present(rows, description)
|
12
|
+
output = String.new
|
13
|
+
|
14
|
+
rows.each do |line|
|
15
|
+
json = JSON.dump({ path: line[0], visits: line[1], description: description })
|
16
|
+
output << "#{json}\n"
|
17
|
+
end
|
18
|
+
|
19
|
+
output
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SmartParser
|
4
|
+
module Presenters
|
5
|
+
# The LinePresenter implements a way to represent the output of the
|
6
|
+
# Counter.to_a as a simple lines showing the number of visits.
|
7
|
+
# Every path + visits will be treat as a line for the output.
|
8
|
+
class LinePresenter
|
9
|
+
class << self
|
10
|
+
def present(rows, description)
|
11
|
+
output = String.new("#{description}\n")
|
12
|
+
|
13
|
+
rows.each do |line|
|
14
|
+
output << "#{line[0]} #{line[1]} visits\n"
|
15
|
+
end
|
16
|
+
|
17
|
+
output
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'terminal-table'
|
4
|
+
|
5
|
+
module SmartParser
|
6
|
+
module Presenters
|
7
|
+
# The TablePresenter implements a way to represent the output of the
|
8
|
+
# Counter.to_a using a Table compatible with terminals.
|
9
|
+
class TablePresenter
|
10
|
+
class << self
|
11
|
+
def present(rows, description)
|
12
|
+
Terminal::Table.new(title: description, headings: %w[Path Count], rows: rows).to_s
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/smart_parser.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'smart_parser/version'
|
4
|
+
require_relative 'smart_parser/counters/counter'
|
5
|
+
require_relative 'smart_parser/counters/most_visited_counter'
|
6
|
+
require_relative 'smart_parser/counters/uniq_visitors_counter'
|
7
|
+
require_relative 'smart_parser/presenters/line_presenter'
|
8
|
+
require_relative 'smart_parser/presenters/table_presenter'
|
9
|
+
require_relative 'smart_parser/presenters/jsonl_presenter'
|
10
|
+
|
11
|
+
module SmartParser
|
12
|
+
# Regex to search for data separeted by a space
|
13
|
+
LINE_REGEX = /(?<path>.*)\s+(?<address>.*)$/.freeze
|
14
|
+
|
15
|
+
# By defaul the MostVisitedCounter and UniqVisitorsCounter
|
16
|
+
# will be used to parse lines.
|
17
|
+
DEFAULT_COUNTERS = [
|
18
|
+
Counters::MostVisitedCounter,
|
19
|
+
Counters::UniqVisitorsCounter
|
20
|
+
].freeze
|
21
|
+
|
22
|
+
# By default the LinePresenter will be used to display
|
23
|
+
# the results of the Parser.
|
24
|
+
DEFAULT_PRESENTER = Presenters::LinePresenter
|
25
|
+
|
26
|
+
# The Parser class join every piece of the gem and expose the
|
27
|
+
# results of the Counters and Presenters selected during its
|
28
|
+
# initialization.
|
29
|
+
#
|
30
|
+
# During the construction of a new Parse instance you must
|
31
|
+
# inject the lines that you want to parse. Also, you can select
|
32
|
+
# your own Counter and your own Presenter.
|
33
|
+
#
|
34
|
+
# ==== Attributes
|
35
|
+
# +lines+ - An Array containing strings to be parsed
|
36
|
+
# +counters+ - An Array containing zero or many Counter's.
|
37
|
+
# +presenter+ - A Presenter which will displays the parser result.
|
38
|
+
#
|
39
|
+
# ==== Examples
|
40
|
+
# # Creates a new Parser with only one Counter and with JSONLPresenter
|
41
|
+
# p = Parser.new(
|
42
|
+
# lines: ['/a 127.8.8.1'],
|
43
|
+
# counters: [SmartParser::Counters::UniqVisitorsCounter]
|
44
|
+
# presenter: SmartParser::Presenters::JSONLPresenter
|
45
|
+
# )
|
46
|
+
# # Now you should parse! and show your results
|
47
|
+
# p.parse!
|
48
|
+
# p.present
|
49
|
+
#
|
50
|
+
class Parser
|
51
|
+
attr_reader :lines
|
52
|
+
attr_accessor :presenter
|
53
|
+
|
54
|
+
def initialize(lines: [], counters: DEFAULT_COUNTERS, presenter: DEFAULT_PRESENTER)
|
55
|
+
@lines = lines
|
56
|
+
@counters = counters
|
57
|
+
@presenter = presenter
|
58
|
+
@parsed = false
|
59
|
+
end
|
60
|
+
|
61
|
+
# Verify all @lines and uses the defined counters to generate
|
62
|
+
# metrics about the log lines. It runs only once.
|
63
|
+
# If lines were parsed successfully it will returns true
|
64
|
+
# otherwise false.
|
65
|
+
#
|
66
|
+
# @returns bool
|
67
|
+
def parse!
|
68
|
+
return false if @counters.empty? || @lines.empty? || parsed?
|
69
|
+
|
70
|
+
@lines.each do |line|
|
71
|
+
@counters.each do |counter|
|
72
|
+
counter.count(line.strip)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
parsed!
|
77
|
+
|
78
|
+
true
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns an Array containing data for every selected Counter.
|
82
|
+
#
|
83
|
+
# returns @array
|
84
|
+
def counters_results
|
85
|
+
@counters.map { |counter| { description: counter.description, data: counter.to_a } }
|
86
|
+
end
|
87
|
+
|
88
|
+
# Shows the parsed result using the selected Presenter.
|
89
|
+
# Note: the result will be displayed using the stdout
|
90
|
+
#
|
91
|
+
# @returns nil
|
92
|
+
def present
|
93
|
+
return if @presenter.nil?
|
94
|
+
|
95
|
+
@counters.each do |counter|
|
96
|
+
puts @presenter.present(counter.to_a, counter.description)
|
97
|
+
puts "\n"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Checks if the parser already ran.
|
102
|
+
#
|
103
|
+
# @returns bool
|
104
|
+
def parsed?
|
105
|
+
@parsed
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def parsed!
|
111
|
+
@parsed = true
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lib/smart_parser/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'smart_parser'
|
7
|
+
spec.version = SmartParser::VERSION
|
8
|
+
spec.authors = ['Joffily Ferreira']
|
9
|
+
spec.email = ['joffilyferreira@gmail.com']
|
10
|
+
|
11
|
+
spec.summary = 'Inspect yours webserver logs'
|
12
|
+
spec.description = 'This powerfull webserver log parser will help you count access through your routes'
|
13
|
+
spec.homepage = 'https://github.com/joffilyfe/smart_parser'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.7.2')
|
16
|
+
|
17
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
18
|
+
spec.metadata['source_code_uri'] = spec.homepage
|
19
|
+
spec.metadata['changelog_uri'] = spec.homepage
|
20
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
21
|
+
|
22
|
+
# Specify which files should be added to the gem when it is released.
|
23
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
24
|
+
# spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
25
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
26
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
27
|
+
end
|
28
|
+
spec.bindir = 'exe'
|
29
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
30
|
+
spec.require_paths = ['lib']
|
31
|
+
end
|
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: smart_parser
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Joffily Ferreira
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-02-26 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: This powerfull webserver log parser will help you count access through
|
14
|
+
your routes
|
15
|
+
email:
|
16
|
+
- joffilyferreira@gmail.com
|
17
|
+
executables:
|
18
|
+
- sparser
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- ".gitignore"
|
23
|
+
- ".rspec"
|
24
|
+
- ".rubocop.yml"
|
25
|
+
- Gemfile
|
26
|
+
- Gemfile.lock
|
27
|
+
- LICENSE.txt
|
28
|
+
- README.md
|
29
|
+
- Rakefile
|
30
|
+
- exe/sparser
|
31
|
+
- lib/smart_parser.rb
|
32
|
+
- lib/smart_parser/counters/counter.rb
|
33
|
+
- lib/smart_parser/counters/most_visited_counter.rb
|
34
|
+
- lib/smart_parser/counters/uniq_visitors_counter.rb
|
35
|
+
- lib/smart_parser/presenters/jsonl_presenter.rb
|
36
|
+
- lib/smart_parser/presenters/line_presenter.rb
|
37
|
+
- lib/smart_parser/presenters/table_presenter.rb
|
38
|
+
- lib/smart_parser/version.rb
|
39
|
+
- smart_parser.gemspec
|
40
|
+
homepage: https://github.com/joffilyfe/smart_parser
|
41
|
+
licenses:
|
42
|
+
- MIT
|
43
|
+
metadata:
|
44
|
+
homepage_uri: https://github.com/joffilyfe/smart_parser
|
45
|
+
source_code_uri: https://github.com/joffilyfe/smart_parser
|
46
|
+
changelog_uri: https://github.com/joffilyfe/smart_parser
|
47
|
+
rubygems_mfa_required: 'true'
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 2.7.2
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
requirements: []
|
63
|
+
rubygems_version: 3.1.4
|
64
|
+
signing_key:
|
65
|
+
specification_version: 4
|
66
|
+
summary: Inspect yours webserver logs
|
67
|
+
test_files: []
|