paralines 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/paralines.rb +158 -0
  3. metadata +58 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 529c8563443dc4d0433a4e969ad894b76ded11e579a8f1781d859bd004e83464
4
+ data.tar.gz: 4f96bf28dfcc1726a27fe770f558b82b1ae7d77f4216abf818df34c970043fc0
5
+ SHA512:
6
+ metadata.gz: 849db8748f0ffac034e984a3083ff6feab2e238a01b23b8c8ce77059ae804349413870444077d374eab0570e261fcb855b425dd7e94905ff289a9d72affd9527
7
+ data.tar.gz: 87cc010c941f8a029ed86f5a8b1945e5ce37cfbb0661cb90052874b74cb81e023ea68eab46ed2ccd4db06e81100c58f03b135ec6b1c0f8d169a3401eaf916f6c
data/lib/paralines.rb ADDED
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ParaLines
4
+ C_mutex = Mutex.new
5
+ def C_mutex.sync_if_needed
6
+ if owned?
7
+ yield
8
+ else
9
+ synchronize do
10
+ yield
11
+ end
12
+ end
13
+ end
14
+
15
+ def initialize
16
+ set_flags!
17
+ @line_by_key = Hash.new {|h, key| h[key] = {line:h.length, col:1, text:''} }
18
+
19
+ # *ensure flush at exit
20
+ if @f_to_file
21
+ at_exit do
22
+ flush
23
+ end
24
+ end
25
+
26
+ if block_given?
27
+ begin
28
+ yield self
29
+ ensure
30
+ flush
31
+ end
32
+ end
33
+ end
34
+
35
+ # plines << "done (#{n})"
36
+ def << (text)
37
+ key = Thread.current
38
+ output key, text
39
+ end
40
+
41
+ # plines.add_static_line '- 5 workers added'
42
+ def add_static_line(text)
43
+ key = text.object_id
44
+ C_mutex.synchronize do
45
+ d = @line_by_key[key]
46
+ if @f_to_console
47
+ puts text
48
+ else # for file
49
+ d[:text] += text.to_s
50
+ end
51
+ end
52
+ end
53
+
54
+ # done_order_line = plines.add_shared_line 'Done order: '
55
+ # done_order_line << 'some text'
56
+ # part = shared_line.part_open "#{n}… " + later: part.close '+'
57
+ # *can output part progress by adding dots: [LS ] [cloud2… ] --> [LS....] [cloud2… ] — call .close('.' * done_count) multiple times with increasing number of dots
58
+ # *this line can be used by many threads
59
+ def add_shared_line(text)
60
+ key = text.object_id
61
+ output key, text
62
+ # < helper obj with the << and .part_open methods
63
+ Object.new.tap do |o|
64
+ rel = self
65
+ line_by_key = @line_by_key
66
+ f_to_console = @f_to_console
67
+
68
+ o.define_singleton_method :<< do |text|
69
+ rel.send :output, key, text
70
+ end
71
+
72
+ o.define_singleton_method :part_open do |text_|
73
+ d = line_by_key[key]
74
+ part_col = nil
75
+
76
+ C_mutex.synchronize do
77
+ # *we replace placeholder chars like: … or _ or just the last char (order here needed for priority to be able to have _ in text and use … as a placeholder)
78
+ part_col = d[:col] + (text_.index('…') || text_.index('_') || text_.length-1)
79
+
80
+ rel.send :output, key, text_
81
+ end
82
+
83
+ # < helper obj with the .close method
84
+ Object.new.tap do |o|
85
+ o.define_singleton_method :close do |end_text|
86
+ # *print the closing chars in the saved position
87
+ if f_to_console
88
+ rel.send :print_in_line,
89
+ lines_up: line_by_key.count - d[:line],
90
+ col: part_col,
91
+ text: end_text
92
+ else # for file
93
+ d[:text][part_col-1, end_text.length] = end_text
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+
101
+ # plines.flush
102
+ # *needed only when @f_to_file
103
+ # *can be called manually if the block form was not used and all the threads are finished
104
+ def flush
105
+ puts @line_by_key.map {|key, d| d[:text] } if @f_to_file
106
+ @line_by_key.clear
107
+ end
108
+
109
+
110
+ # *needed for rewriting in tests
111
+ private \
112
+ def set_flags!
113
+ @f_to_console = $>.tty?
114
+ @f_to_file = !$>.tty?
115
+ end
116
+
117
+
118
+ private \
119
+ def output(key, text)
120
+ text = text.to_s
121
+ C_mutex.sync_if_needed do
122
+
123
+ # add line
124
+ puts if @f_to_console && !@line_by_key.has_key?(key)
125
+
126
+ d = @line_by_key[key]
127
+
128
+ if @f_to_console
129
+ print_in_line(
130
+ lines_up: @line_by_key.count - d[:line],
131
+ col: d[:col],
132
+ text: text
133
+ )
134
+ else # for file
135
+ d[:text] += text
136
+ end
137
+
138
+ d[:col] += text.length
139
+ end
140
+ end
141
+
142
+
143
+ private \
144
+ def print_in_line(lines_up:, col:, text:)
145
+ # \e[s — save cursor position
146
+ # \e[nA — move n lines up
147
+ # \e[nG — move to n-th column
148
+ # \e[u — restore cursor position
149
+ print <<~OUT.delete("\n")
150
+ \e[s
151
+ \e[#{lines_up}A
152
+ \e[#{col}G
153
+ #{text}
154
+ \e[u
155
+ OUT
156
+ end
157
+
158
+ end
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: paralines
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Yura Babak
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-01-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.5'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.5'
27
+ description:
28
+ email:
29
+ - yura.des@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - lib/paralines.rb
35
+ homepage: https://github.com/Inversion-des/paralines
36
+ licenses:
37
+ - MIT
38
+ metadata: {}
39
+ post_install_message:
40
+ rdoc_options: []
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ requirements: []
54
+ rubygems_version: 3.2.22
55
+ signing_key:
56
+ specification_version: 4
57
+ summary: Nice output to console/file from concurrent threads.
58
+ test_files: []