rslog 0.0.11 → 0.0.17

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: fe6bdea7b2a57784e35b11b4ba26542288101df7e3ca4c6a0341663a584d66ae
4
- data.tar.gz: 5063db15e2245369243465af2038ee4f12d582a2ae3a6009510250a011a22dea
3
+ metadata.gz: 52bf0a8fd868cb749fcd759adfb7782a5cba4b148f15f6f52b913147dd6fa95d
4
+ data.tar.gz: a15d5b6d9ad034f6ffd962e47757ed7f3878c25aee4bace3d5d27bbe79d033b3
5
5
  SHA512:
6
- metadata.gz: 1d1bcdec66de44c7a554ecb3d4a9ba73190055c87293b0a257886b129462012fcd89961b4712ed0fccd2672c0fad5bb975d923532109066fdb168b8a9437965d
7
- data.tar.gz: 6277a462d27f2d771aa5b017a676409398c960b773728e5dfd8efc1e6bd03a7816fde053839a2f9129b84ab43589618bedb936c3266aa92cefb6fa7393faea2d
6
+ metadata.gz: 916951619a9a3949fe6c4b5d3d2f9f684d3eaf7107d5cba15162d5402009a24187a61baea8ce0dab153c2315a06045956e49bf25eb2b9aec6b1b85871f198788
7
+ data.tar.gz: 071e2abb7278a4a84aaa2cec1154a5e63c83971746d5cdacd7b32127d5c36225e43f772b22ccff342a73f24d4d4d185743e971d2c9903f5ae2d7f86031594433
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,51 @@
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/data_processing'
7
+ require_relative 'rslog/parser'
10
8
  require_relative 'rslog/presenter'
11
- require_relative 'rslog/opts'
9
+ require 'set'
12
10
 
13
- # For development
14
- # require 'pry'
11
+ module RSlog
12
+ VERSION = '0.0.17'
15
13
 
16
- class RSlog
17
- VERSION = '0.0.11'
14
+ # Module to hold main process
15
+ #
16
+ module Main
17
+ extend RSlog::ArgsHandler
18
+ extend RSlog::Validator
18
19
 
19
- def self.run
20
- Container.new.process_all
20
+ def self.run
21
+ file_names_from_args(ARGV).each do |file_name|
22
+ puts "Statistics for file #{file_name}"
23
+
24
+ lines = IO.readlines(file_name)
25
+
26
+ validate(lines)
27
+
28
+ _process(lines)
29
+ end
30
+ end
31
+
32
+ def self._process(lines)
33
+ _config_sets.each do |conf|
34
+ data_processing = RSlog::DataProcessing.new(lines, conf)
35
+ parsed_data = RSlog::Parser.new(data_processing).parse
36
+ RSlog::Presenter.new(parsed_data, conf).present
37
+ end
38
+ end
39
+
40
+ def self._config_sets
41
+ [{ title: %(List of webpages with most page views ordered from most pages views to less page views:),
42
+ # head_titles: %w[Url Visits Average],
43
+ head_titles: %w[Url Visits],
44
+ calc: proc { |visits| visits.size } },
45
+ { title: 'List of webpages with most unique page views also ordered:',
46
+ # head_titles: %w[Url Unique\ views Average],
47
+ head_titles: %w[Url Unique\ views],
48
+ calc: proc { |visits| Set.new(visits).size } }]
49
+ end
21
50
  end
22
51
  end
23
-
24
- # For development
25
- # 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,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module RSlog
6
+ # Class to parse data
7
+ #
8
+ class Parser
9
+ attr_reader :data_processing
10
+
11
+ extend Forwardable
12
+ def_delegators :@data_processing, :extract, :group, :calculate, :order
13
+
14
+ def initialize(data_processing)
15
+ @data_processing = data_processing
16
+ end
17
+
18
+ def parse
19
+ data_processing.extract.group.calculate.order.result
20
+ end
21
+ end
22
+ 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,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.11
4
+ version: 0.0.17
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