conscriptor 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 34181222df46d9b305f28dacb4d0bfe5580b167149c3af54c1b7eb6b283afc10
4
+ data.tar.gz: b70506321c62b6b27a59b24fabe7686965282d3c9729ac370fe2d4c04c082103
5
+ SHA512:
6
+ metadata.gz: f1d63a3b20b4d384122eb13050d55689dc1406793bea7595a1fe501551201a2e58424c13865545fa6ec151e2239fc47689756aed0c2f75795b223035a5bed5e2
7
+ data.tar.gz: 3e69376b0f1a274f9e35d896c0b7734ee64d624a25bd656e3a436b1703db4d36780c9f66d539be85440cbfb30dec14cbd5b46ff8ba5ab53670eb8d30717dd0cc
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Jeremy Lightsmith
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # Conscriptor
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/conscriptor.svg)](https://rubygems.org/gems/conscriptor)
4
+ [![Circle](https://circleci.com/gh/jeremylightsmith/conscriptor/tree/main.svg?style=shield)](https://app.circleci.com/pipelines/github/jeremylightsmith/conscriptor?branch=main)
5
+ [![Code Climate](https://codeclimate.com/github/jeremylightsmith/conscriptor/badges/gpa.svg)](https://codeclimate.com/github/jeremylightsmith/conscriptor)
6
+
7
+ This is several tools that we have found extremely useful when writing scripts or debugging code in ruby and rails projects.
8
+
9
+ ---
10
+
11
+ - [Quick start](#quick-start)
12
+ - [Examples](#examples)
13
+ - [Support](#support)
14
+ - [License](#license)
15
+ - [Code of conduct](#code-of-conduct)
16
+ - [Contribution guide](#contribution-guide)
17
+
18
+ ## Quick start
19
+
20
+ ```
21
+ $ gem install conscriptor
22
+ ```
23
+
24
+ ```ruby
25
+ require "conscriptor"
26
+ ```
27
+
28
+ ## Examples
29
+
30
+ ```ruby
31
+ include Conscriptor
32
+
33
+ tbd...
34
+
35
+ ```
36
+
37
+ ## Support
38
+
39
+ If you want to report a bug, or have ideas, feedback or questions about the gem, [let me know via GitHub issues](https://github.com/jeremylightsmith/conscriptor/issues/new) and I will do my best to provide a helpful answer. Happy hacking!
40
+
41
+ ## License
42
+
43
+ The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
44
+
45
+ ## Code of conduct
46
+
47
+ Everyone interacting in this project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](CODE_OF_CONDUCT.md).
48
+
49
+ ## Contribution guide
50
+
51
+ Pull requests are welcome!
@@ -0,0 +1,103 @@
1
+ require_relative 'conscriptor/event_counter'
2
+ require_relative 'conscriptor/hierarchical_context_logger'
3
+ require_relative 'conscriptor/histogram'
4
+ require_relative 'conscriptor/progress_reporter'
5
+ require_relative 'conscriptor/say'
6
+
7
+ module Conscriptor
8
+ autoload :VERSION, 'conscriptor/version'
9
+ include Conscriptor::Say
10
+ include Conscriptor::Histogram
11
+
12
+ def clog
13
+ @clog ||= Conscriptor::HierarchicalContextLogger.new
14
+ end
15
+
16
+ def counts
17
+ $counts ||= Conscriptor::EventCounter.new # rubocop:disable Style/GlobalVars
18
+ end
19
+
20
+ def safe_transaction
21
+ ActiveRecord::Base.transaction do
22
+ begin
23
+ yield
24
+ say "job's done"
25
+ rescue StandardError
26
+ say 'nope'
27
+ raise
28
+ end
29
+
30
+ counts.dump
31
+ commit_or_rollback
32
+ end
33
+ end
34
+
35
+ # save an active record, even if it has errors
36
+ def save_even_with_errors(obj)
37
+ return if obj.save
38
+
39
+ counts.record_and_print('!'.yellow, "Error saving: #{obj.errors.full_messages.join(',')}")
40
+ obj.save validate: false
41
+ end
42
+
43
+ def app_backtrace(exception)
44
+ bc = ActiveSupport::BacktraceCleaner.new
45
+ bc.clean(exception.backtrace)
46
+ end
47
+
48
+ def print_error
49
+ puts $ERROR_INFO.message.red
50
+ puts app_backtrace($ERROR_INFO).map { |b| " #{b}" }.join("\n").red
51
+ end
52
+
53
+ def catch_exceptions(message='')
54
+ yield
55
+ rescue StandardError
56
+ print message.red
57
+ print_backtrace
58
+ end
59
+
60
+ def commit_or_rollback
61
+ puts "\nCommit? (y/n)"
62
+
63
+ if $stdin.gets.chomp == 'y'
64
+ puts 'Committing.'
65
+ else
66
+ puts 'Rolling Back.'
67
+ raise ActiveRecord::Rollback
68
+ end
69
+ end
70
+
71
+ def time(name)
72
+ start = Time.now
73
+ retval = nil
74
+ begin
75
+ retval = yield
76
+ ensure
77
+ ((@times ||= {})[name] ||= []) << Time.now - start
78
+ end
79
+ retval
80
+ end
81
+
82
+ def print_times
83
+ puts 'Printing Times'.bold
84
+ @times.each do |name, times|
85
+ puts " #{name} (#{times.count} times) : #{times.sum / times.count}s (avg)"
86
+ end
87
+ end
88
+
89
+ # usage:
90
+ # start_timer total: Lesson.count, report_every: 100
91
+ def start_timer(name: nil, total: nil, report_every: nil, log: nil)
92
+ @progress = Conscriptor::ProgressReporter.new(name: name, total: total, report_every: report_every, log: log)
93
+ end
94
+
95
+ def inc_timer(by: 1)
96
+ @progress.inc(by: by)
97
+ end
98
+
99
+ # lines in a file
100
+ def num_lines(file)
101
+ `wc -l < #{file}`.to_i
102
+ end
103
+ end
@@ -0,0 +1,88 @@
1
+ require_relative './histogram'
2
+ require 'logger'
3
+
4
+ module Conscriptor
5
+ class EventCounter
6
+ include Histogram
7
+
8
+ def initialize(logger=Logger.new($stdout))
9
+ @logger = logger
10
+ clear
11
+ end
12
+
13
+ def record(event)
14
+ record_many(event, 1)
15
+ end
16
+
17
+ def record_and_print(symbol, event)
18
+ record(event)
19
+ print symbol
20
+ @key[event] = symbol
21
+ end
22
+
23
+ def record_many(event, how_many)
24
+ @counts[event] ||= 0
25
+ @counts[event] += how_many
26
+ end
27
+
28
+ def [](event)
29
+ @counts[event]
30
+ end
31
+
32
+ def record_error(event, error)
33
+ (@errors[event] ||= []) << error
34
+ end
35
+
36
+ def clear(*keys)
37
+ if keys.empty?
38
+ @counts = {}
39
+ @errors = {}
40
+ else
41
+ keys.each do |key|
42
+ @counts.delete(key)
43
+ @errors.delete(key)
44
+ end
45
+ end
46
+ @key = {}
47
+ end
48
+
49
+ def empty?
50
+ @counts.empty?
51
+ end
52
+
53
+ def errors?
54
+ !@errors.empty?
55
+ end
56
+
57
+ def to_s
58
+ @counts.map { |k, v| "#{k} = #{v}" }.sort.join(', ')
59
+ end
60
+
61
+ def dump
62
+ @logger.info("\n#{@counts.sort_by { |_k, v| -v }.map { |k, v| "#{k} = #{v}" }.join("\n")}")
63
+
64
+ unless @errors.empty?
65
+ require 'colorize'
66
+
67
+ @errors.each do |event, errors|
68
+ @logger.info "Error #{event} (#{errors.count}):\n#{histogram(errors)}".red
69
+ end
70
+ end
71
+
72
+ print_key
73
+ end
74
+
75
+ def print_key
76
+ return if @key.empty?
77
+
78
+ puts "\nKey"
79
+ @key.each do |event, symbol|
80
+ puts " #{symbol} = #{event} (#{@counts[event]})"
81
+ end
82
+ end
83
+
84
+ def to_h
85
+ @counts
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,54 @@
1
+ # this will include hierarchical context ONLY when you log. so you can add parents and grandparents of a lesson,
2
+ # and only when you call "info" will those print out.
3
+ #
4
+ # Allows you to track layers of context, e.g. multiple levels of iteration schools -> conference_reports -> widgets.
5
+ #
6
+ # Historical context: there's a bunch of code in lesson importing that was managing hierarchical logging before this
7
+ # code was born, and we could probably use this here.
8
+ module Conscriptor
9
+ class HierarchicalContextLogger
10
+ attr_writer :context
11
+
12
+ def initialize
13
+ @context = []
14
+ @printed_context = []
15
+ end
16
+
17
+ # write a message with the context printed as well
18
+ def info(message)
19
+ @context.length.times do |level|
20
+ puts_at_level @context[level], level if @context[0..level] != @printed_context[0..level]
21
+ end
22
+ @printed_context = @context.dup
23
+ puts_at_level message, @context.length
24
+ end
25
+
26
+ def with_context(thing)
27
+ push_context(thing)
28
+ yield
29
+ ensure
30
+ pop_context
31
+ end
32
+
33
+ # ideally, use #with_context instead
34
+ # adds a level of context, e.g. 'school' or 'user'
35
+ def push_context(thing)
36
+ @context.push(thing)
37
+ end
38
+
39
+ # ideally, use #with_context instead
40
+ def pop_context
41
+ @context.pop
42
+ end
43
+
44
+ def pop_context_to(level:)
45
+ @context.pop while @context.length > level
46
+ end
47
+
48
+ private
49
+
50
+ def puts_at_level(message, level)
51
+ puts "#{' ' * level}#{message}"
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,22 @@
1
+ module Conscriptor
2
+ module Histogram
3
+ # Takes an array of values, groups them by identity and counts them, then produces a string output that
4
+ # can be printed.
5
+ #
6
+ # 928 Foo
7
+ # 55 Bar
8
+ # 5 Baz
9
+ def histogram(things, indent: '')
10
+ occurences = {}
11
+ things.each do |thing|
12
+ occurences[thing] = (occurences[thing] || 0) + 1
13
+ end
14
+ lines = occurences
15
+ .sort_by { |a, b| [b, a.to_s] }
16
+ .reverse
17
+ .map { |thing, count| "#{count}\t #{thing}" }
18
+
19
+ "#{indent}#{lines.join("\n#{indent}")}"
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,34 @@
1
+ require 'logger'
2
+
3
+ module Conscriptor
4
+ class ProgressReporter
5
+ attr_reader :count
6
+
7
+ def initialize(total:, name: nil, report_every: 1, logger: nil)
8
+ @name = name
9
+ @total = total
10
+ @report_every = report_every || 1
11
+ @logger = logger || Logger.new($stdout)
12
+ @count = 0
13
+ @start_time = Time.now
14
+ end
15
+
16
+ def inc(name: @name, by: 1)
17
+ @count += by
18
+
19
+ if @count % @report_every == 0 # rubocop:disable Style/GuardClause
20
+ percent_complete = 100 * @count / @total
21
+ time_spent = Time.now - @start_time
22
+ time_left = @total * time_spent / @count - time_spent
23
+
24
+ @logger.info "#{name} #{@count}/#{@total} (#{percent_complete}%)" \
25
+ " #{(time_spent / 60).round(1)}m spent," \
26
+ " #{(time_left / 60).round(1)}m to go-ish"
27
+ end
28
+ end
29
+
30
+ def done?
31
+ @count >= @total
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,9 @@
1
+ module Conscriptor
2
+ module Say
3
+ def say(what)
4
+ `say #{what}`
5
+ rescue StandardError
6
+ # ignore
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module Conscriptor
2
+ VERSION = '0.1.0'.freeze
3
+ end
metadata ADDED
@@ -0,0 +1,56 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: conscriptor
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jeremy Lightsmith
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-07-13 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email:
15
+ - jeremy.lightsmith@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - LICENSE.txt
21
+ - README.md
22
+ - lib/conscriptor.rb
23
+ - lib/conscriptor/event_counter.rb
24
+ - lib/conscriptor/hierarchical_context_logger.rb
25
+ - lib/conscriptor/histogram.rb
26
+ - lib/conscriptor/progress_reporter.rb
27
+ - lib/conscriptor/say.rb
28
+ - lib/conscriptor/version.rb
29
+ homepage: https://github.com/jeremylightsmith/conscriptor
30
+ licenses:
31
+ - MIT
32
+ metadata:
33
+ bug_tracker_uri: https://github.com/jeremylightsmith/conscriptor/issues
34
+ changelog_uri: https://github.com/jeremylightsmith/conscriptor/releases
35
+ source_code_uri: https://github.com/jeremylightsmith/conscriptor
36
+ homepage_uri: https://github.com/jeremylightsmith/conscriptor
37
+ post_install_message:
38
+ rdoc_options: []
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: 2.6.0
46
+ required_rubygems_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ requirements: []
52
+ rubygems_version: 3.1.6
53
+ signing_key:
54
+ specification_version: 4
55
+ summary: Helpful utilities for writing scripts in ruby
56
+ test_files: []