lib/dev/ui/frame.rb in dev-ui-0.1.0 vs lib/dev/ui/frame.rb in dev-ui-0.1.1
- old
+ new
@@ -1,19 +1,54 @@
require 'dev/ui'
module Dev
module UI
module Frame
+ class UnnestedFrameException < StandardError; end
class << self
DEFAULT_FRAME_COLOR = Dev::UI.resolve_color(:cyan)
+ # Opens a new frame. Can be nested
# Can be invoked in two ways: block and blockless
- # In block form, the frame is closed automatically when the block returns
- # In blockless form, caller MUST call Frame.close when the frame is
- # logically done.
- # blockless form is strongly discouraged in cases where block form can be
- # made to work.
+ # * In block form, the frame is closed automatically when the block returns
+ # * In blockless form, caller MUST call +Frame.close+ when the frame is logically done
+ # * Blockless form is strongly discouraged in cases where block form can be made to work
+ #
+ # https://user-images.githubusercontent.com/3074765/33799861-cb5dcb5c-dd01-11e7-977e-6fad38cee08c.png
+ #
+ # The return value of the block determines if the block is a "success" or a "failure"
+ #
+ # ==== Attributes
+ #
+ # * +text+ - (required) the text/title to output in the frame
+ #
+ # ==== Options
+ #
+ # * +:color+ - The color of the frame. Defaults to +DEFAULT_FRAME_COLOR+
+ # * +:failure_text+ - If the block failed, what do we output? Defaults to nil
+ # * +:success_text+ - If the block succeeds, what do we output? Defaults to nil
+ # * +:timing+ - How long did the frame content take? Invalid for blockless. Defaults to true for the block form
+ #
+ # ==== Example
+ #
+ # ===== Block Form (Assumes +Dev::UI::StdoutRouter.enable+ has been called)
+ #
+ # Dev::UI::Frame.open('Open') { puts 'hi' }
+ #
+ # Output:
+ # ┏━━ Open ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+ # ┃ hi
+ # ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ (0.0s) ━━
+ #
+ # ===== Blockless Form
+ #
+ # Dev::UI::Frame.open('Open')
+ #
+ # Output:
+ # ┏━━ Open ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+ #
+ #
def open(
text,
color: DEFAULT_FRAME_COLOR,
failure_text: nil,
success_text: nil,
@@ -62,10 +97,30 @@
end
end
end
end
+ # Closes a frame
+ # Automatically called for a block-form +open+
+ #
+ # ==== Attributes
+ #
+ # * +text+ - (required) the text/title to output in the frame
+ #
+ # ==== Options
+ #
+ # * +:color+ - The color of the frame. Defaults to +DEFAULT_FRAME_COLOR+
+ # * +:elapsed+ - How long did the frame take? Defaults to nil
+ #
+ # ==== Example
+ #
+ # Dev::UI::Frame.close('Close')
+ #
+ # Output:
+ # ┗━━ Close ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+ #
+ #
def close(text, color: DEFAULT_FRAME_COLOR, elapsed: nil)
color = Dev::UI.resolve_color(color)
FrameStack.pop
kwargs = {}
@@ -75,20 +130,52 @@
Dev::UI.raw do
puts edge(text, color: color, first: Dev::UI::Box::Heavy::BL, **kwargs)
end
end
+ # Adds a divider in a frame
+ # Used to separate information within a single frame
+ #
+ # ==== Attributes
+ #
+ # * +text+ - (required) the text/title to output in the frame
+ #
+ # ==== Options
+ #
+ # * +:color+ - The color of the frame. Defaults to +DEFAULT_FRAME_COLOR+
+ #
+ # ==== Example
+ #
+ # Dev::UI::Frame.open('Open') { Dev::UI::Frame.divider('Divider') }
+ #
+ # Output:
+ # ┏━━ Open ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+ # ┣━━ Divider ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+ # ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+ #
+ # ==== Raises
+ #
+ # MUST be inside an open frame or it raises a +UnnestedFrameException+
+ #
def divider(text, color: nil)
+ fs_item = FrameStack.pop
+ raise UnnestedFrameException, "no frame nesting to unnest" unless fs_item
color = Dev::UI.resolve_color(color)
- item = Dev::UI.resolve_color(FrameStack.pop)
+ item = Dev::UI.resolve_color(fs_item)
Dev::UI.raw do
puts edge(text, color: (color || item), first: Dev::UI::Box::Heavy::DIV)
end
FrameStack.push(item)
end
+ # Determines the prefix of a frame entry taking multi-nested frames into account
+ #
+ # ==== Options
+ #
+ # * +:color+ - The color of the prefix. Defaults to +Thread.current[:devui_frame_color_override]+ or nil
+ #
def prefix(color: nil)
pfx = String.new
items = FrameStack.items
items[0..-2].each do |item|
pfx << Dev::UI.resolve_color(item).code << Dev::UI::Box::Heavy::VERT
@@ -99,18 +186,26 @@
<< Dev::UI::Box::Heavy::VERT << ' ' << Dev::UI::Color::RESET.code
end
pfx
end
+ # Override a color for a given thread.
+ #
+ # ==== Attributes
+ #
+ # * +color+ - The color to override to
+ #
def with_frame_color_override(color)
prev = Thread.current[:devui_frame_color_override]
Thread.current[:devui_frame_color_override] = color
yield
ensure
Thread.current[:devui_frame_color_override] = prev
end
+ # The width of a prefix given the number of Frames in the stack
+ #
def prefix_width
w = FrameStack.items.size
w.zero? ? 0 : w + 1
end
@@ -152,19 +247,42 @@
# next line and call it poor usage of this API.
end
o = String.new
+ is_ci = ![0, '', nil].include?(ENV['CI'])
+
+ # Jumping around the line can cause some unwanted flashes
+ o << Dev::UI::ANSI.hide_cursor
+
+ if is_ci
+ # In CI, we can't use absolute horizontal positions because of timestamps.
+ # So we move around the line by offset from this cursor position.
+ o << Dev::UI::ANSI.cursor_save
+ else
+ # Outside of CI, we reset to column 1 so that things like ^C don't
+ # cause output misformatting.
+ o << "\r"
+ end
+
o << color.code
o << Dev::UI::Box::Heavy::HORZ * termwidth # draw a full line
- o << Dev::UI::ANSI.cursor_horizontal_absolute(1 + prefix_start)
- o << prefix
- o << Dev::UI::ANSI.cursor_horizontal_absolute(1 + suffix_start)
- o << suffix
+ o << print_at_x(prefix_start, prefix, is_ci)
+ o << color.code
+ o << print_at_x(suffix_start, suffix, is_ci)
o << Dev::UI::Color::RESET.code
+ o << Dev::UI::ANSI.show_cursor
o << "\n"
o
+ end
+
+ def print_at_x(x, str, is_ci)
+ if is_ci
+ Dev::UI::ANSI.cursor_restore + Dev::UI::ANSI.cursor_forward(x) + str
+ else
+ Dev::UI::ANSI.cursor_horizontal_absolute(1 + x) + str
+ end
end
module FrameStack
ENVVAR = 'DEV_FRAME_STACK'