rslog 0.0.10 → 0.0.16

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bbfdff7943771b6041ad63326ac885ea1f15734a9605d0b0ec9fc966e97cc2e5
4
- data.tar.gz: 19a16f3c7b1e23f52b3ff2f4887c39cbe144470e92b95f9503ba71879c9ef147
3
+ metadata.gz: '089ce1f53d07365d2cd5a8a132d3f016fd971aec94fb5e1d7d097b83c378d16b'
4
+ data.tar.gz: 690f6b582c62d555192f003bb9bf48f47c05c73e6144663fc5271ef263a8a6e2
5
5
  SHA512:
6
- metadata.gz: b7ac79824d646234ed125a91b5652a8974dec571d111eb343c499ef908f47128eba6b182182ab323b24064d06336781820d2dd17570828a35666ad8efee25682
7
- data.tar.gz: ee1ee56ebb29461e55572b6404ce52c9c91b2dc1584c85115f4aaa9c0ddc7c37d1a891993a2b2946a4becdda4a85d8976e6b0c6d475ff1cf898dd02aa765bbe6
6
+ metadata.gz: 2607d6b3ba40a13ecba89d214d0ef3d92d8d7b28b3b0dedf96942b01ea939efd287797ad4fe56e99c6986eac2dcbe53feca954abe41b69f14633ed9a79249f82
7
+ data.tar.gz: f68723a370022e15d2df26516b60725cad3b20d60a3c59bc3d7c2fc55f4146d874cbd123b1900896f7cc5eeab9dbff3af027241a3e8d57fc1f41c734283c25c8
data/bin/rslog CHANGED
@@ -2,4 +2,4 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require_relative '../lib/rslog'
5
- RSlog.run
5
+ RSlog::Main.run
data/lib/rslog.rb CHANGED
@@ -1,25 +1,54 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require_relative 'rslog/container'
5
- require_relative 'rslog/worker'
6
- require_relative 'rslog/input_parser'
7
- require_relative 'rslog/extractor'
4
+ require_relative 'rslog/args_handler'
8
5
  require_relative 'rslog/validator'
9
- require_relative 'rslog/data_parser'
6
+ require_relative 'rslog/parser'
10
7
  require_relative 'rslog/presenter'
11
- require_relative 'rslog/opts'
8
+ require 'set'
12
9
 
13
- # For development
14
- # require 'pry'
10
+ module RSlog
11
+ VERSION = '0.0.16'
15
12
 
16
- class RSlog
17
- VERSION = '0.0.10'
13
+ # Module to hold main process
14
+ #
15
+ module Main
16
+ def self.run
17
+ file_names.each do |file_name|
18
+ puts "Statistics for file #{file_name}"
18
19
 
19
- def self.run
20
- Container.new.process_all
20
+ lines = IO.readlines(file_name)
21
+
22
+ RSlog::Validator.execute(lines)
23
+
24
+ process(config_sets, lines)
25
+ end
26
+ end
27
+
28
+ def self.process(config_sets, lines)
29
+ config_sets.each do |conf|
30
+ parsed = RSlog::Parser.new(lines, conf).execute
31
+ RSlog::Presenter.new(parsed, conf).present
32
+ end
33
+ end
34
+
35
+ def self.file_names
36
+ file_names = RSlog::ArgsHandler.new(ARGV).handle
37
+ return file_names if file_names.all? { |file_name| File.file?(file_name) }
38
+
39
+ puts 'There is no file names given. Check input.'
40
+ []
41
+ end
42
+
43
+ def self.config_sets
44
+ [{ title: %(List of webpages with most page views ordered from most pages views to less page views:),
45
+ # head_titles: %w[Url Visits Average],
46
+ head_titles: %w[Url Visits],
47
+ calc: proc { |visits| visits.size } },
48
+ { title: 'List of webpages with most unique page views also ordered:',
49
+ # head_titles: %w[Url Unique\ views Average],
50
+ head_titles: %w[Url Unique\ views],
51
+ calc: proc { |visits| Set.new(visits).size } }]
52
+ end
21
53
  end
22
54
  end
23
-
24
- # For development
25
- # RSlog.run.talk
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSlog
4
+ # Class to parse options and arguments
5
+ #
6
+ # returns Array of file names
7
+ #
8
+ class ArgsHandler
9
+ attr_reader :args, :options
10
+
11
+ def initialize(args)
12
+ @args = args
13
+ @options = @args.select { |el| el =~ /^-/ }
14
+ end
15
+
16
+ def handle
17
+ if options.any? || args.empty?
18
+ _handle_options
19
+ return []
20
+ end
21
+
22
+ # file_names array
23
+ args - options
24
+ end
25
+
26
+ private
27
+
28
+ def _handle_options
29
+ _show_help || _show_version || _show_unknown_options_warning
30
+ end
31
+
32
+ def _show_help
33
+ if options.delete('-h') || args.empty?
34
+ puts _help_message
35
+ return true
36
+ end
37
+ false
38
+ end
39
+
40
+ def _help_message
41
+ %[
42
+ R(uby)S(imple)log statistics
43
+ Usage:
44
+ > rslog filename[.log|.txt]
45
+
46
+ > rslog [-h|-v]
47
+ -h - show this info
48
+ -v - show version
49
+ ]
50
+ end
51
+
52
+ def _show_version
53
+ if @options.delete('-v')
54
+ puts " Version #{RSlog::VERSION}"
55
+ return true
56
+ end
57
+ false
58
+ end
59
+
60
+ def _show_unknown_options_warning
61
+ if @options.any?
62
+ puts " Unknown options #{@options.join(', ')}"
63
+ return true
64
+ end
65
+ false
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSlog
4
+ # Class to hold data_processing methods
5
+ #
6
+ class DataProcessing
7
+ attr_reader :result
8
+
9
+ def initialize(source, conf)
10
+ @calc = conf.fetch(:calc) { proc }
11
+ @separator = conf.fetch(:separator, ' ')
12
+ @result = Array(source)
13
+ end
14
+
15
+ # extract
16
+ def extract
17
+ @result = @result.map { |item| item.split(@separator) }
18
+ self
19
+ end
20
+
21
+ # group
22
+ def group
23
+ @result = @result.group_by { |url, _visits| url }.to_a
24
+ self
25
+ end
26
+
27
+ # calculate
28
+ def calculate
29
+ # calculate visits
30
+ @result = @result.map { |url, visits| [url, @calc.call(visits)] }
31
+ # calculate average
32
+ # @result = @result.map { |url, visits_qty| [url, visits_qty, (visits_qty.to_f / _total)] }
33
+ self
34
+ end
35
+
36
+ # sort
37
+ def order
38
+ @result = @result.sort_by { |_url, visits_by_page| visits_by_page }
39
+ @result.reverse!
40
+ self
41
+ end
42
+
43
+ private
44
+
45
+ # calculate total
46
+ def _total
47
+ @result.sum { |_url, visits_qty| visits_qty }
48
+ end
49
+ end
50
+
51
+ require 'forwardable'
52
+ # Class to parse data
53
+ #
54
+ class Parser
55
+ attr_reader :data_processing
56
+
57
+ extend Forwardable
58
+ def_delegators :@data_processing, :extract, :group, :calculate, :order
59
+
60
+ def initialize(source, conf)
61
+ @data_processing = DataProcessing.new(source, conf)
62
+ end
63
+
64
+ def execute
65
+ data_processing.extract.group.calculate.order.result
66
+ end
67
+ end
68
+ end
@@ -1,52 +1,68 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'tools/hash'
4
- require_relative 'tools/array'
5
-
6
- # Class to present result in needed format
7
- #
8
- class Presenter < Worker
9
- DECORATORS = {
10
- all: {
11
- title:
12
- 'List of webpages with most page views ordered from most views to less',
13
- suffix: 'visits'
14
- },
15
- uniq: {
16
- title: 'List list of webpages with most unique page views also ordered',
17
- suffix: 'unique views'
18
- }
19
- }.freeze
20
-
21
- def execute(type, formatter)
22
- @type = type
23
- @container.add_message send("format_as_#{formatter}")
24
- self
25
- end
3
+ module RSlog
4
+ # Class for format result according to passed 'format_string'
5
+ #
6
+ # result is array of formatted strings
7
+ # e.g
8
+ # [ "/about 1 visits",
9
+ # "/about/2 1 visits",
10
+ # "/home 1 visits",
11
+ # "/contact 1 visits",
12
+ # "/help_page/1 1 visits" ]
13
+ #
14
+ class Presenter
15
+ LEFT_UP_CORNER = "\u250c"
16
+ RIGHT_UP_CORNER = "\u2510"
17
+ LEFT_DOWN_CORNER = "\u2514"
18
+ RIGHT_DOWN_CORNER = "\u2518"
19
+ VERTICAL_BORDER = "\u2502"
20
+ HORIZONTAL_BORDER = "\u2500"
21
+ VERTICAL_LEFT_BORDER = "\u251C"
22
+ VERTICAL_RIGHT_BORDER = "\u2524"
26
23
 
27
- private
24
+ def initialize(source, conf)
25
+ @source = source
26
+ @col_size = conf.fetch(:col_size, 20)
27
+ @title = conf.fetch(:title, 'Stat Pages')
28
+ @formatter = conf.fetch(:formatter, "%-#{@col_size}s")
29
+ @columns = conf.fetch(:columns, @source&.first&.size || 1)
30
+ @head_titles = conf.fetch(:head_titles, Array.new(@columns, 'title'))
31
+ end
28
32
 
29
- def title
30
- decorator[:title]
31
- end
33
+ def present
34
+ puts @title
35
+ puts _top_border
36
+ puts VERTICAL_BORDER + format(@formatter * @columns, *@head_titles) + VERTICAL_BORDER
37
+ puts _middle_border
38
+ puts _formatted_data
39
+ puts _bottom_border
40
+ puts
41
+ end
32
42
 
33
- def suffix
34
- decorator[:suffix]
35
- end
43
+ private
36
44
 
37
- def decorator
38
- DECORATORS[@type]
39
- end
45
+ def _top_border
46
+ LEFT_UP_CORNER + _horisontal_line + RIGHT_UP_CORNER
47
+ end
40
48
 
41
- def format_as_text
42
- [
43
- title,
44
- @container.result.to_multiline_string(suffix),
45
- separator
46
- ].join("\n")
47
- end
49
+ def _middle_border
50
+ VERTICAL_LEFT_BORDER + _horisontal_line + VERTICAL_RIGHT_BORDER
51
+ end
52
+
53
+ def _bottom_border
54
+ LEFT_DOWN_CORNER + _horisontal_line + RIGHT_DOWN_CORNER
55
+ end
56
+
57
+ def _horisontal_line
58
+ HORIZONTAL_BORDER * @col_size * @columns
59
+ end
48
60
 
49
- def separator
50
- '-----------------------------'
61
+ def _formatted_data
62
+ @source.map do |row|
63
+ row = row.map(&:to_s)
64
+ VERTICAL_BORDER + format(@formatter * @columns, *row).to_s + VERTICAL_BORDER
65
+ end
66
+ end
51
67
  end
52
68
  end
@@ -1,30 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'tools/array'
3
+ module RSlog
4
+ # Class to validate if we have valid data in lines, for example well formatted IPs
5
+ #
6
+ # Checks if IPs are comply with regex
7
+ # Output message "Valid IPs"/"Invalid IPs"
8
+ #
9
+ module Validator
10
+ TEMPLATES = {
11
+ # IP address regex, source https://regexr.com/38odc
12
+ ip: /\b(?:(?:2(?:[0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9])\.){3}(?:(?:2([0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9]))\b/
13
+ }.freeze
4
14
 
5
- # Class to validate if we have valid data in lines, for example well formatted IPs
6
- #
7
- class Validator < Worker
8
- TEMPLATES = {
9
- # IP address regex, source https://regexr.com/38odc
10
- ip: /\b(?:(?:2(?:[0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9])\.){3}(?:(?:2([0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9]))\b/
11
- }.freeze
15
+ MESSAGES = {
16
+ valid: proc { |validator_name| "All #{validator_name.upcase}s are valid" },
17
+ invalid: proc { |validator_name| "Some #{validator_name.upcase}s are NOT valid" }
18
+ }.freeze
12
19
 
13
- MESSAGES = {
14
- valid: proc { |validator| "All #{validator.upcase}s are valid" },
15
- invalid: proc { |validator| "Some #{validator.upcase}s are NOT valid" }
16
- }.freeze
20
+ def self.execute(source)
21
+ puts
22
+ puts MESSAGES[valid?(source)].call(:ip)
23
+ puts
24
+ end
17
25
 
18
- def execute
19
- @container.add_message MESSAGES[validate].call(@container.validator)
20
- self
21
- end
22
-
23
- private
24
-
25
- def validate
26
- return :valid if @container.data.all? TEMPLATES[@container.validator]
26
+ def self.valid?(source)
27
+ return :valid if source.all? TEMPLATES[:ip]
27
28
 
28
- :invalid
29
+ :invalid
30
+ end
29
31
  end
30
32
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rslog
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.10
4
+ version: 0.0.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrey Eremeev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-03-10 00:00:00.000000000 Z
11
+ date: 2021-06-03 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Ruby script to get overall Statistic for weblog logs!
14
14
  email: a.eremeev@outlook.com
@@ -19,16 +19,10 @@ extra_rdoc_files: []
19
19
  files:
20
20
  - bin/rslog
21
21
  - lib/rslog.rb
22
- - lib/rslog/container.rb
23
- - lib/rslog/data_parser.rb
24
- - lib/rslog/extractor.rb
25
- - lib/rslog/input_parser.rb
26
- - lib/rslog/opts.rb
22
+ - lib/rslog/args_handler.rb
23
+ - lib/rslog/parser.rb
27
24
  - lib/rslog/presenter.rb
28
- - lib/rslog/tools/array.rb
29
- - lib/rslog/tools/hash.rb
30
25
  - lib/rslog/validator.rb
31
- - lib/rslog/worker.rb
32
26
  homepage: https://rubygems.org/gems/rslog
33
27
  licenses:
34
28
  - MIT
@@ -1,58 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Class to hold data and implement processing
4
- #
5
- class Container
6
- attr_accessor(*%i[
7
- argv file_name messages errors data options validator types result
8
- ])
9
-
10
- def initialize
11
- @errors = []
12
- @messages = []
13
- @validator = :ip
14
- @types = %i[all uniq]
15
- end
16
-
17
- def file_name?
18
- !file_name.empty? && no_errors?
19
- end
20
-
21
- def data?
22
- messages.include?('Data in place') && no_errors?
23
- end
24
-
25
- def no_errors?
26
- errors.empty?
27
- end
28
-
29
- def add_message(str)
30
- messages << str
31
- end
32
-
33
- def add_error(str)
34
- errors << str
35
- end
36
-
37
- def talk
38
- puts messages.join("\n")
39
- puts errors.join("\n")
40
- self
41
- end
42
-
43
- def process_all
44
- InputParser.new(self).execute
45
- Extractor.new(self).execute if file_name?
46
- validate_parse_present if data?
47
- talk
48
- self
49
- end
50
-
51
- def validate_parse_present
52
- Validator.new(self).execute
53
- types.each do |type|
54
- DataParser.new(self).execute(type)
55
- Presenter.new(self).execute(type, :text)
56
- end
57
- end
58
- end
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'tools/array'
4
- require_relative 'tools/hash'
5
-
6
- # Class to parse data from given array
7
- #
8
- class DataParser < Worker
9
- # type = :count (default) -> just count in groups
10
- # type = :uniq -> uniq entries in groups
11
- def execute(type)
12
- @container.result =
13
- @container.data
14
- .group_by_index(0)
15
- .send("count_by_groups_#{type}")
16
- .to_a
17
- .sort_by { |item| item[1] }
18
- .reverse
19
- .map { |elem_arr| elem_arr.map(&:to_s).join(' ') }
20
- self
21
- end
22
- end
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Class for extracting data from file
4
- #
5
- class Extractor < Worker
6
- def execute
7
- @file_name = @container.file_name
8
- check
9
- self
10
- end
11
-
12
- private
13
-
14
- def check
15
- if File.zero?(@file_name)
16
- @container.add_message 'Empty file'
17
- else
18
- @container.data = File.open(@file_name, 'r').to_a
19
- @container.add_message 'Data in place'
20
- end
21
- rescue StandardError => e
22
- @container.add_error e
23
- end
24
- end
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'optparse'
4
- require_relative 'opts'
5
-
6
- # Class to parse input filenames and args
7
- #
8
- class InputParser < Worker
9
- include Opts
10
-
11
- def execute
12
- handle_opts
13
- handle_args
14
- self
15
- end
16
-
17
- private
18
-
19
- attr_accessor :opts
20
-
21
- def handle_opts
22
- Opts.create_opts.parse!(into: @container.options)
23
- rescue OptionParser::InvalidOption => e
24
- @container.add_error e
25
- end
26
-
27
- def handle_args
28
- @container.file_name = ARGV.select { |str| str =~ /.\.log|.\.txt/ }[0] || ''
29
- @container.add_error 'No file_name provided' if @container.file_name.empty?
30
- end
31
- end
data/lib/rslog/opts.rb DELETED
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Service module to give opts
4
- #
5
- module Opts
6
- VERSION = {
7
- descr: ['-v', '--version', 'Show version and exit'],
8
- action: proc do
9
- puts "#{File.basename($PROGRAM_NAME)}: #{RSlog::VERSION}"
10
- exit
11
- end
12
- }.freeze
13
-
14
- HELP = {
15
- descr: ['-h', '--help', 'Prints this message and exit'],
16
- action: proc do |opts|
17
- puts opts
18
- exit
19
- end
20
- }.freeze
21
-
22
- def self.create_opts
23
- OptionParser.new do |opts|
24
- opts.banner = "Usage: #{File.basename($PROGRAM_NAME)} FILENAME"
25
- opts.on(*Opts::VERSION[:descr]) { Opts::VERSION[:action].call }
26
- opts.on(*Opts::HELP[:descr]) { Opts::HELP[:action].call(opts) }
27
- end
28
- end
29
- end
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Monkey patch for Array class
4
- #
5
- class Array
6
- def group_by_index(index = 0)
7
- group_by { |str| str.split(' ')[index] }
8
- end
9
-
10
- def to_multiline_string(suffix = '')
11
- map { |item| "#{item} #{suffix}" }.join("\n")
12
- end
13
- end
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'set'
4
-
5
- # Monkey patch for a Hash class to make counts
6
- #
7
- class Hash
8
- def count_by_groups_all
9
- res = {}
10
- each do |group_key, arr|
11
- res[group_key] = arr.size
12
- end
13
- res
14
- end
15
-
16
- def count_by_groups_uniq
17
- res = {}
18
- each do |group_key, arr|
19
- res[group_key] = Set.new(arr).size
20
- end
21
- res
22
- end
23
- end
data/lib/rslog/worker.rb DELETED
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Class to hold worker functionality
4
- #
5
- class Worker
6
- def initialize(container)
7
- @container = container
8
- end
9
-
10
- def execute
11
- raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
12
- end
13
- end