rslog 0.0.14 → 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: a458692b87d66b9b3b9159e9263172dc27b8d07a9df51a9fc336da6ff36c9388
4
- data.tar.gz: d5ecb7b45c9534739a6e7c42c03cde7a468bd634b13659a837c9750647763120
3
+ metadata.gz: e78583883f5087e1ce4e44c7d497ce68f1d7d9b37500bfc8223b4316a7b83853
4
+ data.tar.gz: 31f56aadc5936705e58b6e1b1f85b6b141d288e5d58acb542c1ef929d0cb2727
5
5
  SHA512:
6
- metadata.gz: 5e92c43c6e11b59e096a78e750007bd3a607c6742e1332231fb92e4ba6a7156836300f91cef763637e95ecadca52df671e76ad6963b45ec3bf744ef64a55d4f0
7
- data.tar.gz: f77065e38bb39f59fdd2b82e4ada8dc25313891075e4927fa0176ab931b846d036a56507edb94cdbf90c9958ed7e05282c63f9a933906a1bd617438c91d40384
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,26 +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/opts'
7
- require_relative 'rslog/input_parser'
8
- require_relative 'rslog/extractor'
4
+ require_relative 'rslog/args_handler'
9
5
  require_relative 'rslog/validator'
10
- require_relative 'rslog/data_parser'
11
- require_relative 'rslog/decorators'
6
+ require_relative 'rslog/parser'
12
7
  require_relative 'rslog/presenter'
8
+ require 'set'
13
9
 
14
- # For development
15
- # require 'pry'
10
+ module RSlog
11
+ VERSION = '0.0.15'
16
12
 
17
- class RSlog
18
- VERSION = '0.0.14'
13
+ class Main
14
+ def self.run
15
+ file_names = RSlog::ArgsHandler.handle(ARGV)
19
16
 
20
- def self.run
21
- 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
22
53
  end
23
54
  end
24
-
25
- # For development
26
- # 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,32 +1,39 @@
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
3
+ # Class for format result according to passed 'format_string'
7
4
  #
8
- class Presenter < Worker
9
- include Decorators
10
-
11
- attr_accessor :type
12
-
13
- def execute(type, formatter)
14
- @type = type
15
- container.add_message send("format_as_#{formatter}")
16
- self
17
- end
18
-
19
- private
20
-
21
- def format_as_text
22
- [
23
- Decorators.send("#{type}_title"),
24
- container.result.to_multiline_string(Decorators.send("#{type}_suffix")),
25
- separator
26
- ].join("\n")
27
- end
28
-
29
- def separator
30
- '-----------------------------'
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
31
38
  end
32
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.14
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-12 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,17 +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/decorators.rb
25
- - lib/rslog/extractor.rb
26
- - lib/rslog/input_parser.rb
27
- - lib/rslog/opts.rb
22
+ - lib/rslog/args_handler.rb
23
+ - lib/rslog/parser.rb
28
24
  - lib/rslog/presenter.rb
29
- - lib/rslog/tools/array.rb
30
- - lib/rslog/tools/hash.rb
31
25
  - lib/rslog/validator.rb
32
- - lib/rslog/worker.rb
33
26
  homepage: https://rubygems.org/gems/rslog
34
27
  licenses:
35
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 to present result in needed format
4
- #
5
- module Decorators
6
- def self.all_title
7
- 'List of webpages with most page views ordered from most views to less'
8
- end
9
- def self.all_suffix
10
- 'visits'
11
- end
12
-
13
- def self.uniq_title
14
- 'List list of webpages with most unique page views also ordered'
15
- end
16
-
17
- def self.uniq_suffix
18
- 'unique views'
19
- end
20
-
21
- def self.separator
22
- '-----------------------------'
23
- end
24
- end
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Class for extracting data from file
4
- #
5
- class Extractor < Worker
6
- def execute
7
- check
8
- self
9
- end
10
-
11
- private
12
-
13
- def file_name
14
- container.file_name
15
- end
16
-
17
- def check
18
- if File.zero?(file_name)
19
- container.add_message 'Empty file'
20
- else
21
- container.data = File.open(file_name, 'r').to_a
22
- container.add_message 'Data in place'
23
- end
24
- rescue StandardError => e
25
- container.add_error e
26
- end
27
- end
@@ -1,29 +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
- def handle_opts
20
- Opts.create_opts.parse!(into: container.options)
21
- rescue OptionParser::InvalidOption => e
22
- container.add_error e
23
- end
24
-
25
- def handle_args
26
- container.file_name = ARGV.select { |str| str =~ /.\.log|.\.txt/ }[0] || ''
27
- container.add_error 'No file_name provided' if container.file_name.empty?
28
- end
29
- end
data/lib/rslog/opts.rb DELETED
@@ -1,45 +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
- end
11
- }.freeze
12
-
13
- HELP = {
14
- descr: ['-h', '--help', 'Prints this message and exit'],
15
- action: proc do |opts|
16
- puts opts
17
- end
18
- }.freeze
19
-
20
- def self.create_opts
21
- OptionParser.new do |opts|
22
- banner(opts)
23
- help(opts)
24
- version(opts)
25
- end
26
- end
27
-
28
- def self.banner(opts)
29
- opts.banner = "Usage: #{File.basename($PROGRAM_NAME)} FILENAME"
30
- end
31
-
32
- def self.help(opts)
33
- opts.on(*Opts::VERSION[:descr]) do
34
- Opts::VERSION[:action].call
35
- exit
36
- end
37
- end
38
-
39
- def self.version(opts)
40
- opts.on(*Opts::HELP[:descr]) do
41
- Opts::HELP[:action].call(opts)
42
- exit
43
- end
44
- end
45
- 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,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Class to hold worker functionality
4
- #
5
- class Worker
6
- attr_reader :container
7
-
8
- def initialize(container)
9
- @container = container
10
- end
11
-
12
- def execute
13
- raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
14
- end
15
- end