rslog 0.0.14 → 0.0.19

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: 565f890daf1b4081d160be0695bdb10c6522d4477f293e629fd960de9dd6e6eb
4
+ data.tar.gz: b30cfe363b91deea197373d561e09529594062e659cf319230cc9f3c76e3b0dd
5
5
  SHA512:
6
- metadata.gz: 5e92c43c6e11b59e096a78e750007bd3a607c6742e1332231fb92e4ba6a7156836300f91cef763637e95ecadca52df671e76ad6963b45ec3bf744ef64a55d4f0
7
- data.tar.gz: f77065e38bb39f59fdd2b82e4ada8dc25313891075e4927fa0176ab931b846d036a56507edb94cdbf90c9958ed7e05282c63f9a933906a1bd617438c91d40384
6
+ metadata.gz: e2eae3d47dc76857e1dcd3cfc3541b9d9fed3b10ab1340783b744761b61436f550efb67adb6d01ba06ff1c478805b7754bcf08e5a0ee7cf860ce8b5fcb96bfaf
7
+ data.tar.gz: f2f5a2a8396d3a2b529c8684c77b015d9196ee2f278b18a5c0ebed510141b5b93cd2dd1889a001a9f67c0f480ac259dbfd99e5f737c6b8e1367f617c2d0f5b7e
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,53 @@
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/data_processing'
7
+ require_relative 'rslog/parser'
8
+ require_relative 'rslog/decorator'
12
9
  require_relative 'rslog/presenter'
10
+ require 'set'
13
11
 
14
- # For development
15
- # require 'pry'
12
+ module RSlog
13
+ VERSION = '0.0.19'
16
14
 
17
- class RSlog
18
- VERSION = '0.0.14'
15
+ # Module to hold main process
16
+ #
17
+ module Main
18
+ extend RSlog::ArgsHandler
19
+ extend RSlog::Validator
19
20
 
20
- def self.run
21
- Container.new.process_all
21
+ def self.run
22
+ file_names_from_args(ARGV).each do |file_name|
23
+ puts "Statistics for file #{file_name}"
24
+
25
+ lines = IO.readlines(file_name)
26
+
27
+ validate(lines)
28
+
29
+ _process(lines)
30
+ end
31
+ end
32
+
33
+ def self._process(lines)
34
+ decorator = RSlog::Decorator.new(:utf)
35
+ _config_sets.each do |conf|
36
+ data_processing = RSlog::DataProcessing.new(lines, conf)
37
+ parsed_data = RSlog::Parser.new(data_processing).parse
38
+ RSlog::Presenter.new(parsed_data, conf, decorator).present
39
+ end
40
+ end
41
+
42
+ def self._config_sets
43
+ [{ title: %(List of webpages with most page views ordered from most pages views to less page views:),
44
+ # head_titles: %w[Url Visits Average],
45
+ head_titles: %w[Url Visits],
46
+ calc: proc { |visits| visits.size } },
47
+ { title: 'List of webpages with most unique page views also ordered:',
48
+ # head_titles: %w[Url Unique\ views Average],
49
+ head_titles: %w[Url Unique\ views],
50
+ calc: proc { |visits| Set.new(visits).size } } ]
51
+ end
22
52
  end
23
53
  end
24
-
25
- # For development
26
- # RSlog.run.talk
@@ -0,0 +1,74 @@
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
+ module 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 file_names_from_args(args)
17
+ @args = args
18
+ @options = @args.select { |el| el =~ /^-/ }
19
+ if @options.any? || @args.empty?
20
+ _handle_options
21
+ return []
22
+ end
23
+
24
+ # file_names array
25
+ file_names = @args - @options
26
+ return file_names if file_names.all? { |file_name| File.file?(file_name) }
27
+
28
+ puts 'There is no file names given. Check input.'
29
+ []
30
+ end
31
+
32
+ private
33
+
34
+ def _handle_options
35
+ _show_help || _show_version || _show_unknown_options_warning
36
+ end
37
+
38
+ def _show_help
39
+ if @options.delete('-h') || @args.empty?
40
+ puts _help_message
41
+ return true
42
+ end
43
+ false
44
+ end
45
+
46
+ def _help_message
47
+ %[
48
+ R(uby)S(imple)log statistics
49
+ Usage:
50
+ > rslog filename[.log|.txt]
51
+
52
+ > rslog [-h|-v]
53
+ -h - show this info
54
+ -v - show version
55
+ ]
56
+ end
57
+
58
+ def _show_version
59
+ if @options.delete('-v')
60
+ puts " Version #{RSlog::VERSION}"
61
+ return true
62
+ end
63
+ false
64
+ end
65
+
66
+ def _show_unknown_options_warning
67
+ if @options.any?
68
+ puts " Unknown options #{@options.join(', ')}"
69
+ return true
70
+ end
71
+ false
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,50 @@
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
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+
5
+ module RSlog
6
+ # Class for load borders for table
7
+ #
8
+ class Decorator
9
+ FILE_PATH = 'lib/rslog/decorators.yml'
10
+
11
+ attr_reader :decorators
12
+
13
+ def initialize(coding = :utf)
14
+ @coding = String(coding)
15
+ @decorators = YAML.load_file(FILE_PATH)[@coding]
16
+ @decorators.each do |name, value|
17
+ instance_variable_set("@#{name}", value)
18
+ Decorator.attr_reader name.to_sym
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ ---
2
+ utf:
3
+ left_up_corner: "\u250c"
4
+ right_up_corner: "\u2510"
5
+ left_down_corner: "\u2514"
6
+ right_down_corner: "\u2518"
7
+ vertical_border: "\u2502"
8
+ horizontal_border: "\u2500"
9
+ vertical_left_border: "\u251c"
10
+ vertical_right_border: "\u2524"
11
+
12
+ str:
13
+ left_up_corner: "+"
14
+ right_up_corner: "+"
15
+ left_down_corner: "+"
16
+ right_down_corner: "+"
17
+ vertical_border: "|"
18
+ horizontal_border: "-"
19
+ vertical_left_border: "+"
20
+ vertical_right_border: "+"
21
+
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSlog
4
+ # Class to parse data
5
+ #
6
+ class Parser
7
+ attr_reader :data_processing
8
+
9
+ def initialize(data_processing)
10
+ @data_processing = data_processing
11
+ end
12
+
13
+ def parse
14
+ data_processing.extract.group.calculate.order.result
15
+ end
16
+ end
17
+ end
@@ -1,32 +1,70 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'tools/hash'
4
- require_relative 'tools/array'
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
+ def initialize(source, conf, decorator)
16
+ @source = source
17
+ @col_size = conf.fetch(:col_size, 20)
18
+ @title = conf.fetch(:title, 'Stat Pages')
19
+ @formatter = conf.fetch(:formatter, "%-#{@col_size}s")
20
+ @columns = conf.fetch(:columns, @source&.first&.size || 1)
21
+ @head_titles = conf.fetch(:head_titles, Array.new(@columns, 'title'))
22
+ @decorator = decorator || _default_decorator
23
+ end
5
24
 
6
- # Class to present result in needed format
7
- #
8
- class Presenter < Worker
9
- include Decorators
25
+ def present
26
+ puts @title
27
+ puts _top_border
28
+ puts @decorator.vertical_border + _formatted_head_titles + @decorator.vertical_border
29
+ puts _middle_border
30
+ puts _formatted_data
31
+ puts _bottom_border
32
+ puts
33
+ end
10
34
 
11
- attr_accessor :type
35
+ private
36
+
37
+ def _default_decorator
38
+ RSlog::Decorator.new(:utf)
39
+ end
12
40
 
13
- def execute(type, formatter)
14
- @type = type
15
- container.add_message send("format_as_#{formatter}")
16
- self
17
- end
41
+ def _top_border
42
+ @decorator.left_up_corner + _horisontal_line + @decorator.right_up_corner
43
+ end
18
44
 
19
- private
45
+ def _middle_border
46
+ @decorator.vertical_left_border + _horisontal_line + @decorator.vertical_right_border
47
+ end
20
48
 
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
49
+ def _bottom_border
50
+ @decorator.left_down_corner + _horisontal_line + @decorator.right_down_corner
51
+ end
52
+
53
+ def _horisontal_line
54
+ @decorator.horizontal_border * @col_size * @columns
55
+ end
56
+
57
+ def _formatted_head_titles
58
+ format(@formatter * @columns, *@head_titles)
59
+ end
28
60
 
29
- def separator
30
- '-----------------------------'
61
+ def _formatted_data
62
+ @source.map do |row|
63
+ row = row.map(&:to_s)
64
+ @decorator.vertical_border +
65
+ format(@formatter * @columns, *row).to_s +
66
+ @decorator.vertical_border
67
+ end
68
+ end
31
69
  end
32
70
  end
@@ -1,30 +1,34 @@
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 validate(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
26
+ private
24
27
 
25
- def validate
26
- return :valid if container.data.all? TEMPLATES[container.validator]
28
+ def _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.19
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-07 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,13 @@ 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/data_processing.rb
24
+ - lib/rslog/decorator.rb
25
+ - lib/rslog/decorators.yml
26
+ - lib/rslog/parser.rb
28
27
  - lib/rslog/presenter.rb
29
- - lib/rslog/tools/array.rb
30
- - lib/rslog/tools/hash.rb
31
28
  - lib/rslog/validator.rb
32
- - lib/rslog/worker.rb
33
29
  homepage: https://rubygems.org/gems/rslog
34
30
  licenses:
35
31
  - 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