rslog 0.0.9 → 0.0.15

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: 6ce0368ae8830da116ff45bb102f0156e0ff0c320f57ddb0ba133efa019b7460
4
- data.tar.gz: b12a734d292403f0dfabe560e22e170ec1f4e72d9a07227f80291d5aeb72c51b
3
+ metadata.gz: e78583883f5087e1ce4e44c7d497ce68f1d7d9b37500bfc8223b4316a7b83853
4
+ data.tar.gz: 31f56aadc5936705e58b6e1b1f85b6b141d288e5d58acb542c1ef929d0cb2727
5
5
  SHA512:
6
- metadata.gz: 277bec231152641cbd79ad42f7737f7bccf69e2abb5d804d0a976c42d6a4f353fac5761555e7a662bb7767eb6c7ad7499e2716ea9305e343b2c7b542178a3540
7
- data.tar.gz: 8b245fc399ac058b0cad266fd1684cbfa76136d8b2c02e6bc1c088441482758b54e8a5a25d00f2de50ce9889ad42a3ba02ba92ba85594ab1aaa32680e1e59896
6
+ metadata.gz: 29ebd59a174be7ff7841bf2216b8520752067bddf0a81edfe1f46ad87df25c1f9377bd742ffccd1cb9b2367810ca4bbaf5fe6970e7cb34d2b3c1ddfea480fe4c
7
+ data.tar.gz: 0adb1f65ce181f65cb3a0415d9145c39c46c282236b4f4f8da8ae765f3f4a8319b3ccb44ef2ec2f2268cdc593c549cf1b3353c315b4e01277c1af13e85c2cb47
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,24 +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/input_parser'
6
- require_relative 'rslog/extractor'
4
+ require_relative 'rslog/args_handler'
7
5
  require_relative 'rslog/validator'
8
- require_relative 'rslog/data_parser'
6
+ require_relative 'rslog/parser'
9
7
  require_relative 'rslog/presenter'
10
- require_relative 'rslog/opts'
8
+ require 'set'
11
9
 
12
- # For development
13
- # require 'pry'
10
+ module RSlog
11
+ VERSION = '0.0.15'
14
12
 
15
- class RSlog
16
- VERSION = '0.0.9'
13
+ class Main
14
+ def self.run
15
+ file_names = RSlog::ArgsHandler.handle(ARGV)
17
16
 
18
- def self.run
19
- Container.new.process_all
17
+ return unless file_names.any?
18
+
19
+ file_names.each do |file_name|
20
+ unless file_names.all?{|f| File.file?(f) }
21
+ puts "There is no file names given. Check input."
22
+ return
23
+ end
24
+
25
+ lines = IO.readlines(file_name)
26
+
27
+ RSlog::Validator.execute(lines)
28
+
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
+ end
35
+
36
+ def self.config_sets
37
+ [
38
+ { title: %(List of webpages with most page views ordered from most pages views to less page views:),
39
+ format_string: "%-20s %3d",
40
+ suffix: "visits",
41
+ calc: proc { |visits| visits.size } },
42
+
43
+ { title: "List of webpages with most unique page views also ordered:",
44
+ format_string: "%-20s %3d",
45
+ suffix: 'unique views',
46
+ calc: proc { |visits| Set.new(visits).size } },
47
+
48
+ { title: "Average visits sorted:",
49
+ suffix: 'average visits',
50
+ calc: proc { |visits| Set.new(visits).size.to_f / visits.size } }
51
+ ]
52
+ end
20
53
  end
21
54
  end
22
-
23
- # For development
24
- # RSlog.run.talk
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Class to hold data and implement processing
4
+ #
5
+ module RSlog
6
+ module ArgsHandler
7
+ def self.handle(args)
8
+ options = args.select{|el| !(el =~ /^-/).nil?}
9
+ file_names = args - options
10
+
11
+ puts "R(uby)S(imple)log statistics" if options.delete('-h') || args.empty?
12
+ puts "Version #{RSlog::VERSION}" if options.delete('-v') || args.empty?
13
+ puts "Unknown options #{options.join(', ')}" if options.any?
14
+
15
+ file_names
16
+ end
17
+ end
18
+ end
19
+
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Class to hold data and implement processing
4
+ #
5
+ module RSlog
6
+ class Parser
7
+ attr_reader :calc, :separator
8
+ attr_accessor :source, :container
9
+
10
+ def initialize(source, conf)
11
+ @calc = conf.fetch(:calc) { proc }
12
+ @separator = conf.fetch(:separator) { ' ' }
13
+ @source = source
14
+ end
15
+
16
+ def execute
17
+ Array(source)
18
+ .map{|item| item.split(separator)} #extract, returns Array
19
+ .group_by{|page_name, _visits| page_name } #group, returns Hash
20
+ .map{|page_name, visits| [page_name, calc.call(visits)] } #calculate, returns Array
21
+ .sort_by{|_page_name, visits_by_page| visits_by_page } #sort, returns Array
22
+ .reverse
23
+ end
24
+ end
25
+ end
@@ -1,53 +1,39 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'worker'
4
- require_relative 'tools/hash'
5
- require_relative 'tools/array'
6
-
7
- # Class to present result in needed format
3
+ # Class for format result according to passed 'format_string'
8
4
  #
9
- class Presenter < Worker
10
- DECORATORS = {
11
- all: {
12
- title:
13
- 'List of webpages with most page views ordered from most views to less',
14
- suffix: 'visits'
15
- },
16
- uniq: {
17
- title: 'List list of webpages with most unique page views also ordered',
18
- suffix: 'unique views'
19
- }
20
- }.freeze
21
-
22
- def execute(type, formatter)
23
- @type = type
24
- @container.add_message send("format_as_#{formatter}")
25
- self
26
- end
27
-
28
- private
29
-
30
- def title
31
- decorator[:title]
32
- end
33
-
34
- def suffix
35
- decorator[:suffix]
36
- end
37
-
38
- def decorator
39
- DECORATORS[@type]
40
- end
41
-
42
- def format_as_text
43
- [
44
- title,
45
- @container.result.to_multiline_string(suffix),
46
- separator
47
- ].join("\n")
48
- end
49
-
50
- def separator
51
- '-----------------------------'
5
+ # result is array of formatted strings
6
+ # e.g
7
+ # [ "/about 1 visits",
8
+ # "/about/2 1 visits",
9
+ # "/home 1 visits",
10
+ # "/contact 1 visits",
11
+ # "/help_page/1 1 visits" ]
12
+ #
13
+ module RSlog
14
+ class Presenter
15
+ attr_reader :title, :format_string, :suffix
16
+ attr_accessor :source
17
+
18
+ def initialize(source, conf)
19
+ @source = source
20
+ @title = conf.fetch(:title) { 'Stat Pages' }
21
+ @format_string = conf.fetch(:format_string) { "%-20s %5.2f" }
22
+ @suffix = conf.fetch(:suffix) { 'visits' }
23
+ end
24
+
25
+ def present
26
+ puts title
27
+ puts _formatted
28
+ puts
29
+ end
30
+
31
+ private
32
+
33
+ def _formatted
34
+ source.map do |row|
35
+ "#{format(format_string, *row)} #{suffix}"
36
+ end
37
+ end
52
38
  end
53
39
  end
@@ -1,30 +1,34 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'tools/array'
4
-
5
3
  # Class to validate if we have valid data in lines, for example well formatted IPs
4
+ #
5
+ # Checks if IPs are comply with regex
6
+ # Output message "Valid IPs"/"Invalid IPs"
6
7
  #
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
8
+ module RSlog
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
12
14
 
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
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
17
19
 
18
- def execute
19
- @container.add_message MESSAGES[validate].call(@container.validator)
20
- self
21
- end
20
+ def self.execute(source)
21
+ puts
22
+ puts MESSAGES[valid?(source)].call(:ip)
23
+ puts
24
+ end
22
25
 
23
- private
26
+ private
24
27
 
25
- def validate
26
- return :valid if @container.data.all? TEMPLATES[@container.validator]
28
+ def self.valid?(source)
29
+ return :valid if source.all? TEMPLATES[:ip]
27
30
 
28
- :invalid
31
+ :invalid
32
+ end
29
33
  end
30
34
  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.9
4
+ version: 0.0.15
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-01 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,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'tools/array'
4
- require_relative 'tools/hash'
5
- require_relative 'worker'
6
- require_relative 'validator'
7
-
8
- # Class to parse data from given array
9
- #
10
- class DataParser < Worker
11
- # type = :count (default) -> just count in groups
12
- # type = :uniq -> uniq entries in groups
13
- def execute(type)
14
- @container.result =
15
- @container.data
16
- .group_by_index(0)
17
- .send("count_by_groups_#{type}")
18
- .to_a
19
- .sort_by { |item| item[1] }
20
- .reverse
21
- .map { |elem_arr| elem_arr.map(&:to_s).join(' ') }
22
- self
23
- end
24
- end
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'worker'
4
-
5
- # Class for extracting data from file
6
- #
7
- class Extractor < Worker
8
- def execute
9
- @file_name = @container.file_name
10
- check
11
- self
12
- end
13
-
14
- private
15
-
16
- def check
17
- if File.zero?(@file_name)
18
- @container.add_message 'Empty file'
19
- else
20
- @container.data = File.open(@file_name, 'r').to_a
21
- @container.add_message 'Data in place'
22
- end
23
- rescue StandardError => e
24
- @container.add_error e
25
- end
26
- end
@@ -1,32 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'optparse'
4
- require_relative 'worker'
5
- require_relative 'opts'
6
-
7
- # Class to parse input filenames and args
8
- #
9
- class InputParser < Worker
10
- include Opts
11
-
12
- def execute
13
- handle_opts
14
- handle_args
15
- self
16
- end
17
-
18
- private
19
-
20
- attr_accessor :opts
21
-
22
- def handle_opts
23
- Opts.create_opts.parse!(into: @container.options)
24
- rescue OptionParser::InvalidOption => e
25
- @container.add_error e
26
- end
27
-
28
- def handle_args
29
- @container.file_name = ARGV.select { |str| str =~ /.\.log|.\.txt/ }[0] || ''
30
- @container.add_error 'No file_name provided' if @container.file_name.empty?
31
- end
32
- 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