ruby_jard 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +3 -0
  3. data/.github/workflows/documentation.yml +65 -0
  4. data/.github/workflows/{ruby.yml → rspec.yml} +22 -11
  5. data/.rubocop.yml +6 -0
  6. data/CHANGELOG.md +15 -3
  7. data/Gemfile +9 -2
  8. data/README.md +52 -332
  9. data/benchmark/path_filter_bench.rb +58 -0
  10. data/lib/ruby_jard.rb +15 -5
  11. data/lib/ruby_jard/color_schemes.rb +9 -1
  12. data/lib/ruby_jard/color_schemes/256_color_scheme.rb +21 -35
  13. data/lib/ruby_jard/color_schemes/256_light_color_scheme.rb +23 -35
  14. data/lib/ruby_jard/color_schemes/deep_space_color_scheme.rb +20 -34
  15. data/lib/ruby_jard/color_schemes/gruvbox_color_scheme.rb +20 -34
  16. data/lib/ruby_jard/color_schemes/one_half_dark_color_scheme.rb +20 -34
  17. data/lib/ruby_jard/color_schemes/one_half_light_color_scheme.rb +21 -34
  18. data/lib/ruby_jard/commands/color_helpers.rb +32 -0
  19. data/lib/ruby_jard/commands/frame_command.rb +2 -2
  20. data/lib/ruby_jard/commands/jard/color_scheme_command.rb +25 -3
  21. data/lib/ruby_jard/commands/jard/filter_command.rb +136 -0
  22. data/lib/ruby_jard/commands/jard/output_command.rb +13 -5
  23. data/lib/ruby_jard/commands/jard_command.rb +3 -1
  24. data/lib/ruby_jard/config.rb +9 -2
  25. data/lib/ruby_jard/decorators/array_decorator.rb +79 -0
  26. data/lib/ruby_jard/decorators/attributes_decorator.rb +172 -0
  27. data/lib/ruby_jard/decorators/color_decorator.rb +12 -5
  28. data/lib/ruby_jard/decorators/hash_decorator.rb +74 -0
  29. data/lib/ruby_jard/decorators/inspection_decorator.rb +91 -58
  30. data/lib/ruby_jard/decorators/object_decorator.rb +122 -0
  31. data/lib/ruby_jard/decorators/path_decorator.rb +55 -72
  32. data/lib/ruby_jard/decorators/rails_decorator.rb +194 -0
  33. data/lib/ruby_jard/decorators/string_decorator.rb +41 -0
  34. data/lib/ruby_jard/decorators/struct_decorator.rb +79 -0
  35. data/lib/ruby_jard/frame.rb +23 -10
  36. data/lib/ruby_jard/keys.rb +1 -0
  37. data/lib/ruby_jard/layouts/narrow_horizontal_layout.rb +4 -0
  38. data/lib/ruby_jard/layouts/tiny_layout.rb +4 -0
  39. data/lib/ruby_jard/pager.rb +21 -5
  40. data/lib/ruby_jard/path_classifier.rb +133 -0
  41. data/lib/ruby_jard/path_filter.rb +125 -0
  42. data/lib/ruby_jard/reflection.rb +97 -0
  43. data/lib/ruby_jard/repl_processor.rb +71 -38
  44. data/lib/ruby_jard/row_renderer.rb +5 -3
  45. data/lib/ruby_jard/screen.rb +2 -2
  46. data/lib/ruby_jard/screen_manager.rb +13 -36
  47. data/lib/ruby_jard/screen_renderer.rb +1 -1
  48. data/lib/ruby_jard/screens/backtrace_screen.rb +55 -39
  49. data/lib/ruby_jard/screens/menu_screen.rb +30 -30
  50. data/lib/ruby_jard/screens/source_screen.rb +46 -62
  51. data/lib/ruby_jard/screens/threads_screen.rb +59 -72
  52. data/lib/ruby_jard/screens/variables_screen.rb +168 -124
  53. data/lib/ruby_jard/session.rb +120 -16
  54. data/lib/ruby_jard/thread_info.rb +69 -0
  55. data/lib/ruby_jard/version.rb +1 -1
  56. data/ruby_jard.gemspec +3 -1
  57. metadata +20 -39
  58. data/.travis.yml +0 -6
  59. data/CNAME +0 -1
  60. data/_config.yml +0 -1
  61. data/docs/_config.yml +0 -8
  62. data/docs/color_schemes/256-light.png +0 -0
  63. data/docs/color_schemes/256.png +0 -0
  64. data/docs/color_schemes/deep-space.png +0 -0
  65. data/docs/color_schemes/gruvbox.png +0 -0
  66. data/docs/color_schemes/one-half-dark.png +0 -0
  67. data/docs/color_schemes/one-half-light.png +0 -0
  68. data/docs/demo.png +0 -0
  69. data/docs/guide-ui.png +0 -0
  70. data/docs/index.md +0 -238
  71. data/docs/logo.jpg +0 -0
  72. data/docs/screen-backtrace.png +0 -0
  73. data/docs/screen-repl.png +0 -0
  74. data/docs/screen-source.png +0 -0
  75. data/docs/screen-threads.png +0 -0
  76. data/docs/screen-variables.png +0 -0
  77. data/images/bg_hr.png +0 -0
  78. data/images/blacktocat.png +0 -0
  79. data/images/body-bg.jpg +0 -0
  80. data/images/download-button.png +0 -0
  81. data/images/github-button.png +0 -0
  82. data/images/header-bg.jpg +0 -0
  83. data/images/highlight-bg.jpg +0 -0
  84. data/images/icon_download.png +0 -0
  85. data/images/sidebar-bg.jpg +0 -0
  86. data/images/sprite_download.png +0 -0
  87. data/javascripts/main.js +0 -1
  88. data/stylesheets/github-light.css +0 -130
  89. data/stylesheets/normalize.css +0 -424
  90. data/stylesheets/print.css +0 -228
  91. data/stylesheets/stylesheet.css +0 -245
@@ -20,40 +20,27 @@ module RubyJard
20
20
 
21
21
  BACKGROUND = WHITE
22
22
  STYLES = {
23
- background: [GRAY5, BACKGROUND],
24
- border: [GRAY3, BACKGROUND],
25
- title: [WHITE, GREEN],
26
- title_highlighted: [WHITE, YELLOW],
27
- title_secondary: [GRAY5, GRAY2],
28
- title_background: [GRAY3, BACKGROUND],
29
- menu_mode: [YELLOW, BACKGROUND],
30
- menu_tips: [GRAY4, BACKGROUND],
31
- thread_id: [GREEN, BACKGROUND],
32
- thread_name: [GRAY5, BACKGROUND],
33
- thread_status_run: [GREEN, BACKGROUND],
34
- thread_status_sleep: [GRAY4, BACKGROUND],
35
- thread_status_other: [GRAY4, BACKGROUND],
36
- thread_location: [GRAY5, BACKGROUND],
37
- frame_id: [GRAY4, BACKGROUND],
38
- frame_id_highlighted: [YELLOW, BACKGROUND],
39
- frame_location: [GRAY5, BACKGROUND],
40
- variable_mark: [GRAY4, BACKGROUND],
41
- variable_mark_inline: [YELLOW, BACKGROUND],
42
- variable_size: [GRAY5, BACKGROUND],
43
- variable_inspection: [GRAY5, BACKGROUND],
44
- variable_assignment: [GRAY5, BACKGROUND],
45
- source_line_mark: [YELLOW, BACKGROUND],
46
- source_lineno: [GRAY4, BACKGROUND],
47
- keyword: [PURPLE, BACKGROUND],
48
- method: [BLUE, BACKGROUND],
49
- comment: [GRAY4, BACKGROUND],
50
- literal: [YELLOW, BACKGROUND],
51
- string: [GREEN, BACKGROUND],
52
- local_variable: [RED, BACKGROUND],
53
- instance_variable: [RED, BACKGROUND],
54
- constant: [YELLOW, BACKGROUND],
55
- normal_token: [GRAY5, BACKGROUND],
56
- object: [YELLOW, BACKGROUND]
23
+ background: [GRAY5, BACKGROUND],
24
+ border: [GRAY3, BACKGROUND],
25
+ title: [WHITE, GREEN],
26
+ title_highlighted: [WHITE, YELLOW],
27
+ title_secondary: [GRAY5, GRAY2],
28
+ title_background: [GRAY3, BACKGROUND],
29
+ text_primary: [GRAY5, BACKGROUND],
30
+ text_dim: [GRAY4, BACKGROUND],
31
+ text_highlighted: [BLUE, BACKGROUND],
32
+ text_special: [RED, BACKGROUND],
33
+ text_selected: [GREEN, BACKGROUND],
34
+ keyword: [PURPLE, BACKGROUND],
35
+ method: [BLUE, BACKGROUND],
36
+ comment: [GRAY4, BACKGROUND],
37
+ literal: [YELLOW, BACKGROUND],
38
+ string: [GREEN, BACKGROUND],
39
+ local_variable: [RED, BACKGROUND],
40
+ instance_variable: [RED, BACKGROUND],
41
+ constant: [YELLOW, BACKGROUND],
42
+ normal_token: [GRAY5, BACKGROUND],
43
+ object: [YELLOW, BACKGROUND]
57
44
  }.freeze
58
45
  end
59
46
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJard
4
+ module Commands
5
+ ##
6
+ # Helper to dedocrate command output
7
+ module ColorHelpers
8
+ def special(content)
9
+ color_decorator.decorate(:text_special, content)
10
+ end
11
+
12
+ def secondary(content)
13
+ color_decorator.decorate(:text_primary, content)
14
+ end
15
+
16
+ def highlight(content)
17
+ color_decorator.decorate(:text_highlighted, content)
18
+ end
19
+
20
+ def pick_color_scheme
21
+ (
22
+ RubyJard::ColorSchemes[RubyJard.config.color_scheme] ||
23
+ RubyJard::ColorSchemes[RubyJard::Config::DEFAULT_COLOR_SCHEME]
24
+ ).new
25
+ end
26
+
27
+ def color_decorator
28
+ @color_decorator ||= RubyJard::Decorators::ColorDecorator.new(pick_color_scheme)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -22,13 +22,13 @@ module RubyJard
22
22
 
23
23
  def initialize(context = {})
24
24
  super(context)
25
- @session = context[:session] || RubyJard.current_session
25
+ @current_backtrace = (context[:session] || RubyJard::Session).current_backtrace
26
26
  end
27
27
 
28
28
  def process
29
29
  frame = validate_present!(args.first)
30
30
  frame = validate_non_negative_integer!(frame)
31
- frame = validate_range!(frame, 0, @session.current_backtrace.length - 1)
31
+ frame = validate_range!(frame, 0, @current_backtrace.map(&:virtual_pos).compact.max)
32
32
  RubyJard::ControlFlow.dispatch(:frame, frame: frame)
33
33
  end
34
34
  end
@@ -4,6 +4,8 @@ module RubyJard
4
4
  module Commands
5
5
  # Command used to explore stacktrace.
6
6
  class ColorSchemeCommand < Pry::ClassCommand
7
+ include RubyJard::Commands::ColorHelpers
8
+
7
9
  group 'RubyJard'
8
10
  description 'Control the color scheme used in Jard'
9
11
 
@@ -29,24 +31,44 @@ module RubyJard
29
31
  if @color_schemes.names.empty?
30
32
  pry_instance.output.puts 'No loaded color schemes'
31
33
  else
32
- pry_instance.output.puts @color_schemes.names.join("\n")
34
+ print_color_schemes
33
35
  end
34
36
  else
35
37
  color_scheme = args.first.to_s.strip
36
38
  if color_scheme.empty?
37
39
  raise Pry::CommandError,
38
- 'You must provide a color scheme name. Please use `color-scheme -l` to list all color schemes.'
40
+ 'You must provide a color scheme name. '\
41
+ "Please use `#{highlight('jard color-scheme -l')}` to list all color schemes."
39
42
  end
40
43
 
41
44
  if @color_schemes[color_scheme].nil?
42
45
  raise Pry::CommandError,
43
- "Color scheme `#{color_scheme}` not found. Please use `color-scheme -l` to list all color schemes."
46
+ "Color scheme `#{secondary(color_scheme)}` not found. "\
47
+ "Please use `#{highlight('jard color-scheme -l')}` to list all color schemes."
44
48
  end
45
49
 
46
50
  @config.color_scheme = color_scheme
47
51
  RubyJard::ControlFlow.dispatch(:list)
48
52
  end
49
53
  end
54
+
55
+ private
56
+
57
+ def print_color_schemes
58
+ pry_instance.output.puts
59
+ pry_instance.output.puts highlight("#{@color_schemes.names.length} available color schemes")
60
+ pry_instance.output.puts
61
+ padding = @color_schemes.names.map(&:length).max
62
+ @color_schemes.names.each do |name|
63
+ scheme = @color_schemes[name]
64
+ decorator = RubyJard::Decorators::ColorDecorator.new(scheme.new)
65
+ pallete = scheme.const_get(:STYLES).keys.map do |style|
66
+ decorator.decorate(style, '⬤ ')
67
+ end.join(' ')
68
+ pry_instance.output.puts "#{name.ljust(padding)} #{pallete}"
69
+ pry_instance.output.puts
70
+ end
71
+ end
50
72
  end
51
73
  end
52
74
  end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJard
4
+ module Commands
5
+ ##
6
+ # Control filter, included and excluded
7
+ class FilterCommand < Pry::ClassCommand
8
+ include RubyJard::Commands::ColorHelpers
9
+
10
+ group 'RubyJard'
11
+ description 'Filter to keep only relevant location when you debugging'
12
+
13
+ match 'filter'
14
+
15
+ banner <<-BANNER
16
+ Usage:
17
+ jard filter
18
+ jard filter [everything, gems, application, source_tree]
19
+ jard filter [include, exclude] pattern
20
+ jard filter clear
21
+
22
+ Ruby Jard has a strong filtering system. This system consists of a filter mode, included list, and excluded list. Filter mode is how Ruby Jard reacts to control flow commands. See https://rubyjard.org/docs/guides/filter for more information.
23
+
24
+ Examples:
25
+
26
+ jard filter # Show filter status
27
+
28
+ jard filter application # Switch to application mode
29
+ jard filter gems # Switch to gems mode
30
+ jard filter everything # Switch to everything mode
31
+ jard filter source_tree # Switch to source tree mode
32
+
33
+ jard filter include sidekiq # Include sidekiq pattern
34
+ jard filter include aws-*
35
+ jard filter include aws-* active* action* # Multiple patterns separated by <space>
36
+ jard filter include lib/**/*.erb
37
+ jard filter include ~/home/lib/**/*.rb
38
+
39
+ jard filter exclude sidekiq # exclude sidekiq pattern
40
+ jard filter exclude aws-*
41
+ jard filter exclude aws-* active* action* # Multiple patterns separated by <space>
42
+ jard filter exclude lib/**/*.erb
43
+ jard filter exclude ~/home/lib/**/*.rb
44
+
45
+ jard filter clear # Clear filter
46
+ BANNER
47
+
48
+ def initialize(*args)
49
+ super(*args)
50
+ @filters = RubyJard::PathFilter::FILTERS
51
+ @config = context[:config] || RubyJard.config
52
+ end
53
+
54
+ def process
55
+ if args.empty?
56
+ print_current_filter
57
+ return
58
+ end
59
+
60
+ sub_command = args.shift.to_sym
61
+
62
+ case sub_command
63
+ when *@filters
64
+ @config.filter = sub_command
65
+ RubyJard::ControlFlow.dispatch(:list)
66
+ when :include
67
+ handle_included
68
+ when :exclude
69
+ handle_excluded
70
+ when :clear
71
+ handle_clear
72
+ else
73
+ raise Pry::CommandError,
74
+ "Invalid filter '#{secondary(sub_command)}'."\
75
+ "Please type `#{highlight('jard filter --help')}` for more information"
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ def print_current_filter
82
+ pry_instance.output.puts
83
+ pry_instance.output.puts highlight('Filter mode')
84
+ pry_instance.output.puts " #{@config.filter}"
85
+ pry_instance.output.puts highlight("Included (#{@config.filter_included.length})")
86
+ @config.filter_included.each do |included|
87
+ pry_instance.output.puts " +#{included}"
88
+ end
89
+
90
+ pry_instance.output.puts highlight("Excluded (#{@config.filter_excluded.length})")
91
+ @config.filter_excluded.each do |excluded|
92
+ pry_instance.output.puts " -#{excluded}"
93
+ end
94
+ pry_instance.output.puts
95
+ pry_instance.output.puts "Please type `#{highlight('jard filter --help')}` for more information"
96
+ pry_instance.output.puts
97
+ end
98
+
99
+ def handle_included
100
+ if args.empty?
101
+ raise Pry::CommandError,
102
+ 'Wrong number of arguments! '\
103
+ "Please type `#{highlight('jard filter --help')}` for more information"
104
+ end
105
+ filters = args.map(&:strip)
106
+ @config.filter_included.append(*filters)
107
+ @config.filter_included.uniq!
108
+ filters.each do |filter|
109
+ @config.filter_excluded.delete(filter) if @config.filter_excluded.include?(filter)
110
+ end
111
+ RubyJard::ControlFlow.dispatch(:list)
112
+ end
113
+
114
+ def handle_excluded
115
+ if args.empty?
116
+ raise Pry::CommandError,
117
+ 'Wrong number of arguments!'\
118
+ "Please type `#{highlight('jard filter --help')}` for more information"
119
+ end
120
+ filters = args.map(&:strip)
121
+ @config.filter_excluded.append(*filters)
122
+ @config.filter_excluded.uniq!
123
+ filters.each do |filter|
124
+ @config.filter_included.delete(filter) if @config.filter_included.include?(filter)
125
+ end
126
+ RubyJard::ControlFlow.dispatch(:list)
127
+ end
128
+
129
+ def handle_clear
130
+ @config.filter_excluded.clear
131
+ @config.filter_included.clear
132
+ RubyJard::ControlFlow.dispatch(:list)
133
+ end
134
+ end
135
+ end
136
+ end
@@ -13,14 +13,22 @@ module RubyJard
13
13
  Usage: output
14
14
  BANNER
15
15
 
16
- def self.output_storage
17
- RubyJard::ScreenManager.instance.output_storage
16
+ def initialize(*args)
17
+ super(*args)
18
+ @session = (context[:session] || RubyJard::Session)
18
19
  end
19
20
 
20
21
  def process
21
- pry_instance.pager.open(force_open: true, pager_start_at_the_end: true) do |pager|
22
- self.class.output_storage.rewind
23
- pager.write self.class.output_storage.read_nonblock(2048) until self.class.output_storage.eof?
22
+ pry_instance.pager.open(
23
+ force_open: true,
24
+ pager_start_at_the_end: true,
25
+ prompt: 'Program output'
26
+ ) do |pager|
27
+ @session.output_buffer.each do |string|
28
+ string.each do |s|
29
+ pager.write(s)
30
+ end
31
+ end
24
32
  end
25
33
  end
26
34
  end
@@ -4,6 +4,7 @@ require 'ruby_jard/commands/jard/show_command'
4
4
  require 'ruby_jard/commands/jard/hide_command'
5
5
  require 'ruby_jard/commands/jard/color_scheme_command'
6
6
  require 'ruby_jard/commands/jard/output_command'
7
+ require 'ruby_jard/commands/jard/filter_command'
7
8
 
8
9
  module RubyJard
9
10
  module Commands
@@ -22,7 +23,8 @@ module RubyJard
22
23
  'show' => RubyJard::Commands::ShowCommand,
23
24
  'hide' => RubyJard::Commands::HideCommand,
24
25
  'color-scheme' => RubyJard::Commands::ColorSchemeCommand,
25
- 'output' => RubyJard::Commands::OutputCommand
26
+ 'output' => RubyJard::Commands::OutputCommand,
27
+ 'filter' => RubyJard::Commands::FilterCommand
26
28
  }.freeze
27
29
 
28
30
  def subcommands(cmd)
@@ -31,13 +31,17 @@ module RubyJard
31
31
  end
32
32
  end
33
33
 
34
- attr_accessor :color_scheme, :alias_to_debugger, :layout, :enabled_screens
34
+ attr_accessor :color_scheme, :alias_to_debugger, :layout, :enabled_screens,
35
+ :filter, :filter_included, :filter_excluded
35
36
 
36
37
  CONFIG_FILE_NAME = '.jardrc'
37
38
  DEFAULTS = [
38
39
  DEFAULT_COLOR_SCHEME = '256',
39
40
  DEFAULT_ALIAS_TO_DEBUGGER = false,
40
- DEFAULT_LAYOUT = nil # Pick layout automatically
41
+ DEFAULT_LAYOUT = nil, # Pick layout automatically
42
+ DEFAULT_FILTER = RubyJard::PathFilter::FILTER_APPLICATION,
43
+ DEFAULT_FILTER_INCLUDED = [].freeze,
44
+ DEFAULT_FILTER_EXCLUDED = [].freeze
41
45
  ].freeze
42
46
 
43
47
  def initialize
@@ -45,6 +49,9 @@ module RubyJard
45
49
  @alias_to_debugger = DEFAULT_ALIAS_TO_DEBUGGER
46
50
  @layout = DEFAULT_LAYOUT
47
51
  @enabled_screens = RubyJard::Screens.names
52
+ @filter = DEFAULT_FILTER
53
+ @filter_included = DEFAULT_FILTER_INCLUDED.dup
54
+ @filter_excluded = DEFAULT_FILTER_EXCLUDED.dup
48
55
  end
49
56
 
50
57
  def config
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJard
4
+ module Decorators
5
+ ##
6
+ # Decorate Array data structure, supports singleline and multiline form.
7
+ class ArrayDecorator
8
+ def initialize(generic_decorator)
9
+ @generic_decorator = generic_decorator
10
+ @attributes_decorator = RubyJard::Decorators::AttributesDecorator.new(generic_decorator)
11
+ end
12
+
13
+ def match?(variable)
14
+ RubyJard::Reflection.call_is_a?(variable, Array)
15
+ end
16
+
17
+ def decorate_singleline(variable, line_limit:, depth: 0)
18
+ spans = []
19
+ spans << RubyJard::Span.new(content: '[', styles: :text_primary)
20
+ spans += @attributes_decorator.inline_values(
21
+ variable.each_with_index, total: variable.length, line_limit: line_limit - 2, depth: depth + 1
22
+ )
23
+ spans << RubyJard::Span.new(content: ']', styles: :text_primary)
24
+
25
+ spans
26
+ end
27
+
28
+ def decorate_multiline(variable, first_line_limit:, lines:, line_limit:, depth: 0)
29
+ if variable.length > lines * 2
30
+ return do_decorate_multiline(variable, lines: lines, line_limit: line_limit, depth: depth)
31
+ end
32
+
33
+ singleline = decorate_singleline(variable, line_limit: first_line_limit, depth: depth)
34
+ if (singleline.map(&:content_length).sum < line_limit && same_type?(variable, lines)) || variable.length <= 1
35
+ [singleline]
36
+ else
37
+ do_decorate_multiline(variable, lines: lines, line_limit: line_limit, depth: depth)
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def same_type?(variable, sample)
44
+ variable.first(sample).map { |item| RubyJard::Reflection.call_class(item) }.uniq.length <= 1
45
+ end
46
+
47
+ def do_decorate_multiline(variable, lines:, line_limit:, depth: 0)
48
+ spans = [[RubyJard::Span.new(content: '[', styles: :text_primary)]]
49
+
50
+ item_count = 0
51
+ variable.each_with_index do |value, index|
52
+ spans << @attributes_decorator.value(value, line_limit: line_limit, depth: depth + 1)
53
+
54
+ item_count += 1
55
+ break if index >= lines - 2
56
+ end
57
+
58
+ spans << last_line(variable.length, item_count)
59
+ end
60
+
61
+ def last_line(total, item_count)
62
+ if total > item_count
63
+ [
64
+ RubyJard::Span.new(
65
+ content: "▸ #{total - item_count} more...",
66
+ margin_left: 2, styles: :text_dim
67
+ ),
68
+ RubyJard::Span.new(
69
+ content: ']',
70
+ styles: :text_primary
71
+ )
72
+ ]
73
+ else
74
+ [RubyJard::Span.new(content: ']', styles: :text_primary)]
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end