k_doc 0.0.18 → 0.0.24

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KDoc
4
+ # A container acts a base data object for any data that requires tagging
5
+ # such as unique key, type and namespace.
6
+ # Rename: BlockProcessor
7
+ module BlockProcessor
8
+ attr_accessor :block
9
+
10
+ def initialize_block(_opts, &block)
11
+ @block = block if block_given?
12
+ end
13
+
14
+ def execute_block(run_actions: nil)
15
+ eval_block
16
+ run_on_action if run_actions
17
+ end
18
+
19
+ def eval_block
20
+ return if @block.nil?
21
+
22
+ instance_eval(&@block)
23
+ rescue StandardError => e
24
+ log.error('Standard error in document')
25
+ # puts "key #{unique_key}"
26
+ # puts "file #{KUtil.data.console_file_hyperlink(resource.file, resource.file)}"
27
+ log.error(e.message)
28
+ @error = e
29
+ raise
30
+ end
31
+
32
+ def run_on_action
33
+ return if @block.nil?
34
+
35
+ on_action if respond_to?(:on_action)
36
+ rescue StandardError => e
37
+ log.error('Standard error while running actions')
38
+ # puts "key #{unique_key}"
39
+ # puts "file #{KUtil.data.console_file_hyperlink(resource.file, resource.file)}"
40
+ log.error(e.message)
41
+ @error = e
42
+ raise
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KDoc
4
+ # Composite Design Pattern: https://refactoring.guru/design-patterns/composite
5
+ module ComposableComponents
6
+ # Parent allows upwards navigation to parent component
7
+ attr_reader :parent
8
+
9
+ # Components allow downwards navigation plus access to sub-components
10
+ attr_reader :components
11
+
12
+ def attach_parent(parent)
13
+ @parent = parent
14
+ end
15
+
16
+ def navigate_parent
17
+ parent.nil? ? self : parent
18
+ end
19
+
20
+ def root?
21
+ parent.nil?
22
+ end
23
+
24
+ # Implement as needed (Implement is not provided here because you may want to use hash or array and have additional logic)
25
+ # def reset_components
26
+ # end
27
+ # def add_component
28
+ # end
29
+ # def remove_component
30
+ # end
31
+ # def get_components
32
+ # end
33
+ # def has_component?
34
+ # end
35
+ # def execute
36
+ # end
37
+ end
38
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KDoc
4
+ # Data acts as a base data object containers
5
+ module Datum
6
+ include KDoc::Guarded
7
+
8
+ attr_reader :data
9
+
10
+ def initialize_data(opts)
11
+ @default_data_type = opts.delete(:default_data_type) if opts.key?(:default_data_type)
12
+ @data = opts.delete(:data) || opts.delete(:default_data) || default_data_type.new
13
+
14
+ warn("Incompatible data type - #{default_data_type} is incompatible with #{data.class} in constructor") unless data.is_a?(default_data_type)
15
+ end
16
+
17
+ def default_data_type
18
+ raise 'Implement default_data_type in container' unless @default_data_type
19
+ end
20
+
21
+ # Write data object
22
+ #
23
+ # @param [Object] value A compatible data object to be stored against .data property
24
+ # @param [Symbol] data_action The data_action to take when setting data, defaults to :replace
25
+ # @param [:replace] data_action :replace will replace the existing data instance with the incoming data value
26
+ # @param [:append] data_action :append will keep existing data and then new value data over the top
27
+ def set_data(value, data_action: :replace)
28
+ warn("Incompatible data type - #{default_data_type} is incompatible with #{value.class} in set data") unless value.is_a?(default_data_type)
29
+
30
+ case data_action
31
+ when :replace
32
+ @data = value
33
+ when :append
34
+ @data.merge!(value) if @data.is_a?(Hash)
35
+ @data += value if @data.is_a?(Array)
36
+ else
37
+ warn("Unknown data_action: #{data_action}")
38
+ end
39
+ end
40
+
41
+ def clear_data
42
+ @data.clear
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KDoc
4
+ # Guarded provides parameter waring and guarding
5
+ #
6
+ # TODO: this could be moved into KType or KGuard
7
+ module Guarded
8
+ Guard = Struct.new(:type, :message)
9
+
10
+ def guard(message, log: false)
11
+ errors << Guard.new(:guard, message)
12
+ end
13
+
14
+ def warn(message)
15
+ errors << Guard.new(:warning, message)
16
+ end
17
+ alias warning warn
18
+
19
+ def errors
20
+ @errors ||= []
21
+ end
22
+
23
+ def error_messages
24
+ errors.map(&:message)
25
+ end
26
+
27
+ def error_hash
28
+ errors.map(&:to_h)
29
+ end
30
+
31
+ # TODO: Add these predicates
32
+ # def errors?
33
+ # def warnings?
34
+
35
+ def valid?
36
+ errors.length.zero?
37
+ end
38
+
39
+ def log_any_messages
40
+ errors.each do |error|
41
+ log.warn error.message if error.type == :warning
42
+ log.error error.message if error.type == :guard
43
+ end
44
+ end
45
+
46
+ def clear_errors
47
+ errors.clear
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KDoc
4
+ # A container acts a base data object for any data that requires tagging
5
+ # such as unique key, type and namespace.
6
+ module Taggable
7
+ include KLog::Logging
8
+
9
+ attr_reader :tag_options
10
+
11
+ # Tags are provided via an options hash, these tags are remove from the hash as they are read
12
+ #
13
+ # Any container can be uniquely identified via it's key, type, namespace and project_key tags
14
+ #
15
+ # @param [Hash] opts The options
16
+ # @option opts [String|Symbol] project Project Name
17
+ # @option opts [String|Symbol|Array] namespace Namespace or array if namespace is deep nested
18
+ # @option opts [String|Symbol] name Container Name
19
+ # @option opts [String|Symbol] type Type of the container, uses default_container_type if not set
20
+ def initialize_tag(opts)
21
+ @tag_options = opts
22
+ build_tag
23
+ end
24
+
25
+ def tag
26
+ return @tag if defined? @tag
27
+ end
28
+
29
+ # Name of the document (required)
30
+ #
31
+ # Examples: user, account, country
32
+ def key
33
+ @key ||= @tag_options.delete(:key) || SecureRandom.alphanumeric(4)
34
+ end
35
+
36
+ # Type of data
37
+ #
38
+ # Examples by data type
39
+ # :csv, :yaml, :json, :xml
40
+ #
41
+ # Examples by shape of the data in a DSL
42
+ # :entity, :microapp, blueprint
43
+ def type
44
+ @type ||= @tag_options.delete(:type) || default_container_type
45
+ end
46
+
47
+ # TODO: rename to area (or area root namespace)
48
+ # Project name
49
+ #
50
+ # Examples
51
+ # :app_name1, :app_name2, :library_name1
52
+ def project
53
+ @project ||= @tag_options.delete(:project) || ''
54
+ end
55
+
56
+ # Namespace(s) what namespace is this document under
57
+ #
58
+ # Example for single path
59
+ # :controllers, :models
60
+ #
61
+ # Example for deep path
62
+ # [:app, :controllers, :admin]
63
+ def namespace
64
+ return @namespace if defined? @namespace
65
+
66
+ ns = @tag_options.delete(:namespace) || []
67
+ @namespace = ns.is_a?(Array) ? ns : [ns]
68
+ end
69
+
70
+ # # Internal data object
71
+ # def data
72
+ # @data ||= @tag_options.delete(:data) || @tag_options.delete(:default_data) || default_data_value
73
+ # # Settings and Table on Model needed access to @data for modification, I don't think this should be a clone
74
+ # # never return the original data object, but at the same time
75
+ # # do not re-clone it every time this accessor is called.
76
+ # # @clone_data ||= @data.clone
77
+ # end
78
+
79
+ # Implement in container
80
+ # def default_container_type
81
+ # :container
82
+ # end
83
+
84
+ # def default_data_value
85
+ # {}
86
+ # end
87
+
88
+ protected
89
+
90
+ # rubocop:disable Metrics/AbcSize
91
+ def debug_container
92
+ log.kv 'tag' , tag , debug_pad_size
93
+ log.kv 'project' , project , debug_pad_size unless project.nil? || project.empty?
94
+ log.kv 'namespace' , namespace , debug_pad_size unless namespace.nil? || namespace.empty?
95
+ log.kv 'key' , key , debug_pad_size
96
+ log.kv 'type' , type , debug_pad_size
97
+ log.kv 'class type' , self.class.name , debug_pad_size
98
+ # log.kv 'error' , error , debug_kv_pad_size
99
+ end
100
+ # rubocop:enable Metrics/AbcSize
101
+
102
+ def debug_pad_size
103
+ @debug_pad_size ||= @tag_options.delete(:debug_pad_size) || 20
104
+ end
105
+
106
+ private
107
+
108
+ def build_tag
109
+ values = []
110
+ values << project if project
111
+ values += namespace
112
+ values << key
113
+ values << type if type
114
+ values -= [nil, '']
115
+ @tag = values.join('_').to_sym
116
+ end
117
+ end
118
+ end
data/lib/k_doc/model.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  module KDoc
4
4
  # Model is a DSL for modeling general purpose data objects
5
5
  #
6
- # A mode can have
6
+ # A model can have
7
7
  # - 0 or more named setting groups each with their key/value pairs
8
8
  # - 0 or more named table groups each with their own columns and rows
9
9
  #
@@ -17,64 +17,26 @@ module KDoc
17
17
  # include KType::NamedFolder
18
18
  # include KType::LayeredFolder
19
19
 
20
- attr_reader :options
21
-
22
- # Create document
23
- #
24
- # @param [String|Symbol] name Name of the document
25
- # @param args[0] Type of the document, defaults to KDoc:: FakeOpinion.new.default_model_type if not set
26
- # @param default: Default value (using named params), as above
27
- def initialize(key = nil, **options, &block)
28
- super(key: key, type: options[:type] || KDoc.opinion.default_model_type) # , namespace: options[:namespace], project_key: options[:project_key])
29
- initialize_attributes(**options)
30
-
31
- @block = block if block_given?
20
+ def initialize(key = nil, **opts, &block)
21
+ super(**{ key: key }.merge(opts), &block)
32
22
  end
33
23
 
34
- # NOTE: Can this be moved out of the is object?
35
- def execute_block(run_actions: nil)
36
- return if @block.nil?
37
-
38
- # The DSL actions method will only run on run_actions: true
39
- @run_actions = run_actions
40
-
41
- instance_eval(&@block)
42
-
43
- on_action if run_actions && respond_to?(:on_action)
44
- # rescue KDoc::Error => e
45
- # puts('KDoc::Error in document')
46
- # puts "key #{unique_key}"
47
- # # puts "file #{KUtil.data.console_file_hyperlink(resource.file, resource.file)}"
48
- # puts(e.message)
49
- # @error = e
50
- # raise
51
- rescue StandardError => e
52
- log.error('Standard error in document')
53
- # puts "key #{unique_key}"
54
- # puts "file #{KUtil.data.console_file_hyperlink(resource.file, resource.file)}"
55
- log.error(e.message)
56
- @error = e
57
- # log.exception exception2
58
- raise
59
- ensure
60
- @run_actions = nil
61
- end
24
+ # Need to look at Director as an alternative to this technique
25
+ def settings(key = nil, **setting_opts, &block)
26
+ setting_opts ||= {}
62
27
 
63
- def settings(key = nil, **options, &block)
64
- options ||= {}
28
+ setting_opts = {}.merge(opts) # Container options
29
+ .merge(setting_opts) # Settings setting_opts
30
+ .merge(parent: self)
65
31
 
66
- opts = {}.merge(@options) # Data Options
67
- .merge(options) # Settings Options
68
- .merge(parent: self)
69
-
70
- settings_instance(@data, key, **opts, &block)
32
+ settings_instance(data, key, **setting_opts, &block)
71
33
  # settings.run_decorators(opts)
72
34
  end
73
35
 
74
- def table(key = :table, **options, &block)
36
+ def table(key = :table, **opts, &block)
75
37
  # NEED to add support for run_decorators I think
76
- options.merge(parent: self)
77
- table_instance(@data, key, **options, &block)
38
+ opts.merge(parent: self)
39
+ table_instance(data, key, **opts, &block)
78
40
  end
79
41
  alias rows table
80
42
 
@@ -84,6 +46,16 @@ module KDoc
84
46
  # KDoc::Builder::Shotstack.new(@data, key, &block)
85
47
  # end
86
48
 
49
+ # Need to move this down to container
50
+ # Need to use some sort of cache invalidation to know if the internal data has been altered
51
+ def odata
52
+ @odata ||= data_struct
53
+ end
54
+
55
+ def oraw
56
+ @oraw ||= raw_data_struct
57
+ end
58
+
87
59
  def data_struct
88
60
  KUtil.data.to_open_struct(data)
89
61
  end
@@ -93,9 +65,13 @@ module KDoc
93
65
  KUtil.data.to_open_struct(raw_data)
94
66
  end
95
67
 
68
+ def default_container_type
69
+ KDoc.opinion.default_model_type
70
+ end
71
+
96
72
  def get_node_type(node_name)
97
73
  node_name = KUtil.data.clean_symbol(node_name)
98
- node_data = @data[node_name]
74
+ node_data = data[node_name]
99
75
 
100
76
  raise KDoc::Error, "Node not found: #{node_name}" if node_data.nil?
101
77
 
@@ -139,47 +115,26 @@ module KDoc
139
115
 
140
116
  def debug_header
141
117
  log.heading self.class.name
142
- log.kv 'key' , key , 15
143
- log.kv 'type' , type , 15
144
- # log.kv 'namespace', namespace
145
- log.kv 'error' , error , 15
146
-
118
+ debug_container
147
119
  debug_header_keys
148
120
 
149
121
  log.line
150
122
  end
151
123
 
152
124
  def debug_header_keys
153
- options&.keys&.reject { |k| k == :namespace }&.each do |key|
154
- log.kv key, options[key]
125
+ opts&.keys&.reject { |k| k == :namespace }&.each do |key|
126
+ log.kv key, opts[key]
155
127
  end
156
128
  end
157
129
 
158
130
  private
159
131
 
160
- def initialize_attributes(**options)
161
- @options = options || {}
162
- # Is parent a part of model, or is it part of k_manager::document_taggable
163
- @parent = slice_option(:parent)
164
-
165
- # Most documents live within a hash, some tabular documents such as CSV will use an []
166
- @data = slice_option(:default_data) || {}
167
- end
168
-
169
- def settings_instance(data, key, **options, &block)
170
- KDoc.opinion.settings_class.new(data, key, **options, &block)
132
+ def settings_instance(data, key, **opts, &block)
133
+ KDoc.opinion.settings_class.new(data, key, **opts, &block)
171
134
  end
172
135
 
173
- def table_instance(data, key, **options, &block)
174
- KDoc.opinion.table_class.new(data, key, **options, &block)
175
- end
176
-
177
- def slice_option(key)
178
- return nil unless @options.key?(key)
179
-
180
- result = @options[key]
181
- @options.delete(key)
182
- result
136
+ def table_instance(data, key, **opts, &block)
137
+ KDoc.opinion.table_class.new(data, key, **opts, &block)
183
138
  end
184
139
  end
185
140
  end
@@ -0,0 +1,191 @@
1
+ # frozen_string_literal: true
2
+
3
+ # module KDoc
4
+ # # Model is a DSL for modeling general purpose data objects
5
+ # #
6
+ # # A model can have
7
+ # # - 0 or more named setting groups each with their key/value pairs
8
+ # # - 0 or more named table groups each with their own columns and rows
9
+ # #
10
+ # # A settings group without a name will default to name: :settings
11
+ # # A table group without a name will default to name: :table
12
+ # class Model < KDoc::Container
13
+ # include KLog::Logging
14
+
15
+ # # include KType::Error
16
+ # # include KType::ManagedState
17
+ # # include KType::NamedFolder
18
+ # # include KType::LayeredFolder
19
+
20
+ # attr_reader :options
21
+
22
+ # # Create document
23
+ # #
24
+ # # @param [String|Symbol] name Name of the document
25
+ # # @param args[0] Type of the document, defaults to KDoc:: FakeOpinion.new.default_model_type if not set
26
+ # # @param default: Default value (using named params), as above
27
+ # def initialize(key = nil, **options, &block)
28
+ # super(key: key, type: options[:type] || KDoc.opinion.default_model_type) # , namespace: options[:namespace], project_key: options[:project_key])
29
+ # initialize_attributes(**options)
30
+
31
+ # @block = block if block_given?
32
+ # end
33
+
34
+ # # NOTE: Can this be moved out of the is object?
35
+ # def execute_block(run_actions: nil)
36
+ # return if @block.nil?
37
+
38
+ # # The DSL actions method will only run on run_actions: true
39
+ # @run_actions = run_actions
40
+
41
+ # instance_eval(&@block)
42
+
43
+ # on_action if run_actions && respond_to?(:on_action)
44
+ # # rescue KDoc::Error => e
45
+ # # puts('KDoc::Error in document')
46
+ # # puts "key #{unique_key}"
47
+ # # # puts "file #{KUtil.data.console_file_hyperlink(resource.file, resource.file)}"
48
+ # # puts(e.message)
49
+ # # @error = e
50
+ # # raise
51
+ # rescue StandardError => e
52
+ # log.error('Standard error in document')
53
+ # # puts "key #{unique_key}"
54
+ # # puts "file #{KUtil.data.console_file_hyperlink(resource.file, resource.file)}"
55
+ # log.error(e.message)
56
+ # @error = e
57
+ # # log.exception exception2
58
+ # raise
59
+ # ensure
60
+ # @run_actions = nil
61
+ # end
62
+
63
+ # def settings(key = nil, **options, &block)
64
+ # options ||= {}
65
+
66
+ # opts = {}.merge(@options) # Data Options
67
+ # .merge(options) # Settings Options
68
+ # .merge(parent: self)
69
+
70
+ # settings_instance(@data, key, **opts, &block)
71
+ # # settings.run_decorators(opts)
72
+ # end
73
+
74
+ # def table(key = :table, **options, &block)
75
+ # # NEED to add support for run_decorators I think
76
+ # options.merge(parent: self)
77
+ # table_instance(@data, key, **options, &block)
78
+ # end
79
+ # alias rows table
80
+
81
+ # # Sweet add-on would be builders
82
+ # # def builder(key, &block)
83
+ # # # example
84
+ # # KDoc::Builder::Shotstack.new(@data, key, &block)
85
+ # # end
86
+
87
+ # # Need to move this down to container
88
+ # # Need to use some sort of cache invalidation to know if the internal data has been altered
89
+ # def odata
90
+ # @odata ||= data_struct
91
+ # end
92
+
93
+ # def oraw
94
+ # @oraw ||= raw_data_struct
95
+ # end
96
+
97
+ # def data_struct
98
+ # KUtil.data.to_open_struct(data)
99
+ # end
100
+ # # alias d data_struct
101
+
102
+ # def raw_data_struct
103
+ # KUtil.data.to_open_struct(raw_data)
104
+ # end
105
+
106
+ # def get_node_type(node_name)
107
+ # node_name = KUtil.data.clean_symbol(node_name)
108
+ # node_data = @data[node_name]
109
+
110
+ # raise KDoc::Error, "Node not found: #{node_name}" if node_data.nil?
111
+
112
+ # if node_data.keys.length == 2 && (node_data.key?('fields') && node_data.key?('rows'))
113
+ # :table
114
+ # else
115
+ # :settings
116
+ # end
117
+ # end
118
+
119
+ # # Removes any meta data eg. "fields" from a table and just returns the raw data
120
+ # # REFACTOR: IT MAY BE BEST TO MOVE raw_data into each of the node_types
121
+ # def raw_data
122
+ # # REFACT, what if this is CSV, meaning it is just an array?
123
+ # # add specs
124
+ # result = data
125
+
126
+ # result.each_key do |key|
127
+ # # ANTI: get_node_type uses @data while we are using @data.clone here
128
+ # result[key] = if get_node_type(key) == :table
129
+ # # Old format was to keep the rows and delete the fields
130
+ # # Now the format is to pull the row_value up to the key and remove rows and fields
131
+ # # result[key].delete('fields')
132
+ # result[key]['rows']
133
+ # else
134
+ # result[key]
135
+ # end
136
+ # end
137
+
138
+ # result
139
+ # end
140
+
141
+ # # Move this out to the logger function when it has been refactor
142
+ # def debug(include_header: false)
143
+ # debug_header if include_header
144
+
145
+ # # tp dsls.values, :k_key, :k_type, :state, :save_at, :last_at, :data, :last_data, :source, { :file => { :width => 150 } }
146
+ # # puts JSON.pretty_generate(data)
147
+ # log.o(raw_data_struct)
148
+ # end
149
+
150
+ # def debug_header
151
+ # log.heading self.class.name
152
+ # debug_container
153
+ # debug_header_keys
154
+
155
+ # log.line
156
+ # end
157
+
158
+ # def debug_header_keys
159
+ # options&.keys&.reject { |k| k == :namespace }&.each do |key|
160
+ # log.kv key, options[key]
161
+ # end
162
+ # end
163
+
164
+ # private
165
+
166
+ # def initialize_attributes(**options)
167
+ # @options = options || {}
168
+ # # Is parent a part of model, or is it part of k_manager::document_taggable (NOT SURE IF IT IS USED YET)
169
+ # @parent = slice_option(:parent)
170
+
171
+ # # Most documents live within a hash, some tabular documents such as CSV will use an []
172
+ # @data = slice_option(:default_data) || {}
173
+ # end
174
+
175
+ # def settings_instance(data, key, **options, &block)
176
+ # KDoc.opinion.settings_class.new(data, key, **options, &block)
177
+ # end
178
+
179
+ # def table_instance(data, key, **options, &block)
180
+ # KDoc.opinion.table_class.new(data, key, **options, &block)
181
+ # end
182
+
183
+ # def slice_option(key)
184
+ # return nil unless @options.key?(key)
185
+
186
+ # result = @options[key]
187
+ # @options.delete(key)
188
+ # result
189
+ # end
190
+ # end
191
+ # end
data/lib/k_doc/table.rb CHANGED
@@ -25,10 +25,7 @@ module KDoc
25
25
  #
26
26
  # The older format of an array is supported via a splat conversion
27
27
  def fields(*field_definitions)
28
- if field_definitions.length == 1 && field_definitions[0].is_a?(Array)
29
- log.warn('avoid supplying field definitions with array. *Splat fields is the preferred technique.')
30
- field_definitions = *field_definitions[0]
31
- end
28
+ field_definitions = *field_definitions[0] if field_definitions.length == 1 && field_definitions[0].is_a?(Array)
32
29
 
33
30
  fields = @data[@name]['fields']
34
31
 
data/lib/k_doc/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module KDoc
4
- VERSION = '0.0.18'
4
+ VERSION = '0.0.24'
5
5
  end