Repository files navigation

Say

Gem VersionCI Actions

Say gives you the API and the output style you already know and love from ActiveRecord::Migration#say... anywhere! Plus a few extra goodies for long-running processes like incremental progress indicators and remaining time estimation.

Installation

Add this line to your application's Gemfile:

gem"say",github: "pdobb/say"# Not published to RubyGems.

And then execute:

bundle

Compatibility

Tested MRI Ruby Versions:

  • 3.1
  • 3.2
  • 3.3
  • 3.4
  • 4.0

For Ruby 2.7 support, install say gem version 0.5.2.

gem"say",github: "pdobb/say",tag: "v0.5.2"# Not published to RubyGems.

Say has no other dependencies.

Usage

Say.<method>

Typical usage is to just call Say.<method> directly, though it is possible to include Say if you prefer. (See below.)

When called with a block, say will output both Start and Finish banners, then return the result of the block to the caller. When called without a block, say will output a string of the specified type (defaults to :success).

require"say"classDirectAccessProcessordefrunSay.("DirectAccessProcessor"){Say.("Successfully did the thing!")Say.()# Or: Say.callSay.("Debug details about this ...",:debug)Say.("Info about stuff ...",:info)Say.("Maybe look into this thing ...",:warn)Say.("Failed to do a thing ...",:error)"The Result!"}endendresult=DirectAccessProcessor.new.run=DirectAccessProcessor ========================================================
->Successfullydidthe thing!
...
>> Debugdetailsaboutthis ...
-- Infoaboutstuff ...
!¡Maybelookintothisthing ...
** Failedtodoathing ...
=Done(0.0001s) ===============================================================
# => "The Result!"

include Say

Use include Say to gain access to the instance-level say methods.

require"say"classIncludeProcessorincludeSaydefrunsay("IncludeProcessor"){say("Successfully did the thing!")saysay("Debug details about this ...",:debug)say("Info about stuff ...",:info)say("Maybe look into this thing ...",:warn)say("Failed to do a thing ...",:error)"The Result!"}endendresult=IncludeProcessor.new.run=IncludeProcessor =============================================================
-> Successfullydidthe thing!
...
>> Debugdetailsaboutthis ...
-- Infoaboutstuff ...
!¡Maybelookintothisthing ...
** Failedtodoathing ...
=Done(0.0001s) ===============================================================
# => "The Result!"

Say Types

When using Say.(<message>, <type>), the available types and output representations are:

TypeOutput Prefix
:debug" >> "
:error" ** "
:info" -- "
:success" -> "
:warn" !¡ "
Say.debug("TEST")# => " >> TEST"Say.error("TEST")# => " ** TEST"Say.info("TEST")# => " -- TEST"Say.success("TEST")# => " -> TEST"Say.warn("TEST")# => " !¡ TEST"

The default type if call is used is :success.

Say.call("TEST")# => " -> TEST"Say.("TEST")# => " -> TEST"

say(<message>, <type>) Methods

The include Say alternatives for each of the <type> calls in the previous section are:

Say.("TEST",:debug)# => " >> TEST"Say.("TEST",:error)# => " ** TEST"Say.("TEST",:info)# => " -- TEST"Say.("TEST",:success)# => " -> TEST"Say.("TEST",:warn)# => " !¡ TEST"

Say.hr (Horizontal Rule)

Use Say.hr for thin, 1-line separators + padding on top/bottom.

Say.info("Before")Say.hrSay.("After")
-- Before
--------------------------------------------------------------------------------
->After

Horizontal Rule -- Customization

The fill line, template, and length can all be customized:

Say.info("Before")Say.hr("-*",template: "%s",columns: 20)# `template` defaults to: `"\n%s\n"Say.("After")
-- Before
-*-*-*-*-*-*-*-*-*-*
->After

Template can also include book-ends:

Say.hr("-*",template: "|%s|")
|-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*|

Say.section

Use Say.section for 3-line banners to really visually split up your output into major sections.

Say.section
================================================================================
================================================================================
================================================================================
Say.section("TEST")
================================================================================
=TEST =========================================================================
================================================================================
Say.section("TEST",columns: 0)
========
=TEST=
========

Justifiers

The various banner-producing methods also support left/center/right justification. Just pass in e.g. justify: :left, justify: :center, or justify: :right. The default, if nothing is supplied, is justify: :left.

# BlockSay.("Hello, World!"){Say.("Huzzah!")}=Hello,World! ================================================================
-> Huzzah!=Done(0.0000s) ===============================================================
Say.("Hello, World!",justify: :left){Say.("Huzzah!")}=Hello,World! ================================================================
-> Huzzah!=Done(0.0000s) ===============================================================
Say.("Hello, World!",justify: :center){Say.("Huzzah!")}
================================= Hello,World!================================
-> Huzzah!
================================ Done(0.0000s) ================================
Say.("Hello, World!",justify: :right){Say.("Huzzah!")}================================================================ Hello,World!=-> Huzzah!
=============================================================== Done(0.0000s)=# BannerSay.banner("TEST")=TEST =========================================================================Say.banner("TEST",justify: :left)=TEST =========================================================================Say.banner("TEST",justify: :center)
=====================================TEST =====================================Say.banner("TEST",justify: :right)
=========================================================================TEST=# HeaderSay.header("TEST")=TEST =========================================================================Say.header("TEST",justify: :left)=TEST =========================================================================Say.header("TEST",justify: :center)
=====================================TEST =====================================Say.header("TEST",justify: :right)
=========================================================================TEST=# FooterSay.footer=Done =========================================================================Say.footer(justify: :left)=Done =========================================================================Say.footer(justify: :center)
=====================================Done =====================================Say.footer(justify: :right)
=========================================================================Done=# SectionSay.section("TEST")
================================================================================
=TEST =========================================================================
================================================================================
Say.section("TEST",justify: :left)
================================================================================
=TEST =========================================================================
================================================================================
Say.section("TEST",justify: :center)
================================================================================
=====================================TEST =====================================
================================================================================
Say.section("TEST",justify: :right)
================================================================================
=========================================================================TEST=
================================================================================

NOTE: The "line" methods will ignore justification attempts as there is no built in concept of columns for these.

Say.("TEST",justify: :right)# `justify: :right` is ignored.->TEST

Advanced Usage

All of the above examples are using the default interpolation template accessed via the Say.<method> methods. For advanced usage, one may access the Say::InterpolationTemplate directly and either use the predefined templates or specify their own.

Predefined interpolation templates

# :double_lineinterpolation_template=Say::InterpolationTemplate::Builder.double_lineinterpolation_template.inspect# => "['=', ...]{}['=', ...]"interpolation_template.interpolate# => "=="interpolation_template.left_justify(length: 20)# => "===================="# :title (the default template, if none is specified)interpolation_template=Say::InterpolationTemplate::Builder.titleinterpolation_template.inspect# => ['=', ...] {} ['=', ...]interpolation_template.interpolate("TEST")# => "= TEST ="interpolation_template.left_justify("TEST",length: 20)# => "= TEST ============="interpolation_template.center_justify("TEST",length: 20)# => "======= TEST ======="interpolation_template.right_justify("TEST",length: 20)# => "============= TEST ="# :wtfinterpolation_template=Say::InterpolationTemplate::Builder.wtfinterpolation_template.inspect# => "['?', ...] {} ['?', ...]"interpolation_template.interpolate("TEST")# => "? TEST ?"interpolation_template.left_justify("TEST",length: 20)# => "? TEST ?????????????"interpolation_template.center_justify("TEST",length: 20)# => "??????? TEST ???????"interpolation_template.right_justify("TEST",length: 20)# => "????????????? TEST ?"

Custom interpolation templates

# Example 1interpolation_template=Say::InterpolationTemplate.new(left_bookend: "╰(⇀︿⇀)つ-]═",left_fill: "-",right_fill: "-")interpolation_template.inspect# => "╰(⇀︿⇀)つ-]═['-', ...]{}['-', ...]"interpolation_template.interpolate("TEST")# => "╰(⇀︿⇀)つ-]═-TEST-"interpolation_template.left_justify("TEST",length: 40)# => "╰(⇀︿⇀)つ-]═-TEST-------------------------"interpolation_template.center_justify("TEST",length: 40)# => "╰(⇀︿⇀)つ-]═--------TEST------------------"interpolation_template.right_justify("TEST",length: 40)# => "╰(⇀︿⇀)つ-]═-------------------------TEST-"# Example 2interpolation_template=Say::InterpolationTemplate.new(left_bookend: "( •_•)O*¯",left_fill: "`·.·´",right_fill: "`·.·´",right_bookend: "¯°Q(•_• )")interpolation_template.inspect# => "( •_•)O*¯['`·.·´', ...]{}['`·.·´', ...]¯°Q(•_• )"interpolation_template.interpolate("TEST")# => "( •_•)O*¯`·.·´TEST`·.·´¯°Q(•_• )"interpolation_template.left_justify("TEST")# => "( •_•)O*¯`·.·´TEST`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.¯°Q(•_• )"interpolation_template.center_justify("TEST")# => "( •_•)O*¯`·.·´`·.·´`·.·´`·.·´`·.·`·.·´TEST`·.·´`·.·´`·.·´`·.·´`·.·´`·.·¯°Q(•_• )"interpolation_template.right_justify("TEST")# => "( •_•)O*¯`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.`·.·´TEST`·.·´¯°Q(•_• )"

Progress Tracking

Use Say.progress to track long-running processing loops on a given interval. Set the interval to receive say updates only during on-interval ticks through the loop. The default interval is 1, meaning every loop is considered on-interval.

Simple

# The default interval is 1.Say.progressdo |interval|
3.timesdo# Increment the interval's internal index by 1.interval.updateinterval.say("Test",:debug)endend=[20230604151646]Start(i=0) =================================================[20230604151646] >> Test(i=1)[20230604151646] >> Test(i=2)[20230604151646] >> Test(i=3)=Done(0.0000s) ===============================================================

Advanced

Say.progress("Progress Tracking Test",interval: 3)do |interval|
0.upto(6)do |index|
# Set the interval's internal index to the current index. This may be safer.interval.update(index)# Only "say" for on-interval ticks through the loop.interval.say("Before Update Interval.",:debug)# Optionally use a block to time a segment.interval.say("Progress Interval Block.")dosleep(0.025)# Do the work here.# Always "say", regardless of interval, in the usual way: with `Say.call`.Say.("Interval-Agnostic Update. Index: #{index}",:info)endinterval.say("After Update Interval.",:debug)endend=[20230604151646]ProgressTrackingTest(i=0) ================================
-- Interval-AgnosticUpdate.Index: 0
-- Interval-AgnosticUpdate.Index: 1
-- Interval-AgnosticUpdate.Index: 2[20230604151646] >> BeforeUpdateInterval.(i=3)=[20230604151646]ProgressIntervalBlock.(i=3) ==============================
-- Interval-AgnosticUpdate.Index: 3=Done(0.0261s) ===============================================================
[20230604151646] >> AfterUpdateInterval.(i=3)
-- Interval-AgnosticUpdate.Index: 4
-- Interval-AgnosticUpdate.Index: 5[20230604151647] >> BeforeUpdateInterval.(i=6)=[20230604151647]ProgressIntervalBlock.(i=6) ==============================
-- Interval-AgnosticUpdate.Index: 6=Done(0.0261s) ===============================================================
[20230604151647] >> AfterUpdateInterval.(i=6)=Done(0.1828s) ===============================================================

Manual

Internally, calling say on a Say::Progress::Interval object uses Say.progress_line to output the given message and index indicator. You may do the same even without an Interval object.

# Given a message. (The default Type is :info.)Say.progress_line("TEST",index: 3)# => [20230604151647] -- TEST (i=3)Say.progress_line("TEST",:success,index: 3)# => [20230604151647] -> TEST (i=3)# Given no message.Say.progress_line(index: 3)# => [20230604151647] ... (i=3)

Namespace Pollution

If you choose to include Say then your class will gain the following instance methods:

  • say
  • say_banner
  • say_footer
  • say_header
  • say_line
  • say_progress
  • say_progress_line
  • say_section
  • say_with_block

... though you probably really only need say, and sometimes: say_progress and/or say_progress_line.

classWithIncludeincludeSayendclassWithoutIncludeendadded_class_methods=WithInclude.methods - WithoutInclude.methodsSay.("Class methods added by `include Say`: #{added_class_methods}")
-- Classmethodsaddedby`include Say`: []added_instance_methods=(WithInclude.new.methods - WithoutInclude.new.methods).sort!Say.("Instance methods added by `include Say`: #{added_instance_methods}")->Instancemethodsaddedby`include Say`: [:say,:say_banner,:say_footer,:say_header,:say_line,:say_progress,:say_progress_line,:say_section,:say_with_block]

Integration

iTerm2

The standardized nature of Say's logging methods lends itself well to highlighting output types using iTerm2's Text Highlighting Triggers. To set this up, go to Settings for iTerm2 -> Profiles -> Advanced -> Triggers section: "Edit" Button.

iTerm2 Triggers Setup

For more help, see iTerm's documentation on Triggers.

The regular expressions and HEX codes used in the screenshot are:

(?<=^ )->(?= )# Text HEX: #0ae400 Background HEX: transparent(?<=^ )>>(?= )# Text HEX: #ff6500 Background HEX: transparent(?<=^ )--(?= )# Text HEX: #ffffff Background HEX: #666666(?<=^ )!¡(?= )# Text HEX: #ffff00 Background HEX: transparent.*\*{2,}.* # Text HEX: #ffffff Background HEX: #ff0000

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake test to run the tests. Or, run rake to run the tests plus linters as well as yard (to confirm proper YARD documentation practices). You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install.

Testing

To test this gem:

rake

Linters

rubocop
reek
npx prettier . --check
npx prettier . --write

Releases

To release a new version of this gem to RubyGems:

  1. Update the version number in version.rb
  2. Update CHANGELOG.md
  3. Run bundle to update Gemfile.lock with the latest version info
  4. Commit the changes. e.g. Bump to vX.Y.Z
  5. Run rake release, which will create a git tag for the version, push git commits and the created tag, and then attempt to push the .gem file to rubygems.org.
  • NOTE: This will fail, as there is already a say gem and this one is not it. This gem must be installed form github source.

Documentation

YARD documentation can be generated and viewed live:

  1. Install YARD: gem install yard
  2. Run the YARD server: yard server --reload
  3. Open the live documentation site: open http://localhost:8808

While the YARD server is running, documentation in the live site will be auto-updated on source code save (and site reload).

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/pdobb/say. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the Say project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.

About

Say provides logging in the style of ActiveRecord::Migration#say... anywhere!

Resources

Code of conduct

Stars

2 stars

Watchers

1 watching

Forks

Releases

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

Say

Gem VersionCI Actions

Say gives you the API and the output style you already know and love from ActiveRecord::Migration#say... anywhere! Plus a few extra goodies for long-running processes like incremental progress indicators and remaining time estimation.

Installation

Add this line to your application's Gemfile:

gem"say",github: "pdobb/say"# Not published to RubyGems.

And then execute:

bundle

Compatibility

Tested MRI Ruby Versions:

  • 3.1
  • 3.2
  • 3.3
  • 3.4
  • 4.0

For Ruby 2.7 support, install say gem version 0.5.2.

gem"say",github: "pdobb/say",tag: "v0.5.2"# Not published to RubyGems.

Say has no other dependencies.

Usage

Say.<method>

Typical usage is to just call Say.<method> directly, though it is possible to include Say if you prefer. (See below.)

When called with a block, say will output both Start and Finish banners, then return the result of the block to the caller. When called without a block, say will output a string of the specified type (defaults to :success).

require"say"classDirectAccessProcessordefrunSay.("DirectAccessProcessor"){Say.("Successfully did the thing!")Say.()# Or: Say.callSay.("Debug details about this ...",:debug)Say.("Info about stuff ...",:info)Say.("Maybe look into this thing ...",:warn)Say.("Failed to do a thing ...",:error)"The Result!"}endendresult=DirectAccessProcessor.new.run=DirectAccessProcessor ========================================================
->Successfullydidthe thing!
...
>> Debugdetailsaboutthis ...
-- Infoaboutstuff ...
!¡Maybelookintothisthing ...
** Failedtodoathing ...
=Done(0.0001s) ===============================================================
# => "The Result!"

include Say

Use include Say to gain access to the instance-level say methods.

require"say"classIncludeProcessorincludeSaydefrunsay("IncludeProcessor"){say("Successfully did the thing!")saysay("Debug details about this ...",:debug)say("Info about stuff ...",:info)say("Maybe look into this thing ...",:warn)say("Failed to do a thing ...",:error)"The Result!"}endendresult=IncludeProcessor.new.run=IncludeProcessor =============================================================
-> Successfullydidthe thing!
...
>> Debugdetailsaboutthis ...
-- Infoaboutstuff ...
!¡Maybelookintothisthing ...
** Failedtodoathing ...
=Done(0.0001s) ===============================================================
# => "The Result!"

Say Types

When using Say.(<message>, <type>), the available types and output representations are:

TypeOutput Prefix
:debug" >> "
:error" ** "
:info" -- "
:success" -> "
:warn" !¡ "
Say.debug("TEST")# => " >> TEST"Say.error("TEST")# => " ** TEST"Say.info("TEST")# => " -- TEST"Say.success("TEST")# => " -> TEST"Say.warn("TEST")# => " !¡ TEST"

The default type if call is used is :success.

Say.call("TEST")# => " -> TEST"Say.("TEST")# => " -> TEST"

say(<message>, <type>) Methods

The include Say alternatives for each of the <type> calls in the previous section are:

Say.("TEST",:debug)# => " >> TEST"Say.("TEST",:error)# => " ** TEST"Say.("TEST",:info)# => " -- TEST"Say.("TEST",:success)# => " -> TEST"Say.("TEST",:warn)# => " !¡ TEST"

Say.hr (Horizontal Rule)

Use Say.hr for thin, 1-line separators + padding on top/bottom.

Say.info("Before")Say.hrSay.("After")
-- Before
--------------------------------------------------------------------------------
->After

Horizontal Rule -- Customization

The fill line, template, and length can all be customized:

Say.info("Before")Say.hr("-*",template: "%s",columns: 20)# `template` defaults to: `"\n%s\n"Say.("After")
-- Before
-*-*-*-*-*-*-*-*-*-*
->After

Template can also include book-ends:

Say.hr("-*",template: "|%s|")
|-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*|

Say.section

Use Say.section for 3-line banners to really visually split up your output into major sections.

Say.section
================================================================================
================================================================================
================================================================================
Say.section("TEST")
================================================================================
=TEST =========================================================================
================================================================================
Say.section("TEST",columns: 0)
========
=TEST=
========

Justifiers

The various banner-producing methods also support left/center/right justification. Just pass in e.g. justify: :left, justify: :center, or justify: :right. The default, if nothing is supplied, is justify: :left.

# BlockSay.("Hello, World!"){Say.("Huzzah!")}=Hello,World! ================================================================
-> Huzzah!=Done(0.0000s) ===============================================================
Say.("Hello, World!",justify: :left){Say.("Huzzah!")}=Hello,World! ================================================================
-> Huzzah!=Done(0.0000s) ===============================================================
Say.("Hello, World!",justify: :center){Say.("Huzzah!")}
================================= Hello,World!================================
-> Huzzah!
================================ Done(0.0000s) ================================
Say.("Hello, World!",justify: :right){Say.("Huzzah!")}================================================================ Hello,World!=-> Huzzah!
=============================================================== Done(0.0000s)=# BannerSay.banner("TEST")=TEST =========================================================================Say.banner("TEST",justify: :left)=TEST =========================================================================Say.banner("TEST",justify: :center)
=====================================TEST =====================================Say.banner("TEST",justify: :right)
=========================================================================TEST=# HeaderSay.header("TEST")=TEST =========================================================================Say.header("TEST",justify: :left)=TEST =========================================================================Say.header("TEST",justify: :center)
=====================================TEST =====================================Say.header("TEST",justify: :right)
=========================================================================TEST=# FooterSay.footer=Done =========================================================================Say.footer(justify: :left)=Done =========================================================================Say.footer(justify: :center)
=====================================Done =====================================Say.footer(justify: :right)
=========================================================================Done=# SectionSay.section("TEST")
================================================================================
=TEST =========================================================================
================================================================================
Say.section("TEST",justify: :left)
================================================================================
=TEST =========================================================================
================================================================================
Say.section("TEST",justify: :center)
================================================================================
=====================================TEST =====================================
================================================================================
Say.section("TEST",justify: :right)
================================================================================
=========================================================================TEST=
================================================================================

NOTE: The "line" methods will ignore justification attempts as there is no built in concept of columns for these.

Say.("TEST",justify: :right)# `justify: :right` is ignored.->TEST

Advanced Usage

All of the above examples are using the default interpolation template accessed via the Say.<method> methods. For advanced usage, one may access the Say::InterpolationTemplate directly and either use the predefined templates or specify their own.

Predefined interpolation templates

# :double_lineinterpolation_template=Say::InterpolationTemplate::Builder.double_lineinterpolation_template.inspect# => "['=', ...]{}['=', ...]"interpolation_template.interpolate# => "=="interpolation_template.left_justify(length: 20)# => "===================="# :title (the default template, if none is specified)interpolation_template=Say::InterpolationTemplate::Builder.titleinterpolation_template.inspect# => ['=', ...] {} ['=', ...]interpolation_template.interpolate("TEST")# => "= TEST ="interpolation_template.left_justify("TEST",length: 20)# => "= TEST ============="interpolation_template.center_justify("TEST",length: 20)# => "======= TEST ======="interpolation_template.right_justify("TEST",length: 20)# => "============= TEST ="# :wtfinterpolation_template=Say::InterpolationTemplate::Builder.wtfinterpolation_template.inspect# => "['?', ...] {} ['?', ...]"interpolation_template.interpolate("TEST")# => "? TEST ?"interpolation_template.left_justify("TEST",length: 20)# => "? TEST ?????????????"interpolation_template.center_justify("TEST",length: 20)# => "??????? TEST ???????"interpolation_template.right_justify("TEST",length: 20)# => "????????????? TEST ?"

Custom interpolation templates

# Example 1interpolation_template=Say::InterpolationTemplate.new(left_bookend: "╰(⇀︿⇀)つ-]═",left_fill: "-",right_fill: "-")interpolation_template.inspect# => "╰(⇀︿⇀)つ-]═['-', ...]{}['-', ...]"interpolation_template.interpolate("TEST")# => "╰(⇀︿⇀)つ-]═-TEST-"interpolation_template.left_justify("TEST",length: 40)# => "╰(⇀︿⇀)つ-]═-TEST-------------------------"interpolation_template.center_justify("TEST",length: 40)# => "╰(⇀︿⇀)つ-]═--------TEST------------------"interpolation_template.right_justify("TEST",length: 40)# => "╰(⇀︿⇀)つ-]═-------------------------TEST-"# Example 2interpolation_template=Say::InterpolationTemplate.new(left_bookend: "( •_•)O*¯",left_fill: "`·.·´",right_fill: "`·.·´",right_bookend: "¯°Q(•_• )")interpolation_template.inspect# => "( •_•)O*¯['`·.·´', ...]{}['`·.·´', ...]¯°Q(•_• )"interpolation_template.interpolate("TEST")# => "( •_•)O*¯`·.·´TEST`·.·´¯°Q(•_• )"interpolation_template.left_justify("TEST")# => "( •_•)O*¯`·.·´TEST`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.¯°Q(•_• )"interpolation_template.center_justify("TEST")# => "( •_•)O*¯`·.·´`·.·´`·.·´`·.·´`·.·`·.·´TEST`·.·´`·.·´`·.·´`·.·´`·.·´`·.·¯°Q(•_• )"interpolation_template.right_justify("TEST")# => "( •_•)O*¯`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.`·.·´TEST`·.·´¯°Q(•_• )"

Progress Tracking

Use Say.progress to track long-running processing loops on a given interval. Set the interval to receive say updates only during on-interval ticks through the loop. The default interval is 1, meaning every loop is considered on-interval.

Simple

# The default interval is 1.Say.progressdo |interval|
3.timesdo# Increment the interval's internal index by 1.interval.updateinterval.say("Test",:debug)endend=[20230604151646]Start(i=0) =================================================[20230604151646] >> Test(i=1)[20230604151646] >> Test(i=2)[20230604151646] >> Test(i=3)=Done(0.0000s) ===============================================================

Advanced

Say.progress("Progress Tracking Test",interval: 3)do |interval|
0.upto(6)do |index|
# Set the interval's internal index to the current index. This may be safer.interval.update(index)# Only "say" for on-interval ticks through the loop.interval.say("Before Update Interval.",:debug)# Optionally use a block to time a segment.interval.say("Progress Interval Block.")dosleep(0.025)# Do the work here.# Always "say", regardless of interval, in the usual way: with `Say.call`.Say.("Interval-Agnostic Update. Index: #{index}",:info)endinterval.say("After Update Interval.",:debug)endend=[20230604151646]ProgressTrackingTest(i=0) ================================
-- Interval-AgnosticUpdate.Index: 0
-- Interval-AgnosticUpdate.Index: 1
-- Interval-AgnosticUpdate.Index: 2[20230604151646] >> BeforeUpdateInterval.(i=3)=[20230604151646]ProgressIntervalBlock.(i=3) ==============================
-- Interval-AgnosticUpdate.Index: 3=Done(0.0261s) ===============================================================
[20230604151646] >> AfterUpdateInterval.(i=3)
-- Interval-AgnosticUpdate.Index: 4
-- Interval-AgnosticUpdate.Index: 5[20230604151647] >> BeforeUpdateInterval.(i=6)=[20230604151647]ProgressIntervalBlock.(i=6) ==============================
-- Interval-AgnosticUpdate.Index: 6=Done(0.0261s) ===============================================================
[20230604151647] >> AfterUpdateInterval.(i=6)=Done(0.1828s) ===============================================================

Manual

Internally, calling say on a Say::Progress::Interval object uses Say.progress_line to output the given message and index indicator. You may do the same even without an Interval object.

# Given a message. (The default Type is :info.)Say.progress_line("TEST",index: 3)# => [20230604151647] -- TEST (i=3)Say.progress_line("TEST",:success,index: 3)# => [20230604151647] -> TEST (i=3)# Given no message.Say.progress_line(index: 3)# => [20230604151647] ... (i=3)

Namespace Pollution

If you choose to include Say then your class will gain the following instance methods:

  • say
  • say_banner
  • say_footer
  • say_header
  • say_line
  • say_progress
  • say_progress_line
  • say_section
  • say_with_block

... though you probably really only need say, and sometimes: say_progress and/or say_progress_line.

classWithIncludeincludeSayendclassWithoutIncludeendadded_class_methods=WithInclude.methods - WithoutInclude.methodsSay.("Class methods added by `include Say`: #{added_class_methods}")
-- Classmethodsaddedby`include Say`: []added_instance_methods=(WithInclude.new.methods - WithoutInclude.new.methods).sort!Say.("Instance methods added by `include Say`: #{added_instance_methods}")->Instancemethodsaddedby`include Say`: [:say,:say_banner,:say_footer,:say_header,:say_line,:say_progress,:say_progress_line,:say_section,:say_with_block]

Integration

iTerm2

The standardized nature of Say's logging methods lends itself well to highlighting output types using iTerm2's Text Highlighting Triggers. To set this up, go to Settings for iTerm2 -> Profiles -> Advanced -> Triggers section: "Edit" Button.

iTerm2 Triggers Setup

For more help, see iTerm's documentation on Triggers.

The regular expressions and HEX codes used in the screenshot are:

(?<=^ )->(?= )# Text HEX: #0ae400 Background HEX: transparent(?<=^ )>>(?= )# Text HEX: #ff6500 Background HEX: transparent(?<=^ )--(?= )# Text HEX: #ffffff Background HEX: #666666(?<=^ )!¡(?= )# Text HEX: #ffff00 Background HEX: transparent.*\*{2,}.* # Text HEX: #ffffff Background HEX: #ff0000

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake test to run the tests. Or, run rake to run the tests plus linters as well as yard (to confirm proper YARD documentation practices). You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install.

Testing

To test this gem:

rake

Linters

rubocop
reek
npx prettier . --check
npx prettier . --write

Releases

To release a new version of this gem to RubyGems:

  1. Update the version number in version.rb
  2. Update CHANGELOG.md
  3. Run bundle to update Gemfile.lock with the latest version info
  4. Commit the changes. e.g. Bump to vX.Y.Z
  5. Run rake release, which will create a git tag for the version, push git commits and the created tag, and then attempt to push the .gem file to rubygems.org.
  • NOTE: This will fail, as there is already a say gem and this one is not it. This gem must be installed form github source.

Documentation

YARD documentation can be generated and viewed live:

  1. Install YARD: gem install yard
  2. Run the YARD server: yard server --reload
  3. Open the live documentation site: open http://localhost:8808

While the YARD server is running, documentation in the live site will be auto-updated on source code save (and site reload).

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/pdobb/say. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the Say project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.

About

Say provides logging in the style of ActiveRecord::Migration#say... anywhere!

Resources

Code of conduct

Stars

2 stars

Watchers

1 watching

Forks

Releases

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Say

Gem VersionCI Actions

Say gives you the API and the output style you already know and love from ActiveRecord::Migration#say... anywhere! Plus a few extra goodies for long-running processes like incremental progress indicators and remaining time estimation.

Installation

Add this line to your application's Gemfile:

gem"say",github: "pdobb/say"# Not published to RubyGems.

And then execute:

bundle

Compatibility

Tested MRI Ruby Versions:

  • 3.1
  • 3.2
  • 3.3
  • 3.4
  • 4.0

For Ruby 2.7 support, install say gem version 0.5.2.

gem"say",github: "pdobb/say",tag: "v0.5.2"# Not published to RubyGems.

Say has no other dependencies.

Usage

Say.<method>

Typical usage is to just call Say.<method> directly, though it is possible to include Say if you prefer. (See below.)

When called with a block, say will output both Start and Finish banners, then return the result of the block to the caller. When called without a block, say will output a string of the specified type (defaults to :success).

require"say"classDirectAccessProcessordefrunSay.("DirectAccessProcessor"){Say.("Successfully did the thing!")Say.()# Or: Say.callSay.("Debug details about this ...",:debug)Say.("Info about stuff ...",:info)Say.("Maybe look into this thing ...",:warn)Say.("Failed to do a thing ...",:error)"The Result!"}endendresult=DirectAccessProcessor.new.run=DirectAccessProcessor ========================================================
->Successfullydidthe thing!
...
>> Debugdetailsaboutthis ...
-- Infoaboutstuff ...
!¡Maybelookintothisthing ...
** Failedtodoathing ...
=Done(0.0001s) ===============================================================
# => "The Result!"

include Say

Use include Say to gain access to the instance-level say methods.

require"say"classIncludeProcessorincludeSaydefrunsay("IncludeProcessor"){say("Successfully did the thing!")saysay("Debug details about this ...",:debug)say("Info about stuff ...",:info)say("Maybe look into this thing ...",:warn)say("Failed to do a thing ...",:error)"The Result!"}endendresult=IncludeProcessor.new.run=IncludeProcessor =============================================================
-> Successfullydidthe thing!
...
>> Debugdetailsaboutthis ...
-- Infoaboutstuff ...
!¡Maybelookintothisthing ...
** Failedtodoathing ...
=Done(0.0001s) ===============================================================
# => "The Result!"

Say Types

When using Say.(<message>, <type>), the available types and output representations are:

TypeOutput Prefix
:debug" >> "
:error" ** "
:info" -- "
:success" -> "
:warn" !¡ "
Say.debug("TEST")# => " >> TEST"Say.error("TEST")# => " ** TEST"Say.info("TEST")# => " -- TEST"Say.success("TEST")# => " -> TEST"Say.warn("TEST")# => " !¡ TEST"

The default type if call is used is :success.

Say.call("TEST")# => " -> TEST"Say.("TEST")# => " -> TEST"

say(<message>, <type>) Methods

The include Say alternatives for each of the <type> calls in the previous section are:

Say.("TEST",:debug)# => " >> TEST"Say.("TEST",:error)# => " ** TEST"Say.("TEST",:info)# => " -- TEST"Say.("TEST",:success)# => " -> TEST"Say.("TEST",:warn)# => " !¡ TEST"

Say.hr (Horizontal Rule)

Use Say.hr for thin, 1-line separators + padding on top/bottom.

Say.info("Before")Say.hrSay.("After")
-- Before
--------------------------------------------------------------------------------
->After

Horizontal Rule -- Customization

The fill line, template, and length can all be customized:

Say.info("Before")Say.hr("-*",template: "%s",columns: 20)# `template` defaults to: `"\n%s\n"Say.("After")
-- Before
-*-*-*-*-*-*-*-*-*-*
->After

Template can also include book-ends:

Say.hr("-*",template: "|%s|")
|-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*|

Say.section

Use Say.section for 3-line banners to really visually split up your output into major sections.

Say.section
================================================================================
================================================================================
================================================================================
Say.section("TEST")
================================================================================
=TEST =========================================================================
================================================================================
Say.section("TEST",columns: 0)
========
=TEST=
========

Justifiers

The various banner-producing methods also support left/center/right justification. Just pass in e.g. justify: :left, justify: :center, or justify: :right. The default, if nothing is supplied, is justify: :left.

# BlockSay.("Hello, World!"){Say.("Huzzah!")}=Hello,World! ================================================================
-> Huzzah!=Done(0.0000s) ===============================================================
Say.("Hello, World!",justify: :left){Say.("Huzzah!")}=Hello,World! ================================================================
-> Huzzah!=Done(0.0000s) ===============================================================
Say.("Hello, World!",justify: :center){Say.("Huzzah!")}
================================= Hello,World!================================
-> Huzzah!
================================ Done(0.0000s) ================================
Say.("Hello, World!",justify: :right){Say.("Huzzah!")}================================================================ Hello,World!=-> Huzzah!
=============================================================== Done(0.0000s)=# BannerSay.banner("TEST")=TEST =========================================================================Say.banner("TEST",justify: :left)=TEST =========================================================================Say.banner("TEST",justify: :center)
=====================================TEST =====================================Say.banner("TEST",justify: :right)
=========================================================================TEST=# HeaderSay.header("TEST")=TEST =========================================================================Say.header("TEST",justify: :left)=TEST =========================================================================Say.header("TEST",justify: :center)
=====================================TEST =====================================Say.header("TEST",justify: :right)
=========================================================================TEST=# FooterSay.footer=Done =========================================================================Say.footer(justify: :left)=Done =========================================================================Say.footer(justify: :center)
=====================================Done =====================================Say.footer(justify: :right)
=========================================================================Done=# SectionSay.section("TEST")
================================================================================
=TEST =========================================================================
================================================================================
Say.section("TEST",justify: :left)
================================================================================
=TEST =========================================================================
================================================================================
Say.section("TEST",justify: :center)
================================================================================
=====================================TEST =====================================
================================================================================
Say.section("TEST",justify: :right)
================================================================================
=========================================================================TEST=
================================================================================

NOTE: The "line" methods will ignore justification attempts as there is no built in concept of columns for these.

Say.("TEST",justify: :right)# `justify: :right` is ignored.->TEST

Advanced Usage

All of the above examples are using the default interpolation template accessed via the Say.<method> methods. For advanced usage, one may access the Say::InterpolationTemplate directly and either use the predefined templates or specify their own.

Predefined interpolation templates

# :double_lineinterpolation_template=Say::InterpolationTemplate::Builder.double_lineinterpolation_template.inspect# => "['=', ...]{}['=', ...]"interpolation_template.interpolate# => "=="interpolation_template.left_justify(length: 20)# => "===================="# :title (the default template, if none is specified)interpolation_template=Say::InterpolationTemplate::Builder.titleinterpolation_template.inspect# => ['=', ...] {} ['=', ...]interpolation_template.interpolate("TEST")# => "= TEST ="interpolation_template.left_justify("TEST",length: 20)# => "= TEST ============="interpolation_template.center_justify("TEST",length: 20)# => "======= TEST ======="interpolation_template.right_justify("TEST",length: 20)# => "============= TEST ="# :wtfinterpolation_template=Say::InterpolationTemplate::Builder.wtfinterpolation_template.inspect# => "['?', ...] {} ['?', ...]"interpolation_template.interpolate("TEST")# => "? TEST ?"interpolation_template.left_justify("TEST",length: 20)# => "? TEST ?????????????"interpolation_template.center_justify("TEST",length: 20)# => "??????? TEST ???????"interpolation_template.right_justify("TEST",length: 20)# => "????????????? TEST ?"

Custom interpolation templates

# Example 1interpolation_template=Say::InterpolationTemplate.new(left_bookend: "╰(⇀︿⇀)つ-]═",left_fill: "-",right_fill: "-")interpolation_template.inspect# => "╰(⇀︿⇀)つ-]═['-', ...]{}['-', ...]"interpolation_template.interpolate("TEST")# => "╰(⇀︿⇀)つ-]═-TEST-"interpolation_template.left_justify("TEST",length: 40)# => "╰(⇀︿⇀)つ-]═-TEST-------------------------"interpolation_template.center_justify("TEST",length: 40)# => "╰(⇀︿⇀)つ-]═--------TEST------------------"interpolation_template.right_justify("TEST",length: 40)# => "╰(⇀︿⇀)つ-]═-------------------------TEST-"# Example 2interpolation_template=Say::InterpolationTemplate.new(left_bookend: "( •_•)O*¯",left_fill: "`·.·´",right_fill: "`·.·´",right_bookend: "¯°Q(•_• )")interpolation_template.inspect# => "( •_•)O*¯['`·.·´', ...]{}['`·.·´', ...]¯°Q(•_• )"interpolation_template.interpolate("TEST")# => "( •_•)O*¯`·.·´TEST`·.·´¯°Q(•_• )"interpolation_template.left_justify("TEST")# => "( •_•)O*¯`·.·´TEST`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.¯°Q(•_• )"interpolation_template.center_justify("TEST")# => "( •_•)O*¯`·.·´`·.·´`·.·´`·.·´`·.·`·.·´TEST`·.·´`·.·´`·.·´`·.·´`·.·´`·.·¯°Q(•_• )"interpolation_template.right_justify("TEST")# => "( •_•)O*¯`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.`·.·´TEST`·.·´¯°Q(•_• )"

Progress Tracking

Use Say.progress to track long-running processing loops on a given interval. Set the interval to receive say updates only during on-interval ticks through the loop. The default interval is 1, meaning every loop is considered on-interval.

Simple

# The default interval is 1.Say.progressdo |interval|
3.timesdo# Increment the interval's internal index by 1.interval.updateinterval.say("Test",:debug)endend=[20230604151646]Start(i=0) =================================================[20230604151646] >> Test(i=1)[20230604151646] >> Test(i=2)[20230604151646] >> Test(i=3)=Done(0.0000s) ===============================================================

Advanced

Say.progress("Progress Tracking Test",interval: 3)do |interval|
0.upto(6)do |index|
# Set the interval's internal index to the current index. This may be safer.interval.update(index)# Only "say" for on-interval ticks through the loop.interval.say("Before Update Interval.",:debug)# Optionally use a block to time a segment.interval.say("Progress Interval Block.")dosleep(0.025)# Do the work here.# Always "say", regardless of interval, in the usual way: with `Say.call`.Say.("Interval-Agnostic Update. Index: #{index}",:info)endinterval.say("After Update Interval.",:debug)endend=[20230604151646]ProgressTrackingTest(i=0) ================================
-- Interval-AgnosticUpdate.Index: 0
-- Interval-AgnosticUpdate.Index: 1
-- Interval-AgnosticUpdate.Index: 2[20230604151646] >> BeforeUpdateInterval.(i=3)=[20230604151646]ProgressIntervalBlock.(i=3) ==============================
-- Interval-AgnosticUpdate.Index: 3=Done(0.0261s) ===============================================================
[20230604151646] >> AfterUpdateInterval.(i=3)
-- Interval-AgnosticUpdate.Index: 4
-- Interval-AgnosticUpdate.Index: 5[20230604151647] >> BeforeUpdateInterval.(i=6)=[20230604151647]ProgressIntervalBlock.(i=6) ==============================
-- Interval-AgnosticUpdate.Index: 6=Done(0.0261s) ===============================================================
[20230604151647] >> AfterUpdateInterval.(i=6)=Done(0.1828s) ===============================================================

Manual

Internally, calling say on a Say::Progress::Interval object uses Say.progress_line to output the given message and index indicator. You may do the same even without an Interval object.

# Given a message. (The default Type is :info.)Say.progress_line("TEST",index: 3)# => [20230604151647] -- TEST (i=3)Say.progress_line("TEST",:success,index: 3)# => [20230604151647] -> TEST (i=3)# Given no message.Say.progress_line(index: 3)# => [20230604151647] ... (i=3)

Namespace Pollution

If you choose to include Say then your class will gain the following instance methods:

  • say
  • say_banner
  • say_footer
  • say_header
  • say_line
  • say_progress
  • say_progress_line
  • say_section
  • say_with_block

... though you probably really only need say, and sometimes: say_progress and/or say_progress_line.

classWithIncludeincludeSayendclassWithoutIncludeendadded_class_methods=WithInclude.methods - WithoutInclude.methodsSay.("Class methods added by `include Say`: #{added_class_methods}")
-- Classmethodsaddedby`include Say`: []added_instance_methods=(WithInclude.new.methods - WithoutInclude.new.methods).sort!Say.("Instance methods added by `include Say`: #{added_instance_methods}")->Instancemethodsaddedby`include Say`: [:say,:say_banner,:say_footer,:say_header,:say_line,:say_progress,:say_progress_line,:say_section,:say_with_block]

Integration

iTerm2

The standardized nature of Say's logging methods lends itself well to highlighting output types using iTerm2's Text Highlighting Triggers. To set this up, go to Settings for iTerm2 -> Profiles -> Advanced -> Triggers section: "Edit" Button.

iTerm2 Triggers Setup

For more help, see iTerm's documentation on Triggers.

The regular expressions and HEX codes used in the screenshot are:

(?<=^ )->(?= )# Text HEX: #0ae400 Background HEX: transparent(?<=^ )>>(?= )# Text HEX: #ff6500 Background HEX: transparent(?<=^ )--(?= )# Text HEX: #ffffff Background HEX: #666666(?<=^ )!¡(?= )# Text HEX: #ffff00 Background HEX: transparent.*\*{2,}.* # Text HEX: #ffffff Background HEX: #ff0000

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake test to run the tests. Or, run rake to run the tests plus linters as well as yard (to confirm proper YARD documentation practices). You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install.

Testing

To test this gem:

rake

Linters

rubocop
reek
npx prettier . --check
npx prettier . --write

Releases

To release a new version of this gem to RubyGems:

  1. Update the version number in version.rb
  2. Update CHANGELOG.md
  3. Run bundle to update Gemfile.lock with the latest version info
  4. Commit the changes. e.g. Bump to vX.Y.Z
  5. Run rake release, which will create a git tag for the version, push git commits and the created tag, and then attempt to push the .gem file to rubygems.org.
  • NOTE: This will fail, as there is already a say gem and this one is not it. This gem must be installed form github source.

Documentation

YARD documentation can be generated and viewed live:

  1. Install YARD: gem install yard
  2. Run the YARD server: yard server --reload
  3. Open the live documentation site: open http://localhost:8808

While the YARD server is running, documentation in the live site will be auto-updated on source code save (and site reload).

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/pdobb/say. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the Say project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.

About

Say provides logging in the style of ActiveRecord::Migration#say... anywhere!

Resources

Code of conduct

Stars

2 stars

Watchers

1 watching

Forks

Releases

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Say

Gem VersionCI Actions

Say gives you the API and the output style you already know and love from ActiveRecord::Migration#say... anywhere! Plus a few extra goodies for long-running processes like incremental progress indicators and remaining time estimation.

Installation

Add this line to your application's Gemfile:

gem"say",github: "pdobb/say"# Not published to RubyGems.

And then execute:

bundle

Compatibility

Tested MRI Ruby Versions:

  • 3.1
  • 3.2
  • 3.3
  • 3.4
  • 4.0

For Ruby 2.7 support, install say gem version 0.5.2.

gem"say",github: "pdobb/say",tag: "v0.5.2"# Not published to RubyGems.

Say has no other dependencies.

Usage

Say.<method>

Typical usage is to just call Say.<method> directly, though it is possible to include Say if you prefer. (See below.)

When called with a block, say will output both Start and Finish banners, then return the result of the block to the caller. When called without a block, say will output a string of the specified type (defaults to :success).

require"say"classDirectAccessProcessordefrunSay.("DirectAccessProcessor"){Say.("Successfully did the thing!")Say.()# Or: Say.callSay.("Debug details about this ...",:debug)Say.("Info about stuff ...",:info)Say.("Maybe look into this thing ...",:warn)Say.("Failed to do a thing ...",:error)"The Result!"}endendresult=DirectAccessProcessor.new.run=DirectAccessProcessor ========================================================
->Successfullydidthe thing!
...
>> Debugdetailsaboutthis ...
-- Infoaboutstuff ...
!¡Maybelookintothisthing ...
** Failedtodoathing ...
=Done(0.0001s) ===============================================================
# => "The Result!"

include Say

Use include Say to gain access to the instance-level say methods.

require"say"classIncludeProcessorincludeSaydefrunsay("IncludeProcessor"){say("Successfully did the thing!")saysay("Debug details about this ...",:debug)say("Info about stuff ...",:info)say("Maybe look into this thing ...",:warn)say("Failed to do a thing ...",:error)"The Result!"}endendresult=IncludeProcessor.new.run=IncludeProcessor =============================================================
-> Successfullydidthe thing!
...
>> Debugdetailsaboutthis ...
-- Infoaboutstuff ...
!¡Maybelookintothisthing ...
** Failedtodoathing ...
=Done(0.0001s) ===============================================================
# => "The Result!"

Say Types

When using Say.(<message>, <type>), the available types and output representations are:

TypeOutput Prefix
:debug" >> "
:error" ** "
:info" -- "
:success" -> "
:warn" !¡ "
Say.debug("TEST")# => " >> TEST"Say.error("TEST")# => " ** TEST"Say.info("TEST")# => " -- TEST"Say.success("TEST")# => " -> TEST"Say.warn("TEST")# => " !¡ TEST"

The default type if call is used is :success.

Say.call("TEST")# => " -> TEST"Say.("TEST")# => " -> TEST"

say(<message>, <type>) Methods

The include Say alternatives for each of the <type> calls in the previous section are:

Say.("TEST",:debug)# => " >> TEST"Say.("TEST",:error)# => " ** TEST"Say.("TEST",:info)# => " -- TEST"Say.("TEST",:success)# => " -> TEST"Say.("TEST",:warn)# => " !¡ TEST"

Say.hr (Horizontal Rule)

Use Say.hr for thin, 1-line separators + padding on top/bottom.

Say.info("Before")Say.hrSay.("After")
-- Before
--------------------------------------------------------------------------------
->After

Horizontal Rule -- Customization

The fill line, template, and length can all be customized:

Say.info("Before")Say.hr("-*",template: "%s",columns: 20)# `template` defaults to: `"\n%s\n"Say.("After")
-- Before
-*-*-*-*-*-*-*-*-*-*
->After

Template can also include book-ends:

Say.hr("-*",template: "|%s|")
|-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*|

Say.section

Use Say.section for 3-line banners to really visually split up your output into major sections.

Say.section
================================================================================
================================================================================
================================================================================
Say.section("TEST")
================================================================================
=TEST =========================================================================
================================================================================
Say.section("TEST",columns: 0)
========
=TEST=
========

Justifiers

The various banner-producing methods also support left/center/right justification. Just pass in e.g. justify: :left, justify: :center, or justify: :right. The default, if nothing is supplied, is justify: :left.

# BlockSay.("Hello, World!"){Say.("Huzzah!")}=Hello,World! ================================================================
-> Huzzah!=Done(0.0000s) ===============================================================
Say.("Hello, World!",justify: :left){Say.("Huzzah!")}=Hello,World! ================================================================
-> Huzzah!=Done(0.0000s) ===============================================================
Say.("Hello, World!",justify: :center){Say.("Huzzah!")}
================================= Hello,World!================================
-> Huzzah!
================================ Done(0.0000s) ================================
Say.("Hello, World!",justify: :right){Say.("Huzzah!")}================================================================ Hello,World!=-> Huzzah!
=============================================================== Done(0.0000s)=# BannerSay.banner("TEST")=TEST =========================================================================Say.banner("TEST",justify: :left)=TEST =========================================================================Say.banner("TEST",justify: :center)
=====================================TEST =====================================Say.banner("TEST",justify: :right)
=========================================================================TEST=# HeaderSay.header("TEST")=TEST =========================================================================Say.header("TEST",justify: :left)=TEST =========================================================================Say.header("TEST",justify: :center)
=====================================TEST =====================================Say.header("TEST",justify: :right)
=========================================================================TEST=# FooterSay.footer=Done =========================================================================Say.footer(justify: :left)=Done =========================================================================Say.footer(justify: :center)
=====================================Done =====================================Say.footer(justify: :right)
=========================================================================Done=# SectionSay.section("TEST")
================================================================================
=TEST =========================================================================
================================================================================
Say.section("TEST",justify: :left)
================================================================================
=TEST =========================================================================
================================================================================
Say.section("TEST",justify: :center)
================================================================================
=====================================TEST =====================================
================================================================================
Say.section("TEST",justify: :right)
================================================================================
=========================================================================TEST=
================================================================================

NOTE: The "line" methods will ignore justification attempts as there is no built in concept of columns for these.

Say.("TEST",justify: :right)# `justify: :right` is ignored.->TEST

Advanced Usage

All of the above examples are using the default interpolation template accessed via the Say.<method> methods. For advanced usage, one may access the Say::InterpolationTemplate directly and either use the predefined templates or specify their own.

Predefined interpolation templates

# :double_lineinterpolation_template=Say::InterpolationTemplate::Builder.double_lineinterpolation_template.inspect# => "['=', ...]{}['=', ...]"interpolation_template.interpolate# => "=="interpolation_template.left_justify(length: 20)# => "===================="# :title (the default template, if none is specified)interpolation_template=Say::InterpolationTemplate::Builder.titleinterpolation_template.inspect# => ['=', ...] {} ['=', ...]interpolation_template.interpolate("TEST")# => "= TEST ="interpolation_template.left_justify("TEST",length: 20)# => "= TEST ============="interpolation_template.center_justify("TEST",length: 20)# => "======= TEST ======="interpolation_template.right_justify("TEST",length: 20)# => "============= TEST ="# :wtfinterpolation_template=Say::InterpolationTemplate::Builder.wtfinterpolation_template.inspect# => "['?', ...] {} ['?', ...]"interpolation_template.interpolate("TEST")# => "? TEST ?"interpolation_template.left_justify("TEST",length: 20)# => "? TEST ?????????????"interpolation_template.center_justify("TEST",length: 20)# => "??????? TEST ???????"interpolation_template.right_justify("TEST",length: 20)# => "????????????? TEST ?"

Custom interpolation templates

# Example 1interpolation_template=Say::InterpolationTemplate.new(left_bookend: "╰(⇀︿⇀)つ-]═",left_fill: "-",right_fill: "-")interpolation_template.inspect# => "╰(⇀︿⇀)つ-]═['-', ...]{}['-', ...]"interpolation_template.interpolate("TEST")# => "╰(⇀︿⇀)つ-]═-TEST-"interpolation_template.left_justify("TEST",length: 40)# => "╰(⇀︿⇀)つ-]═-TEST-------------------------"interpolation_template.center_justify("TEST",length: 40)# => "╰(⇀︿⇀)つ-]═--------TEST------------------"interpolation_template.right_justify("TEST",length: 40)# => "╰(⇀︿⇀)つ-]═-------------------------TEST-"# Example 2interpolation_template=Say::InterpolationTemplate.new(left_bookend: "( •_•)O*¯",left_fill: "`·.·´",right_fill: "`·.·´",right_bookend: "¯°Q(•_• )")interpolation_template.inspect# => "( •_•)O*¯['`·.·´', ...]{}['`·.·´', ...]¯°Q(•_• )"interpolation_template.interpolate("TEST")# => "( •_•)O*¯`·.·´TEST`·.·´¯°Q(•_• )"interpolation_template.left_justify("TEST")# => "( •_•)O*¯`·.·´TEST`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.¯°Q(•_• )"interpolation_template.center_justify("TEST")# => "( •_•)O*¯`·.·´`·.·´`·.·´`·.·´`·.·`·.·´TEST`·.·´`·.·´`·.·´`·.·´`·.·´`·.·¯°Q(•_• )"interpolation_template.right_justify("TEST")# => "( •_•)O*¯`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.`·.·´TEST`·.·´¯°Q(•_• )"

Progress Tracking

Use Say.progress to track long-running processing loops on a given interval. Set the interval to receive say updates only during on-interval ticks through the loop. The default interval is 1, meaning every loop is considered on-interval.

Simple

# The default interval is 1.Say.progressdo |interval|
3.timesdo# Increment the interval's internal index by 1.interval.updateinterval.say("Test",:debug)endend=[20230604151646]Start(i=0) =================================================[20230604151646] >> Test(i=1)[20230604151646] >> Test(i=2)[20230604151646] >> Test(i=3)=Done(0.0000s) ===============================================================

Advanced

Say.progress("Progress Tracking Test",interval: 3)do |interval|
0.upto(6)do |index|
# Set the interval's internal index to the current index. This may be safer.interval.update(index)# Only "say" for on-interval ticks through the loop.interval.say("Before Update Interval.",:debug)# Optionally use a block to time a segment.interval.say("Progress Interval Block.")dosleep(0.025)# Do the work here.# Always "say", regardless of interval, in the usual way: with `Say.call`.Say.("Interval-Agnostic Update. Index: #{index}",:info)endinterval.say("After Update Interval.",:debug)endend=[20230604151646]ProgressTrackingTest(i=0) ================================
-- Interval-AgnosticUpdate.Index: 0
-- Interval-AgnosticUpdate.Index: 1
-- Interval-AgnosticUpdate.Index: 2[20230604151646] >> BeforeUpdateInterval.(i=3)=[20230604151646]ProgressIntervalBlock.(i=3) ==============================
-- Interval-AgnosticUpdate.Index: 3=Done(0.0261s) ===============================================================
[20230604151646] >> AfterUpdateInterval.(i=3)
-- Interval-AgnosticUpdate.Index: 4
-- Interval-AgnosticUpdate.Index: 5[20230604151647] >> BeforeUpdateInterval.(i=6)=[20230604151647]ProgressIntervalBlock.(i=6) ==============================
-- Interval-AgnosticUpdate.Index: 6=Done(0.0261s) ===============================================================
[20230604151647] >> AfterUpdateInterval.(i=6)=Done(0.1828s) ===============================================================

Manual

Internally, calling say on a Say::Progress::Interval object uses Say.progress_line to output the given message and index indicator. You may do the same even without an Interval object.

# Given a message. (The default Type is :info.)Say.progress_line("TEST",index: 3)# => [20230604151647] -- TEST (i=3)Say.progress_line("TEST",:success,index: 3)# => [20230604151647] -> TEST (i=3)# Given no message.Say.progress_line(index: 3)# => [20230604151647] ... (i=3)

Namespace Pollution

If you choose to include Say then your class will gain the following instance methods:

  • say
  • say_banner
  • say_footer
  • say_header
  • say_line
  • say_progress
  • say_progress_line
  • say_section
  • say_with_block

... though you probably really only need say, and sometimes: say_progress and/or say_progress_line.

classWithIncludeincludeSayendclassWithoutIncludeendadded_class_methods=WithInclude.methods - WithoutInclude.methodsSay.("Class methods added by `include Say`: #{added_class_methods}")
-- Classmethodsaddedby`include Say`: []added_instance_methods=(WithInclude.new.methods - WithoutInclude.new.methods).sort!Say.("Instance methods added by `include Say`: #{added_instance_methods}")->Instancemethodsaddedby`include Say`: [:say,:say_banner,:say_footer,:say_header,:say_line,:say_progress,:say_progress_line,:say_section,:say_with_block]

Integration

iTerm2

The standardized nature of Say's logging methods lends itself well to highlighting output types using iTerm2's Text Highlighting Triggers. To set this up, go to Settings for iTerm2 -> Profiles -> Advanced -> Triggers section: "Edit" Button.

iTerm2 Triggers Setup

For more help, see iTerm's documentation on Triggers.

The regular expressions and HEX codes used in the screenshot are:

(?<=^ )->(?= )# Text HEX: #0ae400 Background HEX: transparent(?<=^ )>>(?= )# Text HEX: #ff6500 Background HEX: transparent(?<=^ )--(?= )# Text HEX: #ffffff Background HEX: #666666(?<=^ )!¡(?= )# Text HEX: #ffff00 Background HEX: transparent.*\*{2,}.* # Text HEX: #ffffff Background HEX: #ff0000

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake test to run the tests. Or, run rake to run the tests plus linters as well as yard (to confirm proper YARD documentation practices). You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install.

Testing

To test this gem:

rake

Linters

rubocop
reek
npx prettier . --check
npx prettier . --write

Releases

To release a new version of this gem to RubyGems:

  1. Update the version number in version.rb
  2. Update CHANGELOG.md
  3. Run bundle to update Gemfile.lock with the latest version info
  4. Commit the changes. e.g. Bump to vX.Y.Z
  5. Run rake release, which will create a git tag for the version, push git commits and the created tag, and then attempt to push the .gem file to rubygems.org.
  • NOTE: This will fail, as there is already a say gem and this one is not it. This gem must be installed form github source.

Documentation

YARD documentation can be generated and viewed live:

  1. Install YARD: gem install yard
  2. Run the YARD server: yard server --reload
  3. Open the live documentation site: open http://localhost:8808

While the YARD server is running, documentation in the live site will be auto-updated on source code save (and site reload).

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/pdobb/say. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the Say project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.

About

Say provides logging in the style of ActiveRecord::Migration#say... anywhere!

Resources

Code of conduct

Stars

2 stars

Watchers

1 watching

Forks

Releases

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

Say

Gem VersionCI Actions

Say gives you the API and the output style you already know and love from ActiveRecord::Migration#say... anywhere! Plus a few extra goodies for long-running processes like incremental progress indicators and remaining time estimation.

Installation

Add this line to your application's Gemfile:

gem"say",github: "pdobb/say"# Not published to RubyGems.

And then execute:

bundle

Compatibility

Tested MRI Ruby Versions:

  • 3.1
  • 3.2
  • 3.3
  • 3.4
  • 4.0

For Ruby 2.7 support, install say gem version 0.5.2.

gem"say",github: "pdobb/say",tag: "v0.5.2"# Not published to RubyGems.

Say has no other dependencies.

Usage

Say.<method>

Typical usage is to just call Say.<method> directly, though it is possible to include Say if you prefer. (See below.)

When called with a block, say will output both Start and Finish banners, then return the result of the block to the caller. When called without a block, say will output a string of the specified type (defaults to :success).

require"say"classDirectAccessProcessordefrunSay.("DirectAccessProcessor"){Say.("Successfully did the thing!")Say.()# Or: Say.callSay.("Debug details about this ...",:debug)Say.("Info about stuff ...",:info)Say.("Maybe look into this thing ...",:warn)Say.("Failed to do a thing ...",:error)"The Result!"}endendresult=DirectAccessProcessor.new.run=DirectAccessProcessor ========================================================
->Successfullydidthe thing!
...
>> Debugdetailsaboutthis ...
-- Infoaboutstuff ...
!¡Maybelookintothisthing ...
** Failedtodoathing ...
=Done(0.0001s) ===============================================================
# => "The Result!"

include Say

Use include Say to gain access to the instance-level say methods.

require"say"classIncludeProcessorincludeSaydefrunsay("IncludeProcessor"){say("Successfully did the thing!")saysay("Debug details about this ...",:debug)say("Info about stuff ...",:info)say("Maybe look into this thing ...",:warn)say("Failed to do a thing ...",:error)"The Result!"}endendresult=IncludeProcessor.new.run=IncludeProcessor =============================================================
-> Successfullydidthe thing!
...
>> Debugdetailsaboutthis ...
-- Infoaboutstuff ...
!¡Maybelookintothisthing ...
** Failedtodoathing ...
=Done(0.0001s) ===============================================================
# => "The Result!"

Say Types

When using Say.(<message>, <type>), the available types and output representations are:

TypeOutput Prefix
:debug" >> "
:error" ** "
:info" -- "
:success" -> "
:warn" !¡ "
Say.debug("TEST")# => " >> TEST"Say.error("TEST")# => " ** TEST"Say.info("TEST")# => " -- TEST"Say.success("TEST")# => " -> TEST"Say.warn("TEST")# => " !¡ TEST"

The default type if call is used is :success.

Say.call("TEST")# => " -> TEST"Say.("TEST")# => " -> TEST"

say(<message>, <type>) Methods

The include Say alternatives for each of the <type> calls in the previous section are:

Say.("TEST",:debug)# => " >> TEST"Say.("TEST",:error)# => " ** TEST"Say.("TEST",:info)# => " -- TEST"Say.("TEST",:success)# => " -> TEST"Say.("TEST",:warn)# => " !¡ TEST"

Say.hr (Horizontal Rule)

Use Say.hr for thin, 1-line separators + padding on top/bottom.

Say.info("Before")Say.hrSay.("After")
-- Before
--------------------------------------------------------------------------------
->After

Horizontal Rule -- Customization

The fill line, template, and length can all be customized:

Say.info("Before")Say.hr("-*",template: "%s",columns: 20)# `template` defaults to: `"\n%s\n"Say.("After")
-- Before
-*-*-*-*-*-*-*-*-*-*
->After

Template can also include book-ends:

Say.hr("-*",template: "|%s|")
|-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*|

Say.section

Use Say.section for 3-line banners to really visually split up your output into major sections.

Say.section
================================================================================
================================================================================
================================================================================
Say.section("TEST")
================================================================================
=TEST =========================================================================
================================================================================
Say.section("TEST",columns: 0)
========
=TEST=
========

Justifiers

The various banner-producing methods also support left/center/right justification. Just pass in e.g. justify: :left, justify: :center, or justify: :right. The default, if nothing is supplied, is justify: :left.

# BlockSay.("Hello, World!"){Say.("Huzzah!")}=Hello,World! ================================================================
-> Huzzah!=Done(0.0000s) ===============================================================
Say.("Hello, World!",justify: :left){Say.("Huzzah!")}=Hello,World! ================================================================
-> Huzzah!=Done(0.0000s) ===============================================================
Say.("Hello, World!",justify: :center){Say.("Huzzah!")}
================================= Hello,World!================================
-> Huzzah!
================================ Done(0.0000s) ================================
Say.("Hello, World!",justify: :right){Say.("Huzzah!")}================================================================ Hello,World!=-> Huzzah!
=============================================================== Done(0.0000s)=# BannerSay.banner("TEST")=TEST =========================================================================Say.banner("TEST",justify: :left)=TEST =========================================================================Say.banner("TEST",justify: :center)
=====================================TEST =====================================Say.banner("TEST",justify: :right)
=========================================================================TEST=# HeaderSay.header("TEST")=TEST =========================================================================Say.header("TEST",justify: :left)=TEST =========================================================================Say.header("TEST",justify: :center)
=====================================TEST =====================================Say.header("TEST",justify: :right)
=========================================================================TEST=# FooterSay.footer=Done =========================================================================Say.footer(justify: :left)=Done =========================================================================Say.footer(justify: :center)
=====================================Done =====================================Say.footer(justify: :right)
=========================================================================Done=# SectionSay.section("TEST")
================================================================================
=TEST =========================================================================
================================================================================
Say.section("TEST",justify: :left)
================================================================================
=TEST =========================================================================
================================================================================
Say.section("TEST",justify: :center)
================================================================================
=====================================TEST =====================================
================================================================================
Say.section("TEST",justify: :right)
================================================================================
=========================================================================TEST=
================================================================================

NOTE: The "line" methods will ignore justification attempts as there is no built in concept of columns for these.

Say.("TEST",justify: :right)# `justify: :right` is ignored.->TEST

Advanced Usage

All of the above examples are using the default interpolation template accessed via the Say.<method> methods. For advanced usage, one may access the Say::InterpolationTemplate directly and either use the predefined templates or specify their own.

Predefined interpolation templates

# :double_lineinterpolation_template=Say::InterpolationTemplate::Builder.double_lineinterpolation_template.inspect# => "['=', ...]{}['=', ...]"interpolation_template.interpolate# => "=="interpolation_template.left_justify(length: 20)# => "===================="# :title (the default template, if none is specified)interpolation_template=Say::InterpolationTemplate::Builder.titleinterpolation_template.inspect# => ['=', ...] {} ['=', ...]interpolation_template.interpolate("TEST")# => "= TEST ="interpolation_template.left_justify("TEST",length: 20)# => "= TEST ============="interpolation_template.center_justify("TEST",length: 20)# => "======= TEST ======="interpolation_template.right_justify("TEST",length: 20)# => "============= TEST ="# :wtfinterpolation_template=Say::InterpolationTemplate::Builder.wtfinterpolation_template.inspect# => "['?', ...] {} ['?', ...]"interpolation_template.interpolate("TEST")# => "? TEST ?"interpolation_template.left_justify("TEST",length: 20)# => "? TEST ?????????????"interpolation_template.center_justify("TEST",length: 20)# => "??????? TEST ???????"interpolation_template.right_justify("TEST",length: 20)# => "????????????? TEST ?"

Custom interpolation templates

# Example 1interpolation_template=Say::InterpolationTemplate.new(left_bookend: "╰(⇀︿⇀)つ-]═",left_fill: "-",right_fill: "-")interpolation_template.inspect# => "╰(⇀︿⇀)つ-]═['-', ...]{}['-', ...]"interpolation_template.interpolate("TEST")# => "╰(⇀︿⇀)つ-]═-TEST-"interpolation_template.left_justify("TEST",length: 40)# => "╰(⇀︿⇀)つ-]═-TEST-------------------------"interpolation_template.center_justify("TEST",length: 40)# => "╰(⇀︿⇀)つ-]═--------TEST------------------"interpolation_template.right_justify("TEST",length: 40)# => "╰(⇀︿⇀)つ-]═-------------------------TEST-"# Example 2interpolation_template=Say::InterpolationTemplate.new(left_bookend: "( •_•)O*¯",left_fill: "`·.·´",right_fill: "`·.·´",right_bookend: "¯°Q(•_• )")interpolation_template.inspect# => "( •_•)O*¯['`·.·´', ...]{}['`·.·´', ...]¯°Q(•_• )"interpolation_template.interpolate("TEST")# => "( •_•)O*¯`·.·´TEST`·.·´¯°Q(•_• )"interpolation_template.left_justify("TEST")# => "( •_•)O*¯`·.·´TEST`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.¯°Q(•_• )"interpolation_template.center_justify("TEST")# => "( •_•)O*¯`·.·´`·.·´`·.·´`·.·´`·.·`·.·´TEST`·.·´`·.·´`·.·´`·.·´`·.·´`·.·¯°Q(•_• )"interpolation_template.right_justify("TEST")# => "( •_•)O*¯`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.`·.·´TEST`·.·´¯°Q(•_• )"

Progress Tracking

Use Say.progress to track long-running processing loops on a given interval. Set the interval to receive say updates only during on-interval ticks through the loop. The default interval is 1, meaning every loop is considered on-interval.

Simple

# The default interval is 1.Say.progressdo |interval|
3.timesdo# Increment the interval's internal index by 1.interval.updateinterval.say("Test",:debug)endend=[20230604151646]Start(i=0) =================================================[20230604151646] >> Test(i=1)[20230604151646] >> Test(i=2)[20230604151646] >> Test(i=3)=Done(0.0000s) ===============================================================

Advanced

Say.progress("Progress Tracking Test",interval: 3)do |interval|
0.upto(6)do |index|
# Set the interval's internal index to the current index. This may be safer.interval.update(index)# Only "say" for on-interval ticks through the loop.interval.say("Before Update Interval.",:debug)# Optionally use a block to time a segment.interval.say("Progress Interval Block.")dosleep(0.025)# Do the work here.# Always "say", regardless of interval, in the usual way: with `Say.call`.Say.("Interval-Agnostic Update. Index: #{index}",:info)endinterval.say("After Update Interval.",:debug)endend=[20230604151646]ProgressTrackingTest(i=0) ================================
-- Interval-AgnosticUpdate.Index: 0
-- Interval-AgnosticUpdate.Index: 1
-- Interval-AgnosticUpdate.Index: 2[20230604151646] >> BeforeUpdateInterval.(i=3)=[20230604151646]ProgressIntervalBlock.(i=3) ==============================
-- Interval-AgnosticUpdate.Index: 3=Done(0.0261s) ===============================================================
[20230604151646] >> AfterUpdateInterval.(i=3)
-- Interval-AgnosticUpdate.Index: 4
-- Interval-AgnosticUpdate.Index: 5[20230604151647] >> BeforeUpdateInterval.(i=6)=[20230604151647]ProgressIntervalBlock.(i=6) ==============================
-- Interval-AgnosticUpdate.Index: 6=Done(0.0261s) ===============================================================
[20230604151647] >> AfterUpdateInterval.(i=6)=Done(0.1828s) ===============================================================

Manual

Internally, calling say on a Say::Progress::Interval object uses Say.progress_line to output the given message and index indicator. You may do the same even without an Interval object.

# Given a message. (The default Type is :info.)Say.progress_line("TEST",index: 3)# => [20230604151647] -- TEST (i=3)Say.progress_line("TEST",:success,index: 3)# => [20230604151647] -> TEST (i=3)# Given no message.Say.progress_line(index: 3)# => [20230604151647] ... (i=3)

Namespace Pollution

If you choose to include Say then your class will gain the following instance methods:

  • say
  • say_banner
  • say_footer
  • say_header
  • say_line
  • say_progress
  • say_progress_line
  • say_section
  • say_with_block

... though you probably really only need say, and sometimes: say_progress and/or say_progress_line.

classWithIncludeincludeSayendclassWithoutIncludeendadded_class_methods=WithInclude.methods - WithoutInclude.methodsSay.("Class methods added by `include Say`: #{added_class_methods}")
-- Classmethodsaddedby`include Say`: []added_instance_methods=(WithInclude.new.methods - WithoutInclude.new.methods).sort!Say.("Instance methods added by `include Say`: #{added_instance_methods}")->Instancemethodsaddedby`include Say`: [:say,:say_banner,:say_footer,:say_header,:say_line,:say_progress,:say_progress_line,:say_section,:say_with_block]

Integration

iTerm2

The standardized nature of Say's logging methods lends itself well to highlighting output types using iTerm2's Text Highlighting Triggers. To set this up, go to Settings for iTerm2 -> Profiles -> Advanced -> Triggers section: "Edit" Button.

iTerm2 Triggers Setup

For more help, see iTerm's documentation on Triggers.

The regular expressions and HEX codes used in the screenshot are:

(?<=^ )->(?= )# Text HEX: #0ae400 Background HEX: transparent(?<=^ )>>(?= )# Text HEX: #ff6500 Background HEX: transparent(?<=^ )--(?= )# Text HEX: #ffffff Background HEX: #666666(?<=^ )!¡(?= )# Text HEX: #ffff00 Background HEX: transparent.*\*{2,}.* # Text HEX: #ffffff Background HEX: #ff0000

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake test to run the tests. Or, run rake to run the tests plus linters as well as yard (to confirm proper YARD documentation practices). You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install.

Testing

To test this gem:

rake

Linters

rubocop
reek
npx prettier . --check
npx prettier . --write

Releases

To release a new version of this gem to RubyGems:

  1. Update the version number in version.rb
  2. Update CHANGELOG.md
  3. Run bundle to update Gemfile.lock with the latest version info
  4. Commit the changes. e.g. Bump to vX.Y.Z
  5. Run rake release, which will create a git tag for the version, push git commits and the created tag, and then attempt to push the .gem file to rubygems.org.
  • NOTE: This will fail, as there is already a say gem and this one is not it. This gem must be installed form github source.

Documentation

YARD documentation can be generated and viewed live:

  1. Install YARD: gem install yard
  2. Run the YARD server: yard server --reload
  3. Open the live documentation site: open http://localhost:8808

While the YARD server is running, documentation in the live site will be auto-updated on source code save (and site reload).

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/pdobb/say. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the Say project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.

About

Say provides logging in the style of ActiveRecord::Migration#say... anywhere!

Resources

Code of conduct

Stars

2 stars

Watchers

1 watching

Forks

Releases

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Say

Gem VersionCI Actions

Say gives you the API and the output style you already know and love from ActiveRecord::Migration#say... anywhere! Plus a few extra goodies for long-running processes like incremental progress indicators and remaining time estimation.

Installation

Add this line to your application's Gemfile:

gem"say",github: "pdobb/say"# Not published to RubyGems.

And then execute:

bundle

Compatibility

Tested MRI Ruby Versions:

  • 3.1
  • 3.2
  • 3.3
  • 3.4
  • 4.0

For Ruby 2.7 support, install say gem version 0.5.2.

gem"say",github: "pdobb/say",tag: "v0.5.2"# Not published to RubyGems.

Say has no other dependencies.

Usage

Say.<method>

Typical usage is to just call Say.<method> directly, though it is possible to include Say if you prefer. (See below.)

When called with a block, say will output both Start and Finish banners, then return the result of the block to the caller. When called without a block, say will output a string of the specified type (defaults to :success).

require"say"classDirectAccessProcessordefrunSay.("DirectAccessProcessor"){Say.("Successfully did the thing!")Say.()# Or: Say.callSay.("Debug details about this ...",:debug)Say.("Info about stuff ...",:info)Say.("Maybe look into this thing ...",:warn)Say.("Failed to do a thing ...",:error)"The Result!"}endendresult=DirectAccessProcessor.new.run=DirectAccessProcessor ========================================================
->Successfullydidthe thing!
...
>> Debugdetailsaboutthis ...
-- Infoaboutstuff ...
!¡Maybelookintothisthing ...
** Failedtodoathing ...
=Done(0.0001s) ===============================================================
# => "The Result!"

include Say

Use include Say to gain access to the instance-level say methods.

require"say"classIncludeProcessorincludeSaydefrunsay("IncludeProcessor"){say("Successfully did the thing!")saysay("Debug details about this ...",:debug)say("Info about stuff ...",:info)say("Maybe look into this thing ...",:warn)say("Failed to do a thing ...",:error)"The Result!"}endendresult=IncludeProcessor.new.run=IncludeProcessor =============================================================
-> Successfullydidthe thing!
...
>> Debugdetailsaboutthis ...
-- Infoaboutstuff ...
!¡Maybelookintothisthing ...
** Failedtodoathing ...
=Done(0.0001s) ===============================================================
# => "The Result!"

Say Types

When using Say.(<message>, <type>), the available types and output representations are:

TypeOutput Prefix
:debug" >> "
:error" ** "
:info" -- "
:success" -> "
:warn" !¡ "
Say.debug("TEST")# => " >> TEST"Say.error("TEST")# => " ** TEST"Say.info("TEST")# => " -- TEST"Say.success("TEST")# => " -> TEST"Say.warn("TEST")# => " !¡ TEST"

The default type if call is used is :success.

Say.call("TEST")# => " -> TEST"Say.("TEST")# => " -> TEST"

say(<message>, <type>) Methods

The include Say alternatives for each of the <type> calls in the previous section are:

Say.("TEST",:debug)# => " >> TEST"Say.("TEST",:error)# => " ** TEST"Say.("TEST",:info)# => " -- TEST"Say.("TEST",:success)# => " -> TEST"Say.("TEST",:warn)# => " !¡ TEST"

Say.hr (Horizontal Rule)

Use Say.hr for thin, 1-line separators + padding on top/bottom.

Say.info("Before")Say.hrSay.("After")
-- Before
--------------------------------------------------------------------------------
->After

Horizontal Rule -- Customization

The fill line, template, and length can all be customized:

Say.info("Before")Say.hr("-*",template: "%s",columns: 20)# `template` defaults to: `"\n%s\n"Say.("After")
-- Before
-*-*-*-*-*-*-*-*-*-*
->After

Template can also include book-ends:

Say.hr("-*",template: "|%s|")
|-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*|

Say.section

Use Say.section for 3-line banners to really visually split up your output into major sections.

Say.section
================================================================================
================================================================================
================================================================================
Say.section("TEST")
================================================================================
=TEST =========================================================================
================================================================================
Say.section("TEST",columns: 0)
========
=TEST=
========

Justifiers

The various banner-producing methods also support left/center/right justification. Just pass in e.g. justify: :left, justify: :center, or justify: :right. The default, if nothing is supplied, is justify: :left.

# BlockSay.("Hello, World!"){Say.("Huzzah!")}=Hello,World! ================================================================
-> Huzzah!=Done(0.0000s) ===============================================================
Say.("Hello, World!",justify: :left){Say.("Huzzah!")}=Hello,World! ================================================================
-> Huzzah!=Done(0.0000s) ===============================================================
Say.("Hello, World!",justify: :center){Say.("Huzzah!")}
================================= Hello,World!================================
-> Huzzah!
================================ Done(0.0000s) ================================
Say.("Hello, World!",justify: :right){Say.("Huzzah!")}================================================================ Hello,World!=-> Huzzah!
=============================================================== Done(0.0000s)=# BannerSay.banner("TEST")=TEST =========================================================================Say.banner("TEST",justify: :left)=TEST =========================================================================Say.banner("TEST",justify: :center)
=====================================TEST =====================================Say.banner("TEST",justify: :right)
=========================================================================TEST=# HeaderSay.header("TEST")=TEST =========================================================================Say.header("TEST",justify: :left)=TEST =========================================================================Say.header("TEST",justify: :center)
=====================================TEST =====================================Say.header("TEST",justify: :right)
=========================================================================TEST=# FooterSay.footer=Done =========================================================================Say.footer(justify: :left)=Done =========================================================================Say.footer(justify: :center)
=====================================Done =====================================Say.footer(justify: :right)
=========================================================================Done=# SectionSay.section("TEST")
================================================================================
=TEST =========================================================================
================================================================================
Say.section("TEST",justify: :left)
================================================================================
=TEST =========================================================================
================================================================================
Say.section("TEST",justify: :center)
================================================================================
=====================================TEST =====================================
================================================================================
Say.section("TEST",justify: :right)
================================================================================
=========================================================================TEST=
================================================================================

NOTE: The "line" methods will ignore justification attempts as there is no built in concept of columns for these.

Say.("TEST",justify: :right)# `justify: :right` is ignored.->TEST

Advanced Usage

All of the above examples are using the default interpolation template accessed via the Say.<method> methods. For advanced usage, one may access the Say::InterpolationTemplate directly and either use the predefined templates or specify their own.

Predefined interpolation templates

# :double_lineinterpolation_template=Say::InterpolationTemplate::Builder.double_lineinterpolation_template.inspect# => "['=', ...]{}['=', ...]"interpolation_template.interpolate# => "=="interpolation_template.left_justify(length: 20)# => "===================="# :title (the default template, if none is specified)interpolation_template=Say::InterpolationTemplate::Builder.titleinterpolation_template.inspect# => ['=', ...] {} ['=', ...]interpolation_template.interpolate("TEST")# => "= TEST ="interpolation_template.left_justify("TEST",length: 20)# => "= TEST ============="interpolation_template.center_justify("TEST",length: 20)# => "======= TEST ======="interpolation_template.right_justify("TEST",length: 20)# => "============= TEST ="# :wtfinterpolation_template=Say::InterpolationTemplate::Builder.wtfinterpolation_template.inspect# => "['?', ...] {} ['?', ...]"interpolation_template.interpolate("TEST")# => "? TEST ?"interpolation_template.left_justify("TEST",length: 20)# => "? TEST ?????????????"interpolation_template.center_justify("TEST",length: 20)# => "??????? TEST ???????"interpolation_template.right_justify("TEST",length: 20)# => "????????????? TEST ?"

Custom interpolation templates

# Example 1interpolation_template=Say::InterpolationTemplate.new(left_bookend: "╰(⇀︿⇀)つ-]═",left_fill: "-",right_fill: "-")interpolation_template.inspect# => "╰(⇀︿⇀)つ-]═['-', ...]{}['-', ...]"interpolation_template.interpolate("TEST")# => "╰(⇀︿⇀)つ-]═-TEST-"interpolation_template.left_justify("TEST",length: 40)# => "╰(⇀︿⇀)つ-]═-TEST-------------------------"interpolation_template.center_justify("TEST",length: 40)# => "╰(⇀︿⇀)つ-]═--------TEST------------------"interpolation_template.right_justify("TEST",length: 40)# => "╰(⇀︿⇀)つ-]═-------------------------TEST-"# Example 2interpolation_template=Say::InterpolationTemplate.new(left_bookend: "( •_•)O*¯",left_fill: "`·.·´",right_fill: "`·.·´",right_bookend: "¯°Q(•_• )")interpolation_template.inspect# => "( •_•)O*¯['`·.·´', ...]{}['`·.·´', ...]¯°Q(•_• )"interpolation_template.interpolate("TEST")# => "( •_•)O*¯`·.·´TEST`·.·´¯°Q(•_• )"interpolation_template.left_justify("TEST")# => "( •_•)O*¯`·.·´TEST`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.¯°Q(•_• )"interpolation_template.center_justify("TEST")# => "( •_•)O*¯`·.·´`·.·´`·.·´`·.·´`·.·`·.·´TEST`·.·´`·.·´`·.·´`·.·´`·.·´`·.·¯°Q(•_• )"interpolation_template.right_justify("TEST")# => "( •_•)O*¯`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.`·.·´TEST`·.·´¯°Q(•_• )"

Progress Tracking

Use Say.progress to track long-running processing loops on a given interval. Set the interval to receive say updates only during on-interval ticks through the loop. The default interval is 1, meaning every loop is considered on-interval.

Simple

# The default interval is 1.Say.progressdo |interval|
3.timesdo# Increment the interval's internal index by 1.interval.updateinterval.say("Test",:debug)endend=[20230604151646]Start(i=0) =================================================[20230604151646] >> Test(i=1)[20230604151646] >> Test(i=2)[20230604151646] >> Test(i=3)=Done(0.0000s) ===============================================================

Advanced

Say.progress("Progress Tracking Test",interval: 3)do |interval|
0.upto(6)do |index|
# Set the interval's internal index to the current index. This may be safer.interval.update(index)# Only "say" for on-interval ticks through the loop.interval.say("Before Update Interval.",:debug)# Optionally use a block to time a segment.interval.say("Progress Interval Block.")dosleep(0.025)# Do the work here.# Always "say", regardless of interval, in the usual way: with `Say.call`.Say.("Interval-Agnostic Update. Index: #{index}",:info)endinterval.say("After Update Interval.",:debug)endend=[20230604151646]ProgressTrackingTest(i=0) ================================
-- Interval-AgnosticUpdate.Index: 0
-- Interval-AgnosticUpdate.Index: 1
-- Interval-AgnosticUpdate.Index: 2[20230604151646] >> BeforeUpdateInterval.(i=3)=[20230604151646]ProgressIntervalBlock.(i=3) ==============================
-- Interval-AgnosticUpdate.Index: 3=Done(0.0261s) ===============================================================
[20230604151646] >> AfterUpdateInterval.(i=3)
-- Interval-AgnosticUpdate.Index: 4
-- Interval-AgnosticUpdate.Index: 5[20230604151647] >> BeforeUpdateInterval.(i=6)=[20230604151647]ProgressIntervalBlock.(i=6) ==============================
-- Interval-AgnosticUpdate.Index: 6=Done(0.0261s) ===============================================================
[20230604151647] >> AfterUpdateInterval.(i=6)=Done(0.1828s) ===============================================================

Manual

Internally, calling say on a Say::Progress::Interval object uses Say.progress_line to output the given message and index indicator. You may do the same even without an Interval object.

# Given a message. (The default Type is :info.)Say.progress_line("TEST",index: 3)# => [20230604151647] -- TEST (i=3)Say.progress_line("TEST",:success,index: 3)# => [20230604151647] -> TEST (i=3)# Given no message.Say.progress_line(index: 3)# => [20230604151647] ... (i=3)

Namespace Pollution

If you choose to include Say then your class will gain the following instance methods:

  • say
  • say_banner
  • say_footer
  • say_header
  • say_line
  • say_progress
  • say_progress_line
  • say_section
  • say_with_block

... though you probably really only need say, and sometimes: say_progress and/or say_progress_line.

classWithIncludeincludeSayendclassWithoutIncludeendadded_class_methods=WithInclude.methods - WithoutInclude.methodsSay.("Class methods added by `include Say`: #{added_class_methods}")
-- Classmethodsaddedby`include Say`: []added_instance_methods=(WithInclude.new.methods - WithoutInclude.new.methods).sort!Say.("Instance methods added by `include Say`: #{added_instance_methods}")->Instancemethodsaddedby`include Say`: [:say,:say_banner,:say_footer,:say_header,:say_line,:say_progress,:say_progress_line,:say_section,:say_with_block]

Integration

iTerm2

The standardized nature of Say's logging methods lends itself well to highlighting output types using iTerm2's Text Highlighting Triggers. To set this up, go to Settings for iTerm2 -> Profiles -> Advanced -> Triggers section: "Edit" Button.

iTerm2 Triggers Setup

For more help, see iTerm's documentation on Triggers.

The regular expressions and HEX codes used in the screenshot are:

(?<=^ )->(?= )# Text HEX: #0ae400 Background HEX: transparent(?<=^ )>>(?= )# Text HEX: #ff6500 Background HEX: transparent(?<=^ )--(?= )# Text HEX: #ffffff Background HEX: #666666(?<=^ )!¡(?= )# Text HEX: #ffff00 Background HEX: transparent.*\*{2,}.* # Text HEX: #ffffff Background HEX: #ff0000

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake test to run the tests. Or, run rake to run the tests plus linters as well as yard (to confirm proper YARD documentation practices). You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install.

Testing

To test this gem:

rake

Linters

rubocop
reek
npx prettier . --check
npx prettier . --write

Releases

To release a new version of this gem to RubyGems:

  1. Update the version number in version.rb
  2. Update CHANGELOG.md
  3. Run bundle to update Gemfile.lock with the latest version info
  4. Commit the changes. e.g. Bump to vX.Y.Z
  5. Run rake release, which will create a git tag for the version, push git commits and the created tag, and then attempt to push the .gem file to rubygems.org.
  • NOTE: This will fail, as there is already a say gem and this one is not it. This gem must be installed form github source.

Documentation

YARD documentation can be generated and viewed live:

  1. Install YARD: gem install yard
  2. Run the YARD server: yard server --reload
  3. Open the live documentation site: open http://localhost:8808

While the YARD server is running, documentation in the live site will be auto-updated on source code save (and site reload).

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/pdobb/say. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the Say project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.

About

Say provides logging in the style of ActiveRecord::Migration#say... anywhere!

Resources

Code of conduct

Stars

2 stars

Watchers

1 watching

Forks

Releases

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Say

Gem VersionCI Actions

Say gives you the API and the output style you already know and love from ActiveRecord::Migration#say... anywhere! Plus a few extra goodies for long-running processes like incremental progress indicators and remaining time estimation.

Installation

Add this line to your application's Gemfile:

gem"say",github: "pdobb/say"# Not published to RubyGems.

And then execute:

bundle

Compatibility

Tested MRI Ruby Versions:

  • 3.1
  • 3.2
  • 3.3
  • 3.4
  • 4.0

For Ruby 2.7 support, install say gem version 0.5.2.

gem"say",github: "pdobb/say",tag: "v0.5.2"# Not published to RubyGems.

Say has no other dependencies.

Usage

Say.<method>

Typical usage is to just call Say.<method> directly, though it is possible to include Say if you prefer. (See below.)

When called with a block, say will output both Start and Finish banners, then return the result of the block to the caller. When called without a block, say will output a string of the specified type (defaults to :success).

require"say"classDirectAccessProcessordefrunSay.("DirectAccessProcessor"){Say.("Successfully did the thing!")Say.()# Or: Say.callSay.("Debug details about this ...",:debug)Say.("Info about stuff ...",:info)Say.("Maybe look into this thing ...",:warn)Say.("Failed to do a thing ...",:error)"The Result!"}endendresult=DirectAccessProcessor.new.run=DirectAccessProcessor ========================================================
->Successfullydidthe thing!
...
>> Debugdetailsaboutthis ...
-- Infoaboutstuff ...
!¡Maybelookintothisthing ...
** Failedtodoathing ...
=Done(0.0001s) ===============================================================
# => "The Result!"

include Say

Use include Say to gain access to the instance-level say methods.

require"say"classIncludeProcessorincludeSaydefrunsay("IncludeProcessor"){say("Successfully did the thing!")saysay("Debug details about this ...",:debug)say("Info about stuff ...",:info)say("Maybe look into this thing ...",:warn)say("Failed to do a thing ...",:error)"The Result!"}endendresult=IncludeProcessor.new.run=IncludeProcessor =============================================================
-> Successfullydidthe thing!
...
>> Debugdetailsaboutthis ...
-- Infoaboutstuff ...
!¡Maybelookintothisthing ...
** Failedtodoathing ...
=Done(0.0001s) ===============================================================
# => "The Result!"

Say Types

When using Say.(<message>, <type>), the available types and output representations are:

TypeOutput Prefix
:debug" >> "
:error" ** "
:info" -- "
:success" -> "
:warn" !¡ "
Say.debug("TEST")# => " >> TEST"Say.error("TEST")# => " ** TEST"Say.info("TEST")# => " -- TEST"Say.success("TEST")# => " -> TEST"Say.warn("TEST")# => " !¡ TEST"

The default type if call is used is :success.

Say.call("TEST")# => " -> TEST"Say.("TEST")# => " -> TEST"

say(<message>, <type>) Methods

The include Say alternatives for each of the <type> calls in the previous section are:

Say.("TEST",:debug)# => " >> TEST"Say.("TEST",:error)# => " ** TEST"Say.("TEST",:info)# => " -- TEST"Say.("TEST",:success)# => " -> TEST"Say.("TEST",:warn)# => " !¡ TEST"

Say.hr (Horizontal Rule)

Use Say.hr for thin, 1-line separators + padding on top/bottom.

Say.info("Before")Say.hrSay.("After")
-- Before
--------------------------------------------------------------------------------
->After

Horizontal Rule -- Customization

The fill line, template, and length can all be customized:

Say.info("Before")Say.hr("-*",template: "%s",columns: 20)# `template` defaults to: `"\n%s\n"Say.("After")
-- Before
-*-*-*-*-*-*-*-*-*-*
->After

Template can also include book-ends:

Say.hr("-*",template: "|%s|")
|-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*|

Say.section

Use Say.section for 3-line banners to really visually split up your output into major sections.

Say.section
================================================================================
================================================================================
================================================================================
Say.section("TEST")
================================================================================
=TEST =========================================================================
================================================================================
Say.section("TEST",columns: 0)
========
=TEST=
========

Justifiers

The various banner-producing methods also support left/center/right justification. Just pass in e.g. justify: :left, justify: :center, or justify: :right. The default, if nothing is supplied, is justify: :left.

# BlockSay.("Hello, World!"){Say.("Huzzah!")}=Hello,World! ================================================================
-> Huzzah!=Done(0.0000s) ===============================================================
Say.("Hello, World!",justify: :left){Say.("Huzzah!")}=Hello,World! ================================================================
-> Huzzah!=Done(0.0000s) ===============================================================
Say.("Hello, World!",justify: :center){Say.("Huzzah!")}
================================= Hello,World!================================
-> Huzzah!
================================ Done(0.0000s) ================================
Say.("Hello, World!",justify: :right){Say.("Huzzah!")}================================================================ Hello,World!=-> Huzzah!
=============================================================== Done(0.0000s)=# BannerSay.banner("TEST")=TEST =========================================================================Say.banner("TEST",justify: :left)=TEST =========================================================================Say.banner("TEST",justify: :center)
=====================================TEST =====================================Say.banner("TEST",justify: :right)
=========================================================================TEST=# HeaderSay.header("TEST")=TEST =========================================================================Say.header("TEST",justify: :left)=TEST =========================================================================Say.header("TEST",justify: :center)
=====================================TEST =====================================Say.header("TEST",justify: :right)
=========================================================================TEST=# FooterSay.footer=Done =========================================================================Say.footer(justify: :left)=Done =========================================================================Say.footer(justify: :center)
=====================================Done =====================================Say.footer(justify: :right)
=========================================================================Done=# SectionSay.section("TEST")
================================================================================
=TEST =========================================================================
================================================================================
Say.section("TEST",justify: :left)
================================================================================
=TEST =========================================================================
================================================================================
Say.section("TEST",justify: :center)
================================================================================
=====================================TEST =====================================
================================================================================
Say.section("TEST",justify: :right)
================================================================================
=========================================================================TEST=
================================================================================

NOTE: The "line" methods will ignore justification attempts as there is no built in concept of columns for these.

Say.("TEST",justify: :right)# `justify: :right` is ignored.->TEST

Advanced Usage

All of the above examples are using the default interpolation template accessed via the Say.<method> methods. For advanced usage, one may access the Say::InterpolationTemplate directly and either use the predefined templates or specify their own.

Predefined interpolation templates

# :double_lineinterpolation_template=Say::InterpolationTemplate::Builder.double_lineinterpolation_template.inspect# => "['=', ...]{}['=', ...]"interpolation_template.interpolate# => "=="interpolation_template.left_justify(length: 20)# => "===================="# :title (the default template, if none is specified)interpolation_template=Say::InterpolationTemplate::Builder.titleinterpolation_template.inspect# => ['=', ...] {} ['=', ...]interpolation_template.interpolate("TEST")# => "= TEST ="interpolation_template.left_justify("TEST",length: 20)# => "= TEST ============="interpolation_template.center_justify("TEST",length: 20)# => "======= TEST ======="interpolation_template.right_justify("TEST",length: 20)# => "============= TEST ="# :wtfinterpolation_template=Say::InterpolationTemplate::Builder.wtfinterpolation_template.inspect# => "['?', ...] {} ['?', ...]"interpolation_template.interpolate("TEST")# => "? TEST ?"interpolation_template.left_justify("TEST",length: 20)# => "? TEST ?????????????"interpolation_template.center_justify("TEST",length: 20)# => "??????? TEST ???????"interpolation_template.right_justify("TEST",length: 20)# => "????????????? TEST ?"

Custom interpolation templates

# Example 1interpolation_template=Say::InterpolationTemplate.new(left_bookend: "╰(⇀︿⇀)つ-]═",left_fill: "-",right_fill: "-")interpolation_template.inspect# => "╰(⇀︿⇀)つ-]═['-', ...]{}['-', ...]"interpolation_template.interpolate("TEST")# => "╰(⇀︿⇀)つ-]═-TEST-"interpolation_template.left_justify("TEST",length: 40)# => "╰(⇀︿⇀)つ-]═-TEST-------------------------"interpolation_template.center_justify("TEST",length: 40)# => "╰(⇀︿⇀)つ-]═--------TEST------------------"interpolation_template.right_justify("TEST",length: 40)# => "╰(⇀︿⇀)つ-]═-------------------------TEST-"# Example 2interpolation_template=Say::InterpolationTemplate.new(left_bookend: "( •_•)O*¯",left_fill: "`·.·´",right_fill: "`·.·´",right_bookend: "¯°Q(•_• )")interpolation_template.inspect# => "( •_•)O*¯['`·.·´', ...]{}['`·.·´', ...]¯°Q(•_• )"interpolation_template.interpolate("TEST")# => "( •_•)O*¯`·.·´TEST`·.·´¯°Q(•_• )"interpolation_template.left_justify("TEST")# => "( •_•)O*¯`·.·´TEST`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.¯°Q(•_• )"interpolation_template.center_justify("TEST")# => "( •_•)O*¯`·.·´`·.·´`·.·´`·.·´`·.·`·.·´TEST`·.·´`·.·´`·.·´`·.·´`·.·´`·.·¯°Q(•_• )"interpolation_template.right_justify("TEST")# => "( •_•)O*¯`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.`·.·´TEST`·.·´¯°Q(•_• )"

Progress Tracking

Use Say.progress to track long-running processing loops on a given interval. Set the interval to receive say updates only during on-interval ticks through the loop. The default interval is 1, meaning every loop is considered on-interval.

Simple

# The default interval is 1.Say.progressdo |interval|
3.timesdo# Increment the interval's internal index by 1.interval.updateinterval.say("Test",:debug)endend=[20230604151646]Start(i=0) =================================================[20230604151646] >> Test(i=1)[20230604151646] >> Test(i=2)[20230604151646] >> Test(i=3)=Done(0.0000s) ===============================================================

Advanced

Say.progress("Progress Tracking Test",interval: 3)do |interval|
0.upto(6)do |index|
# Set the interval's internal index to the current index. This may be safer.interval.update(index)# Only "say" for on-interval ticks through the loop.interval.say("Before Update Interval.",:debug)# Optionally use a block to time a segment.interval.say("Progress Interval Block.")dosleep(0.025)# Do the work here.# Always "say", regardless of interval, in the usual way: with `Say.call`.Say.("Interval-Agnostic Update. Index: #{index}",:info)endinterval.say("After Update Interval.",:debug)endend=[20230604151646]ProgressTrackingTest(i=0) ================================
-- Interval-AgnosticUpdate.Index: 0
-- Interval-AgnosticUpdate.Index: 1
-- Interval-AgnosticUpdate.Index: 2[20230604151646] >> BeforeUpdateInterval.(i=3)=[20230604151646]ProgressIntervalBlock.(i=3) ==============================
-- Interval-AgnosticUpdate.Index: 3=Done(0.0261s) ===============================================================
[20230604151646] >> AfterUpdateInterval.(i=3)
-- Interval-AgnosticUpdate.Index: 4
-- Interval-AgnosticUpdate.Index: 5[20230604151647] >> BeforeUpdateInterval.(i=6)=[20230604151647]ProgressIntervalBlock.(i=6) ==============================
-- Interval-AgnosticUpdate.Index: 6=Done(0.0261s) ===============================================================
[20230604151647] >> AfterUpdateInterval.(i=6)=Done(0.1828s) ===============================================================

Manual

Internally, calling say on a Say::Progress::Interval object uses Say.progress_line to output the given message and index indicator. You may do the same even without an Interval object.

# Given a message. (The default Type is :info.)Say.progress_line("TEST",index: 3)# => [20230604151647] -- TEST (i=3)Say.progress_line("TEST",:success,index: 3)# => [20230604151647] -> TEST (i=3)# Given no message.Say.progress_line(index: 3)# => [20230604151647] ... (i=3)

Namespace Pollution

If you choose to include Say then your class will gain the following instance methods:

  • say
  • say_banner
  • say_footer
  • say_header
  • say_line
  • say_progress
  • say_progress_line
  • say_section
  • say_with_block

... though you probably really only need say, and sometimes: say_progress and/or say_progress_line.

classWithIncludeincludeSayendclassWithoutIncludeendadded_class_methods=WithInclude.methods - WithoutInclude.methodsSay.("Class methods added by `include Say`: #{added_class_methods}")
-- Classmethodsaddedby`include Say`: []added_instance_methods=(WithInclude.new.methods - WithoutInclude.new.methods).sort!Say.("Instance methods added by `include Say`: #{added_instance_methods}")->Instancemethodsaddedby`include Say`: [:say,:say_banner,:say_footer,:say_header,:say_line,:say_progress,:say_progress_line,:say_section,:say_with_block]

Integration

iTerm2

The standardized nature of Say's logging methods lends itself well to highlighting output types using iTerm2's Text Highlighting Triggers. To set this up, go to Settings for iTerm2 -> Profiles -> Advanced -> Triggers section: "Edit" Button.

iTerm2 Triggers Setup

For more help, see iTerm's documentation on Triggers.

The regular expressions and HEX codes used in the screenshot are:

(?<=^ )->(?= )# Text HEX: #0ae400 Background HEX: transparent(?<=^ )>>(?= )# Text HEX: #ff6500 Background HEX: transparent(?<=^ )--(?= )# Text HEX: #ffffff Background HEX: #666666(?<=^ )!¡(?= )# Text HEX: #ffff00 Background HEX: transparent.*\*{2,}.* # Text HEX: #ffffff Background HEX: #ff0000

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake test to run the tests. Or, run rake to run the tests plus linters as well as yard (to confirm proper YARD documentation practices). You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install.

Testing

To test this gem:

rake

Linters

rubocop
reek
npx prettier . --check
npx prettier . --write

Releases

To release a new version of this gem to RubyGems:

  1. Update the version number in version.rb
  2. Update CHANGELOG.md
  3. Run bundle to update Gemfile.lock with the latest version info
  4. Commit the changes. e.g. Bump to vX.Y.Z
  5. Run rake release, which will create a git tag for the version, push git commits and the created tag, and then attempt to push the .gem file to rubygems.org.
  • NOTE: This will fail, as there is already a say gem and this one is not it. This gem must be installed form github source.

Documentation

YARD documentation can be generated and viewed live:

  1. Install YARD: gem install yard
  2. Run the YARD server: yard server --reload
  3. Open the live documentation site: open http://localhost:8808

While the YARD server is running, documentation in the live site will be auto-updated on source code save (and site reload).

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/pdobb/say. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the Say project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.

About

Say provides logging in the style of ActiveRecord::Migration#say... anywhere!

Resources

Code of conduct

Stars

2 stars

Watchers

1 watching

Forks

Releases

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

Say

Gem VersionCI Actions

Say gives you the API and the output style you already know and love from ActiveRecord::Migration#say... anywhere! Plus a few extra goodies for long-running processes like incremental progress indicators and remaining time estimation.

Installation

Add this line to your application's Gemfile:

gem"say",github: "pdobb/say"# Not published to RubyGems.

And then execute:

bundle

Compatibility

Tested MRI Ruby Versions:

  • 3.1
  • 3.2
  • 3.3
  • 3.4
  • 4.0

For Ruby 2.7 support, install say gem version 0.5.2.

gem"say",github: "pdobb/say",tag: "v0.5.2"# Not published to RubyGems.

Say has no other dependencies.

Usage

Say.<method>

Typical usage is to just call Say.<method> directly, though it is possible to include Say if you prefer. (See below.)

When called with a block, say will output both Start and Finish banners, then return the result of the block to the caller. When called without a block, say will output a string of the specified type (defaults to :success).

require"say"classDirectAccessProcessordefrunSay.("DirectAccessProcessor"){Say.("Successfully did the thing!")Say.()# Or: Say.callSay.("Debug details about this ...",:debug)Say.("Info about stuff ...",:info)Say.("Maybe look into this thing ...",:warn)Say.("Failed to do a thing ...",:error)"The Result!"}endendresult=DirectAccessProcessor.new.run=DirectAccessProcessor ========================================================
->Successfullydidthe thing!
...
>> Debugdetailsaboutthis ...
-- Infoaboutstuff ...
!¡Maybelookintothisthing ...
** Failedtodoathing ...
=Done(0.0001s) ===============================================================
# => "The Result!"

include Say

Use include Say to gain access to the instance-level say methods.

require"say"classIncludeProcessorincludeSaydefrunsay("IncludeProcessor"){say("Successfully did the thing!")saysay("Debug details about this ...",:debug)say("Info about stuff ...",:info)say("Maybe look into this thing ...",:warn)say("Failed to do a thing ...",:error)"The Result!"}endendresult=IncludeProcessor.new.run=IncludeProcessor =============================================================
-> Successfullydidthe thing!
...
>> Debugdetailsaboutthis ...
-- Infoaboutstuff ...
!¡Maybelookintothisthing ...
** Failedtodoathing ...
=Done(0.0001s) ===============================================================
# => "The Result!"

Say Types

When using Say.(<message>, <type>), the available types and output representations are:

TypeOutput Prefix
:debug" >> "
:error" ** "
:info" -- "
:success" -> "
:warn" !¡ "
Say.debug("TEST")# => " >> TEST"Say.error("TEST")# => " ** TEST"Say.info("TEST")# => " -- TEST"Say.success("TEST")# => " -> TEST"Say.warn("TEST")# => " !¡ TEST"

The default type if call is used is :success.

Say.call("TEST")# => " -> TEST"Say.("TEST")# => " -> TEST"

say(<message>, <type>) Methods

The include Say alternatives for each of the <type> calls in the previous section are:

Say.("TEST",:debug)# => " >> TEST"Say.("TEST",:error)# => " ** TEST"Say.("TEST",:info)# => " -- TEST"Say.("TEST",:success)# => " -> TEST"Say.("TEST",:warn)# => " !¡ TEST"

Say.hr (Horizontal Rule)

Use Say.hr for thin, 1-line separators + padding on top/bottom.

Say.info("Before")Say.hrSay.("After")
-- Before
--------------------------------------------------------------------------------
->After

Horizontal Rule -- Customization

The fill line, template, and length can all be customized:

Say.info("Before")Say.hr("-*",template: "%s",columns: 20)# `template` defaults to: `"\n%s\n"Say.("After")
-- Before
-*-*-*-*-*-*-*-*-*-*
->After

Template can also include book-ends:

Say.hr("-*",template: "|%s|")
|-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*|

Say.section

Use Say.section for 3-line banners to really visually split up your output into major sections.

Say.section
================================================================================
================================================================================
================================================================================
Say.section("TEST")
================================================================================
=TEST =========================================================================
================================================================================
Say.section("TEST",columns: 0)
========
=TEST=
========

Justifiers

The various banner-producing methods also support left/center/right justification. Just pass in e.g. justify: :left, justify: :center, or justify: :right. The default, if nothing is supplied, is justify: :left.

# BlockSay.("Hello, World!"){Say.("Huzzah!")}=Hello,World! ================================================================
-> Huzzah!=Done(0.0000s) ===============================================================
Say.("Hello, World!",justify: :left){Say.("Huzzah!")}=Hello,World! ================================================================
-> Huzzah!=Done(0.0000s) ===============================================================
Say.("Hello, World!",justify: :center){Say.("Huzzah!")}
================================= Hello,World!================================
-> Huzzah!
================================ Done(0.0000s) ================================
Say.("Hello, World!",justify: :right){Say.("Huzzah!")}================================================================ Hello,World!=-> Huzzah!
=============================================================== Done(0.0000s)=# BannerSay.banner("TEST")=TEST =========================================================================Say.banner("TEST",justify: :left)=TEST =========================================================================Say.banner("TEST",justify: :center)
=====================================TEST =====================================Say.banner("TEST",justify: :right)
=========================================================================TEST=# HeaderSay.header("TEST")=TEST =========================================================================Say.header("TEST",justify: :left)=TEST =========================================================================Say.header("TEST",justify: :center)
=====================================TEST =====================================Say.header("TEST",justify: :right)
=========================================================================TEST=# FooterSay.footer=Done =========================================================================Say.footer(justify: :left)=Done =========================================================================Say.footer(justify: :center)
=====================================Done =====================================Say.footer(justify: :right)
=========================================================================Done=# SectionSay.section("TEST")
================================================================================
=TEST =========================================================================
================================================================================
Say.section("TEST",justify: :left)
================================================================================
=TEST =========================================================================
================================================================================
Say.section("TEST",justify: :center)
================================================================================
=====================================TEST =====================================
================================================================================
Say.section("TEST",justify: :right)
================================================================================
=========================================================================TEST=
================================================================================

NOTE: The "line" methods will ignore justification attempts as there is no built in concept of columns for these.

Say.("TEST",justify: :right)# `justify: :right` is ignored.->TEST

Advanced Usage

All of the above examples are using the default interpolation template accessed via the Say.<method> methods. For advanced usage, one may access the Say::InterpolationTemplate directly and either use the predefined templates or specify their own.

Predefined interpolation templates

# :double_lineinterpolation_template=Say::InterpolationTemplate::Builder.double_lineinterpolation_template.inspect# => "['=', ...]{}['=', ...]"interpolation_template.interpolate# => "=="interpolation_template.left_justify(length: 20)# => "===================="# :title (the default template, if none is specified)interpolation_template=Say::InterpolationTemplate::Builder.titleinterpolation_template.inspect# => ['=', ...] {} ['=', ...]interpolation_template.interpolate("TEST")# => "= TEST ="interpolation_template.left_justify("TEST",length: 20)# => "= TEST ============="interpolation_template.center_justify("TEST",length: 20)# => "======= TEST ======="interpolation_template.right_justify("TEST",length: 20)# => "============= TEST ="# :wtfinterpolation_template=Say::InterpolationTemplate::Builder.wtfinterpolation_template.inspect# => "['?', ...] {} ['?', ...]"interpolation_template.interpolate("TEST")# => "? TEST ?"interpolation_template.left_justify("TEST",length: 20)# => "? TEST ?????????????"interpolation_template.center_justify("TEST",length: 20)# => "??????? TEST ???????"interpolation_template.right_justify("TEST",length: 20)# => "????????????? TEST ?"

Custom interpolation templates

# Example 1interpolation_template=Say::InterpolationTemplate.new(left_bookend: "╰(⇀︿⇀)つ-]═",left_fill: "-",right_fill: "-")interpolation_template.inspect# => "╰(⇀︿⇀)つ-]═['-', ...]{}['-', ...]"interpolation_template.interpolate("TEST")# => "╰(⇀︿⇀)つ-]═-TEST-"interpolation_template.left_justify("TEST",length: 40)# => "╰(⇀︿⇀)つ-]═-TEST-------------------------"interpolation_template.center_justify("TEST",length: 40)# => "╰(⇀︿⇀)つ-]═--------TEST------------------"interpolation_template.right_justify("TEST",length: 40)# => "╰(⇀︿⇀)つ-]═-------------------------TEST-"# Example 2interpolation_template=Say::InterpolationTemplate.new(left_bookend: "( •_•)O*¯",left_fill: "`·.·´",right_fill: "`·.·´",right_bookend: "¯°Q(•_• )")interpolation_template.inspect# => "( •_•)O*¯['`·.·´', ...]{}['`·.·´', ...]¯°Q(•_• )"interpolation_template.interpolate("TEST")# => "( •_•)O*¯`·.·´TEST`·.·´¯°Q(•_• )"interpolation_template.left_justify("TEST")# => "( •_•)O*¯`·.·´TEST`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.¯°Q(•_• )"interpolation_template.center_justify("TEST")# => "( •_•)O*¯`·.·´`·.·´`·.·´`·.·´`·.·`·.·´TEST`·.·´`·.·´`·.·´`·.·´`·.·´`·.·¯°Q(•_• )"interpolation_template.right_justify("TEST")# => "( •_•)O*¯`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.·´`·.`·.·´TEST`·.·´¯°Q(•_• )"

Progress Tracking

Use Say.progress to track long-running processing loops on a given interval. Set the interval to receive say updates only during on-interval ticks through the loop. The default interval is 1, meaning every loop is considered on-interval.

Simple

# The default interval is 1.Say.progressdo |interval|
3.timesdo# Increment the interval's internal index by 1.interval.updateinterval.say("Test",:debug)endend=[20230604151646]Start(i=0) =================================================[20230604151646] >> Test(i=1)[20230604151646] >> Test(i=2)[20230604151646] >> Test(i=3)=Done(0.0000s) ===============================================================

Advanced

Say.progress("Progress Tracking Test",interval: 3)do |interval|
0.upto(6)do |index|
# Set the interval's internal index to the current index. This may be safer.interval.update(index)# Only "say" for on-interval ticks through the loop.interval.say("Before Update Interval.",:debug)# Optionally use a block to time a segment.interval.say("Progress Interval Block.")dosleep(0.025)# Do the work here.# Always "say", regardless of interval, in the usual way: with `Say.call`.Say.("Interval-Agnostic Update. Index: #{index}",:info)endinterval.say("After Update Interval.",:debug)endend=[20230604151646]ProgressTrackingTest(i=0) ================================
-- Interval-AgnosticUpdate.Index: 0
-- Interval-AgnosticUpdate.Index: 1
-- Interval-AgnosticUpdate.Index: 2[20230604151646] >> BeforeUpdateInterval.(i=3)=[20230604151646]ProgressIntervalBlock.(i=3) ==============================
-- Interval-AgnosticUpdate.Index: 3=Done(0.0261s) ===============================================================
[20230604151646] >> AfterUpdateInterval.(i=3)
-- Interval-AgnosticUpdate.Index: 4
-- Interval-AgnosticUpdate.Index: 5[20230604151647] >> BeforeUpdateInterval.(i=6)=[20230604151647]ProgressIntervalBlock.(i=6) ==============================
-- Interval-AgnosticUpdate.Index: 6=Done(0.0261s) ===============================================================
[20230604151647] >> AfterUpdateInterval.(i=6)=Done(0.1828s) ===============================================================

Manual

Internally, calling say on a Say::Progress::Interval object uses Say.progress_line to output the given message and index indicator. You may do the same even without an Interval object.

# Given a message. (The default Type is :info.)Say.progress_line("TEST",index: 3)# => [20230604151647] -- TEST (i=3)Say.progress_line("TEST",:success,index: 3)# => [20230604151647] -> TEST (i=3)# Given no message.Say.progress_line(index: 3)# => [20230604151647] ... (i=3)

Namespace Pollution

If you choose to include Say then your class will gain the following instance methods:

  • say
  • say_banner
  • say_footer
  • say_header
  • say_line
  • say_progress
  • say_progress_line
  • say_section
  • say_with_block

... though you probably really only need say, and sometimes: say_progress and/or say_progress_line.

classWithIncludeincludeSayendclassWithoutIncludeendadded_class_methods=WithInclude.methods - WithoutInclude.methodsSay.("Class methods added by `include Say`: #{added_class_methods}")
-- Classmethodsaddedby`include Say`: []added_instance_methods=(WithInclude.new.methods - WithoutInclude.new.methods).sort!Say.("Instance methods added by `include Say`: #{added_instance_methods}")->Instancemethodsaddedby`include Say`: [:say,:say_banner,:say_footer,:say_header,:say_line,:say_progress,:say_progress_line,:say_section,:say_with_block]

Integration

iTerm2

The standardized nature of Say's logging methods lends itself well to highlighting output types using iTerm2's Text Highlighting Triggers. To set this up, go to Settings for iTerm2 -> Profiles -> Advanced -> Triggers section: "Edit" Button.

iTerm2 Triggers Setup

For more help, see iTerm's documentation on Triggers.

The regular expressions and HEX codes used in the screenshot are:

(?<=^ )->(?= )# Text HEX: #0ae400 Background HEX: transparent(?<=^ )>>(?= )# Text HEX: #ff6500 Background HEX: transparent(?<=^ )--(?= )# Text HEX: #ffffff Background HEX: #666666(?<=^ )!¡(?= )# Text HEX: #ffff00 Background HEX: transparent.*\*{2,}.* # Text HEX: #ffffff Background HEX: #ff0000

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake test to run the tests. Or, run rake to run the tests plus linters as well as yard (to confirm proper YARD documentation practices). You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install.

Testing

To test this gem:

rake

Linters

rubocop
reek
npx prettier . --check
npx prettier . --write

Releases

To release a new version of this gem to RubyGems:

  1. Update the version number in version.rb
  2. Update CHANGELOG.md
  3. Run bundle to update Gemfile.lock with the latest version info
  4. Commit the changes. e.g. Bump to vX.Y.Z
  5. Run rake release, which will create a git tag for the version, push git commits and the created tag, and then attempt to push the .gem file to rubygems.org.
  • NOTE: This will fail, as there is already a say gem and this one is not it. This gem must be installed form github source.

Documentation

YARD documentation can be generated and viewed live:

  1. Install YARD: gem install yard
  2. Run the YARD server: yard server --reload
  3. Open the live documentation site: open http://localhost:8808

While the YARD server is running, documentation in the live site will be auto-updated on source code save (and site reload).

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/pdobb/say. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the Say project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.

About

Say provides logging in the style of ActiveRecord::Migration#say... anywhere!

Resources

Code of conduct

Stars

2 stars

Watchers

1 watching

Forks

Releases

Used by

Contributors

Languages