cline-rb 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +139 -0
- data/README.md +1216 -0
- data/TODO.md +2 -0
- data/lib/cline/cli.rb +373 -0
- data/lib/cline/config.rb +100 -0
- data/lib/cline/configuration.rb +23 -0
- data/lib/cline/data.rb +119 -0
- data/lib/cline/file_content.rb +33 -0
- data/lib/cline/global_settings.rb +17 -0
- data/lib/cline/global_state/api_providers.rb +48 -0
- data/lib/cline/global_state/auto_approval.rb +73 -0
- data/lib/cline/global_state/browser.rb +52 -0
- data/lib/cline/global_state/features.rb +56 -0
- data/lib/cline/global_state/general.rb +77 -0
- data/lib/cline/global_state/models.rb +127 -0
- data/lib/cline/global_state/toggles.rb +33 -0
- data/lib/cline/global_state/workspace.rb +41 -0
- data/lib/cline/global_state.rb +16 -0
- data/lib/cline/log.rb +288 -0
- data/lib/cline/logs.rb +136 -0
- data/lib/cline/mcp_settings.rb +30 -0
- data/lib/cline/model.rb +47 -0
- data/lib/cline/models.rb +11 -0
- data/lib/cline/overlay_hash.rb +125 -0
- data/lib/cline/providers.rb +59 -0
- data/lib/cline/schema.rb +144 -0
- data/lib/cline/secret_string.rb +83 -0
- data/lib/cline/secrets.rb +119 -0
- data/lib/cline/serializable/cline_data.rb +131 -0
- data/lib/cline/serializable/dir.rb +81 -0
- data/lib/cline/serializable/file.rb +106 -0
- data/lib/cline/session.rb +87 -0
- data/lib/cline/session_data.rb +154 -0
- data/lib/cline/session_message.rb +178 -0
- data/lib/cline/session_messages.rb +61 -0
- data/lib/cline/sessions.rb +30 -0
- data/lib/cline/skill.rb +148 -0
- data/lib/cline/skills.rb +8 -0
- data/lib/cline/task.rb +75 -0
- data/lib/cline/task_message.rb +247 -0
- data/lib/cline/task_messages.rb +11 -0
- data/lib/cline/tasks.rb +30 -0
- data/lib/cline/usage.rb +37 -0
- data/lib/cline/utils/enumerable_dir_objects.rb +103 -0
- data/lib/cline/utils/file.rb +71 -0
- data/lib/cline/utils/file_monitor.rb +56 -0
- data/lib/cline/utils/logger.rb +37 -0
- data/lib/cline/utils/os/linux.rb +43 -0
- data/lib/cline/utils/os/mingw32.rb +46 -0
- data/lib/cline/utils/os.rb +31 -0
- data/lib/cline/utils/schema.rb +290 -0
- data/lib/cline/version.rb +6 -0
- data/lib/cline/workspace.rb +25 -0
- data/lib/cline/workspace_settings.rb +29 -0
- data/lib/cline/workspaces.rb +8 -0
- data/lib/cline.rb +22 -0
- metadata +249 -0
data/lib/cline/schema.rb
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'shale'
|
|
3
|
+
|
|
4
|
+
module Cline
|
|
5
|
+
# Base class for any Cline domain object that defines some attributes that are serializable in JSON.
|
|
6
|
+
# Handle the following features:
|
|
7
|
+
# * Provide Shale attributes interface.
|
|
8
|
+
# * Automatically transforms Cline camelCase naming.
|
|
9
|
+
# * Keep track of extra attributes to serialize them back if needed.
|
|
10
|
+
class Schema < Shale::Mapper
|
|
11
|
+
# @!group Internal
|
|
12
|
+
|
|
13
|
+
# @return [Hash] Store all extra values from a JSON parse
|
|
14
|
+
attr_accessor :extra_attributes
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
# Define the attributes that are already in snake case in Cline files
|
|
18
|
+
#
|
|
19
|
+
# @param attributes [Array<Symbol>] List of attributes already in snake case
|
|
20
|
+
def cline_snake_attributes(*attributes)
|
|
21
|
+
@snake_attributes ||= []
|
|
22
|
+
@snake_attributes.concat(attributes)
|
|
23
|
+
@snake_attributes.uniq!
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Parse a Hash object and instantiate the proper instance from it.
|
|
27
|
+
#
|
|
28
|
+
# @param hash [Hash] Data
|
|
29
|
+
# @param args [Array] Remaining arguments to be transferred to Shale
|
|
30
|
+
# @param kwargs [Hash] Remaining kwargs to be transferred to Shale
|
|
31
|
+
# @return [Schema] Corresponding instance
|
|
32
|
+
def of_hash(hash, *args, **kwargs)
|
|
33
|
+
complete_hash_mapping
|
|
34
|
+
known = hash_mapping.keys.keys
|
|
35
|
+
# Separate unknown attributes.
|
|
36
|
+
known_hash = {}
|
|
37
|
+
extra_hash = {}
|
|
38
|
+
hash.each do |key, value|
|
|
39
|
+
if known.include?(key)
|
|
40
|
+
known_hash[key] = value
|
|
41
|
+
else
|
|
42
|
+
extra_hash[key] = value
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
# Give Shale the data it knows about, without extra attributes
|
|
46
|
+
instance = super(known_hash, *args, **kwargs)
|
|
47
|
+
instance.extra_attributes = extra_hash unless extra_hash.empty?
|
|
48
|
+
instance
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Get a Hash object from an instance.
|
|
52
|
+
#
|
|
53
|
+
# @param instance [Schema] Object to serialize to a Hash
|
|
54
|
+
# @param args [Array] Remaining arguments to be transferred to Shale
|
|
55
|
+
# @param kwargs [Hash] Remaining kwargs to be transferred to Shale
|
|
56
|
+
# @return [Hash] Corresponding hash
|
|
57
|
+
def as_hash(instance, *args, **kwargs)
|
|
58
|
+
complete_hash_mapping
|
|
59
|
+
hash = super
|
|
60
|
+
hash.merge!(instance.extra_attributes) if instance.extra_attributes
|
|
61
|
+
hash
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Cast an input value to this Schema object.
|
|
65
|
+
# Allow the attribute to be initialized directly using its Hash form.
|
|
66
|
+
#
|
|
67
|
+
# @param value [Schema, Hash, nil] The value that could be used to initialize a new instance of this attribute.
|
|
68
|
+
# @return [Schema, nil] The corresponding instance, or nil if none.
|
|
69
|
+
def cast(value)
|
|
70
|
+
return nil if value.nil?
|
|
71
|
+
|
|
72
|
+
# We expect the value to be either a Hash that can be used to initialize a new instance, or a new instance already initialized.
|
|
73
|
+
if value.is_a?(self)
|
|
74
|
+
value
|
|
75
|
+
elsif value.is_a?(Hash)
|
|
76
|
+
new(**value)
|
|
77
|
+
else
|
|
78
|
+
raise "Unable to cast #{value} into #{name}"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
# Complete the hash mapping to include camelCase to snake_case as the defaults.
|
|
85
|
+
# Do it only once.
|
|
86
|
+
def complete_hash_mapping
|
|
87
|
+
return if @hash_mapping_completed
|
|
88
|
+
|
|
89
|
+
snake_attributes = @snake_attributes || []
|
|
90
|
+
# Find the hash name that we expect for each attribute name
|
|
91
|
+
attributes_mapping = attributes.keys.to_h do |attribute|
|
|
92
|
+
[
|
|
93
|
+
attribute,
|
|
94
|
+
if snake_attributes.include?(attribute)
|
|
95
|
+
attribute.to_s
|
|
96
|
+
else
|
|
97
|
+
attribute.to_s.gsub(/(?<!_)_([a-zA-Z0-9])(?!_)/) { Regexp.last_match(1).upcase }
|
|
98
|
+
end
|
|
99
|
+
]
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Redefine the hash mapping for all expected attributes
|
|
103
|
+
hsh do
|
|
104
|
+
attributes_mapping.each do |attribute, hash_attribute|
|
|
105
|
+
# Find the hash name that we expect for each attribute name
|
|
106
|
+
map hash_attribute, to: attribute
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
@hash_mapping_completed = true
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Output this object as Cline JSON.
|
|
114
|
+
#
|
|
115
|
+
# @return [String] Cline JSON
|
|
116
|
+
def to_cline_json
|
|
117
|
+
JSON.dump(self.class.as_hash(self))
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Output this object as a Hash.
|
|
121
|
+
#
|
|
122
|
+
# @return [Hash] Cline JSON
|
|
123
|
+
def to_hash
|
|
124
|
+
hash = self.class.attributes.to_h do |attribute|
|
|
125
|
+
value = send(attribute.to_sym)
|
|
126
|
+
[
|
|
127
|
+
attribute,
|
|
128
|
+
value.respond_to?(:to_hash) ? value.to_hash : value
|
|
129
|
+
]
|
|
130
|
+
end
|
|
131
|
+
hash.merge!(extra_attributes:) if extra_attributes
|
|
132
|
+
hash
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Equality check
|
|
136
|
+
#
|
|
137
|
+
# @param other [Object] The other to check equality with
|
|
138
|
+
# @return [Boolean] True if objects are equal
|
|
139
|
+
def ==(other)
|
|
140
|
+
other.is_a?(Schema) &&
|
|
141
|
+
other.to_hash == to_hash
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
require 'secret_string'
|
|
2
|
+
|
|
3
|
+
module Cline
|
|
4
|
+
# ::SecretString wrapper that allows us to use it in our Shale schemas
|
|
5
|
+
class SecretString < Schema
|
|
6
|
+
extend Forwardable
|
|
7
|
+
|
|
8
|
+
# @!group Public API
|
|
9
|
+
|
|
10
|
+
def_delegators :secret_string, *%i[to_s to_unprotected]
|
|
11
|
+
|
|
12
|
+
# Equality check
|
|
13
|
+
#
|
|
14
|
+
# @param other [Object] The other to check equality with
|
|
15
|
+
# @return [Boolean] True if objects are equal
|
|
16
|
+
def ==(other)
|
|
17
|
+
other.is_a?(SecretString) &&
|
|
18
|
+
other.to_unprotected == to_unprotected
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @!group Internal
|
|
22
|
+
|
|
23
|
+
# Constructor
|
|
24
|
+
#
|
|
25
|
+
# @param unprotected_string [String] The unprotected string
|
|
26
|
+
def initialize(unprotected_string)
|
|
27
|
+
super()
|
|
28
|
+
@secret_string = ::SecretString.new(unprotected_string)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @return [Hash] The internal secret string
|
|
32
|
+
attr_reader :secret_string
|
|
33
|
+
|
|
34
|
+
# Output this object as a Hash.
|
|
35
|
+
#
|
|
36
|
+
# @return [Hash] Cline JSON
|
|
37
|
+
def to_hash
|
|
38
|
+
SecretString.as_hash(self)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
class << self
|
|
42
|
+
# @!group Internal
|
|
43
|
+
|
|
44
|
+
# Parse a Hash object and instantiate the proper instance from it.
|
|
45
|
+
#
|
|
46
|
+
# @param hash [Hash] Data
|
|
47
|
+
# @param _args [Array] Remaining arguments to be transferred to Shale
|
|
48
|
+
# @param _kwargs [Hash] Remaining kwargs to be transferred to Shale
|
|
49
|
+
# @return [Schema] Corresponding instance
|
|
50
|
+
def of_hash(hash, *_args, **_kwargs)
|
|
51
|
+
SecretString.new(hash)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Get a Hash object from an instance.
|
|
55
|
+
#
|
|
56
|
+
# @param instance [Schema] Object to serialize to a Hash
|
|
57
|
+
# @param _args [Array] Remaining arguments to be transferred to Shale
|
|
58
|
+
# @param _kwargs [Hash] Remaining kwargs to be transferred to Shale
|
|
59
|
+
# @return [Hash] Corresponding hash
|
|
60
|
+
def as_hash(instance, *_args, **_kwargs)
|
|
61
|
+
instance.secret_string.to_unprotected
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Cast an input value to this Schema object.
|
|
65
|
+
# Allow the attribute to be initialized directly using its Hash form.
|
|
66
|
+
#
|
|
67
|
+
# @param value [Schema, String, nil] The value that could be used to initialize a new instance of this attribute.
|
|
68
|
+
# @return [Schema, nil] The corresponding instance, or nil if none.
|
|
69
|
+
def cast(value)
|
|
70
|
+
return nil if value.nil?
|
|
71
|
+
|
|
72
|
+
# We expect the value to be a String, or a new instance already initialized.
|
|
73
|
+
if value.is_a?(self)
|
|
74
|
+
value
|
|
75
|
+
elsif value.is_a?(String)
|
|
76
|
+
new(value)
|
|
77
|
+
else
|
|
78
|
+
raise "Unable to cast #{value} into #{name}"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
module Cline
|
|
2
|
+
# Access secrets stored in the data directory's secrets.json file
|
|
3
|
+
class Secrets < Schema
|
|
4
|
+
# @!group Public API
|
|
5
|
+
|
|
6
|
+
Serializable::ClineData.include_for(self, 'secrets.json')
|
|
7
|
+
|
|
8
|
+
# @return [SecretString, nil] Cline API key
|
|
9
|
+
attribute :cline_api_key, SecretString
|
|
10
|
+
|
|
11
|
+
# @return [SecretString, nil] OpenAI API key
|
|
12
|
+
attribute :open_ai_api_key, SecretString
|
|
13
|
+
|
|
14
|
+
# @return [SecretString, nil] Gemini API key
|
|
15
|
+
attribute :gemini_api_key, SecretString
|
|
16
|
+
|
|
17
|
+
# @return [SecretString, nil] Generic API key
|
|
18
|
+
attribute :api_key, SecretString
|
|
19
|
+
|
|
20
|
+
# @return [SecretString, nil] AWS access key
|
|
21
|
+
attribute :aws_access_key, SecretString
|
|
22
|
+
|
|
23
|
+
# @return [SecretString, nil] AWS secret key
|
|
24
|
+
attribute :aws_secret_key, SecretString
|
|
25
|
+
|
|
26
|
+
# @return [SecretString, nil] AWS session token
|
|
27
|
+
attribute :aws_session_token, SecretString
|
|
28
|
+
|
|
29
|
+
# @return [SecretString, nil] DeepSeek API key
|
|
30
|
+
attribute :deep_seek_api_key, SecretString
|
|
31
|
+
|
|
32
|
+
# @return [SecretString, nil] OpenAI native API key
|
|
33
|
+
attribute :open_ai_native_api_key, SecretString
|
|
34
|
+
|
|
35
|
+
# @return [SecretString, nil] OpenRouter API key
|
|
36
|
+
attribute :open_router_api_key, SecretString
|
|
37
|
+
|
|
38
|
+
# @return [SecretString, nil] LiteLLM API key
|
|
39
|
+
attribute :lite_llm_api_key, SecretString
|
|
40
|
+
|
|
41
|
+
# @return [SecretString, nil] SAP AI Core client ID
|
|
42
|
+
attribute :sap_ai_core_client_id, SecretString
|
|
43
|
+
|
|
44
|
+
# @return [SecretString, nil] SAP AI Core client secret
|
|
45
|
+
attribute :sap_ai_core_client_secret, SecretString
|
|
46
|
+
|
|
47
|
+
# @return [SecretString, nil] Mistral API key
|
|
48
|
+
attribute :mistral_api_key, SecretString
|
|
49
|
+
|
|
50
|
+
# @return [SecretString, nil] ZAI API key
|
|
51
|
+
attribute :zai_api_key, SecretString
|
|
52
|
+
|
|
53
|
+
# @return [SecretString, nil] Groq API key
|
|
54
|
+
attribute :groq_api_key, SecretString
|
|
55
|
+
|
|
56
|
+
# @return [SecretString, nil] Cerebras API key
|
|
57
|
+
attribute :cerebras_api_key, SecretString
|
|
58
|
+
|
|
59
|
+
# @return [SecretString, nil] Vercel AI Gateway API key
|
|
60
|
+
attribute :vercel_ai_gateway_api_key, SecretString
|
|
61
|
+
|
|
62
|
+
# @return [SecretString, nil] Baseten API key
|
|
63
|
+
attribute :baseten_api_key, SecretString
|
|
64
|
+
|
|
65
|
+
# @return [SecretString, nil] Requesty API key
|
|
66
|
+
attribute :requesty_api_key, SecretString
|
|
67
|
+
|
|
68
|
+
# @return [SecretString, nil] Fireworks API key
|
|
69
|
+
attribute :fireworks_api_key, SecretString
|
|
70
|
+
|
|
71
|
+
# @return [SecretString, nil] Together API key
|
|
72
|
+
attribute :together_api_key, SecretString
|
|
73
|
+
|
|
74
|
+
# @return [SecretString, nil] Qwen API key
|
|
75
|
+
attribute :qwen_api_key, SecretString
|
|
76
|
+
|
|
77
|
+
# @return [SecretString, nil] Doubao API key
|
|
78
|
+
attribute :doubao_api_key, SecretString
|
|
79
|
+
|
|
80
|
+
# @return [SecretString, nil] Moonshot API key
|
|
81
|
+
attribute :moonshot_api_key, SecretString
|
|
82
|
+
|
|
83
|
+
# @return [SecretString, nil] HuggingFace API key
|
|
84
|
+
attribute :hugging_face_api_key, SecretString
|
|
85
|
+
|
|
86
|
+
# @return [SecretString, nil] Nebius API key
|
|
87
|
+
attribute :nebius_api_key, SecretString
|
|
88
|
+
|
|
89
|
+
# @return [SecretString, nil] AskSage API key
|
|
90
|
+
attribute :asksage_api_key, SecretString
|
|
91
|
+
|
|
92
|
+
# @return [SecretString, nil] xAI API key
|
|
93
|
+
attribute :xai_api_key, SecretString
|
|
94
|
+
|
|
95
|
+
# @return [SecretString, nil] Sambanova API key
|
|
96
|
+
attribute :sambanova_api_key, SecretString
|
|
97
|
+
|
|
98
|
+
# @return [SecretString, nil] Huawei Cloud Maas API key
|
|
99
|
+
attribute :huawei_cloud_maas_api_key, SecretString
|
|
100
|
+
|
|
101
|
+
# @return [SecretString, nil] Dify API key
|
|
102
|
+
attribute :dify_api_key, SecretString
|
|
103
|
+
|
|
104
|
+
# @return [SecretString, nil] Minimax API key
|
|
105
|
+
attribute :minimax_api_key, SecretString
|
|
106
|
+
|
|
107
|
+
# @return [SecretString, nil] Hicap API key
|
|
108
|
+
attribute :hicap_api_key, SecretString
|
|
109
|
+
|
|
110
|
+
# @return [SecretString, nil] AIHubMix API key
|
|
111
|
+
attribute :aihubmix_api_key, SecretString
|
|
112
|
+
|
|
113
|
+
# @return [SecretString, nil] Nous Research API key
|
|
114
|
+
attribute :nous_research_api_key, SecretString
|
|
115
|
+
|
|
116
|
+
# @return [SecretString, nil] Weights & Biases API key
|
|
117
|
+
attribute :wandb_api_key, SecretString
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
module Cline
|
|
2
|
+
# List of mixins that provide serialization/deserialization features on other objects.
|
|
3
|
+
module Serializable
|
|
4
|
+
# Add features to initialize from and save an object to Cline data.
|
|
5
|
+
# Cline data is defined as a JSON file, relative to a base directory.
|
|
6
|
+
# This mixin is intended to be included using the `.include_for(self, cline_json_file)` method.
|
|
7
|
+
#
|
|
8
|
+
# Provides:
|
|
9
|
+
# - `.from_cline_data(base_dir) -> [Object, nil]` Provides a new instance initialized from a Cline JSON file present in a base directory.
|
|
10
|
+
# - `.monitor_cline_data_changes(base_dir, on_change)` Provides a monitor to be notified on Cline data changes.
|
|
11
|
+
# - `#to_cline_data(base_dir)` Save an instance in the Cline data.
|
|
12
|
+
#
|
|
13
|
+
# Requires:
|
|
14
|
+
# - `.of_hash(hash) -> Object` The deserializer that returns an instance from a JSON object.
|
|
15
|
+
# - `#to_cline_json -> String` The serializer that returns a JSON string from the instance.
|
|
16
|
+
module ClineData
|
|
17
|
+
# @!group Public API
|
|
18
|
+
|
|
19
|
+
# Save the instance into the Cline data
|
|
20
|
+
def save
|
|
21
|
+
raise 'This instance has not been initialized from a Cline file' unless file
|
|
22
|
+
|
|
23
|
+
FileUtils.mkdir_p(::File.dirname(file))
|
|
24
|
+
::File.write(file, to_cline_json)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @!group Internal
|
|
28
|
+
|
|
29
|
+
# Class methods that should be made accessible to any class including our mixin
|
|
30
|
+
module ClassMethods
|
|
31
|
+
# @!group Internal
|
|
32
|
+
|
|
33
|
+
# Instantiate an instance of the including class from a base directory.
|
|
34
|
+
#
|
|
35
|
+
# @param base_dir [String] Base directory used to initialize the new instance
|
|
36
|
+
# @param args [Array] Extra parameters to give to the instance's constructor
|
|
37
|
+
# @param create [Boolean] Should data be created if it does not exist?
|
|
38
|
+
# @param kwargs [Hash] Extra kwargs to give to the instance's constructor
|
|
39
|
+
# @return [Object, nil] The instance, or nil if no Cline data exists
|
|
40
|
+
def from_cline_data(base_dir, *args, create: false, **kwargs)
|
|
41
|
+
instance = self.open(
|
|
42
|
+
::File.join(base_dir, cline_json_file(base_dir)),
|
|
43
|
+
*args,
|
|
44
|
+
default: create ? '{}' : nil,
|
|
45
|
+
**kwargs
|
|
46
|
+
)
|
|
47
|
+
return unless instance
|
|
48
|
+
|
|
49
|
+
instance.initialize_from_dir(base_dir, create:)
|
|
50
|
+
instance
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Monitor changes done on the file and call a callback for each update.
|
|
54
|
+
#
|
|
55
|
+
# @param base_dir [String] Base directory used to initialize the new instance
|
|
56
|
+
# @param args [Array] Extra parameters to give to the instance's constructor
|
|
57
|
+
# @param on_change [#call] Block called each time there is an update.
|
|
58
|
+
# * Param instance [Object, nil] New instance with updates, or nil if no instance
|
|
59
|
+
# @param monitoring_interval_secs [Float] The monitoring interval in seconds
|
|
60
|
+
# @param kwargs [Hash] Extra kwargs to give to the instance's constructor
|
|
61
|
+
# @yield Optional code called while monitoring is in place.
|
|
62
|
+
# If used then monitoring is stopped at the end of the block's execution.
|
|
63
|
+
# @return [Utils::FileMonitor, nil] If no block has been given, return the monitor that needs to be
|
|
64
|
+
# stopped by the caller when monitoring should end.
|
|
65
|
+
def monitor_cline_data_changes(base_dir, *args, on_change:, monitoring_interval_secs: 1, **kwargs, &)
|
|
66
|
+
monitor_updates(
|
|
67
|
+
::File.join(base_dir, cline_json_file(base_dir)),
|
|
68
|
+
*args,
|
|
69
|
+
on_change: proc do |instance|
|
|
70
|
+
instance.initialize_from_dir(base_dir, create: false)
|
|
71
|
+
on_change.call(instance)
|
|
72
|
+
end,
|
|
73
|
+
monitoring_interval_secs:,
|
|
74
|
+
**kwargs,
|
|
75
|
+
&
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Default factory for instances.
|
|
80
|
+
# This could be overriden by some classes that need to instantiate differently.
|
|
81
|
+
#
|
|
82
|
+
# @param file [String] File to initialize from.
|
|
83
|
+
# @param args [Array] Extra parameters to give to the instance's constructor.
|
|
84
|
+
# @param kwargs [Hash] Extra kwargs to give to the instance's constructor.
|
|
85
|
+
# @return [Object] A new instance.
|
|
86
|
+
def new_instance(file, *args, **kwargs)
|
|
87
|
+
of_hash(Utils::File.safe_json_read(file), *args, **kwargs)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
# Get the relative JSON file path from the base directory
|
|
93
|
+
#
|
|
94
|
+
# @param base_dir [String] The base directory
|
|
95
|
+
# @return [String] The relative JSON file path
|
|
96
|
+
def cline_json_file(base_dir)
|
|
97
|
+
cline_json_file_def.is_a?(Proc) ? cline_json_file_def.call(base_dir) : cline_json_file_def
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Include the mixin and configure it with the JSON file path
|
|
102
|
+
#
|
|
103
|
+
# @param calling_class [Class] The class that is calling this method.
|
|
104
|
+
# @param cline_json_file_def [String, #call] The definition of the relative JSON file path to use. Can be one of:
|
|
105
|
+
# - [String] The static relative JSON file path to be used.
|
|
106
|
+
# - [#call] A proc that can devise dynamically the relative file path from the base directory:
|
|
107
|
+
# - Param base_dir [String] The base directory from which the JSON file is searched.
|
|
108
|
+
# - Return [String] The corresponding relative JSON file path from base_dir.
|
|
109
|
+
def self.include_for(calling_class, cline_json_file_def)
|
|
110
|
+
calling_class.class_eval do
|
|
111
|
+
include Dir
|
|
112
|
+
include File
|
|
113
|
+
include ClineData
|
|
114
|
+
|
|
115
|
+
class << self
|
|
116
|
+
# @return [String] The relative JSON file path
|
|
117
|
+
attr_accessor :cline_json_file_def
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
calling_class.cline_json_file_def = cline_json_file_def
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Hook used when this mixin is included in a base class
|
|
124
|
+
#
|
|
125
|
+
# @param base [Class] The base class
|
|
126
|
+
def self.included(base)
|
|
127
|
+
base.extend(ClassMethods)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
|
|
3
|
+
module Cline
|
|
4
|
+
module Serializable
|
|
5
|
+
# Add features to initialize from and save an object to a directory.
|
|
6
|
+
#
|
|
7
|
+
# Provides:
|
|
8
|
+
# - `.open(dir) -> [Object, nil]` Provides a new instance initialized from the directory, or nil if no directory.
|
|
9
|
+
# - `#dir -> [String]` The directory from which this object was initialized.
|
|
10
|
+
# - `#subpath(path) -> [String]` Provide a sub-path from the directory the object was initialized from.
|
|
11
|
+
module Dir
|
|
12
|
+
# Class methods that should be made accessible to any class including our mixin
|
|
13
|
+
module ClassMethods
|
|
14
|
+
# @!group Public API
|
|
15
|
+
|
|
16
|
+
# Instantiate an instance of the including class from a given directory.
|
|
17
|
+
#
|
|
18
|
+
# @param dir [String] Directory used to initialize the new instance
|
|
19
|
+
# @param args [Array] Extra parameters to give to the instance's constructor
|
|
20
|
+
# @param create [Boolean] Should the directory be created if it does not exist?
|
|
21
|
+
# @param kwargs [Hash] Extra kwargs to give to the instance's constructor
|
|
22
|
+
# @return [Object, nil] The instance initialized from this directory, or nil if none
|
|
23
|
+
def open(dir, *args, create: false, **kwargs)
|
|
24
|
+
unless ::File.exist?(dir) && ::File.directory?(dir)
|
|
25
|
+
return unless create
|
|
26
|
+
|
|
27
|
+
FileUtils.mkdir_p dir
|
|
28
|
+
end
|
|
29
|
+
instance = new_instance(dir, *args, **kwargs)
|
|
30
|
+
instance.initialize_from_dir(dir, create:)
|
|
31
|
+
instance
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @!group Internal
|
|
35
|
+
|
|
36
|
+
# Default factory for instances.
|
|
37
|
+
# This could be overriden by some classes that need to instantiate differently.
|
|
38
|
+
#
|
|
39
|
+
# @param _dir [String] The directory to create the instance for.
|
|
40
|
+
# @param args [Array] Extra parameters to give to the instance's constructor.
|
|
41
|
+
# @param kwargs [Hash] Extra kwargs to give to the instance's constructor.
|
|
42
|
+
# @return [Object] A new instance.
|
|
43
|
+
def new_instance(_dir, *args, **kwargs)
|
|
44
|
+
new(*args, **kwargs)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @!group Internal
|
|
49
|
+
|
|
50
|
+
# Hook used when this mixin is included in a base class
|
|
51
|
+
#
|
|
52
|
+
# @param base [Class] The base class
|
|
53
|
+
def self.included(base)
|
|
54
|
+
base.extend(ClassMethods)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @return [String] The directory used for the object's initialization
|
|
58
|
+
attr_reader :dir
|
|
59
|
+
|
|
60
|
+
# @return [Boolean] Should data be created if it does not exist?
|
|
61
|
+
attr_reader :create
|
|
62
|
+
|
|
63
|
+
# Initialize this instance from a directory
|
|
64
|
+
#
|
|
65
|
+
# @param dir [String] The directory to be used to initialize this instance
|
|
66
|
+
# @param create [Boolean] Should data be created if it does not exist?
|
|
67
|
+
def initialize_from_dir(dir, create:)
|
|
68
|
+
@dir = dir
|
|
69
|
+
@create = create
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Return the path to a sub-path of our instance directory
|
|
73
|
+
#
|
|
74
|
+
# @param path [String] The relative sub-path
|
|
75
|
+
# @return [String] The full path to the sub-path
|
|
76
|
+
def subpath(path)
|
|
77
|
+
::File.join(dir, path)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
|
|
3
|
+
module Cline
|
|
4
|
+
module Serializable
|
|
5
|
+
# Add features to initialize from and save an object to a file.
|
|
6
|
+
#
|
|
7
|
+
# Provides:
|
|
8
|
+
# - `.open(file) -> [Object, nil]` Provides a new instance initialized from the file, or nil if no file.
|
|
9
|
+
# - `.monitor_file_changes(file, on_change)` Provides a monitor to be notified on file changes.
|
|
10
|
+
# - `.monitor_updates(file, on_change)` Provides a monitor to be notified on new instances upon updates.
|
|
11
|
+
# - `#file -> [String]` The file from which this object was initialized.
|
|
12
|
+
module File
|
|
13
|
+
# @!group Internal
|
|
14
|
+
|
|
15
|
+
# Class methods that should be made accessible to any class including our mixin
|
|
16
|
+
module ClassMethods
|
|
17
|
+
# Instantiate an instance of the including class from a given file.
|
|
18
|
+
#
|
|
19
|
+
# @param file [String] File path used to initialize the new instance
|
|
20
|
+
# @param args [Array] Extra parameters to give to the instance's constructor
|
|
21
|
+
# @param default [String, nil] Default file content to be created, or nil to only read existing one
|
|
22
|
+
# @param kwargs [Hash] Extra kwargs to give to the instance's constructor
|
|
23
|
+
# @return [Object, nil] The instance, or nil if no file exists
|
|
24
|
+
def open(file, *args, default: nil, **kwargs)
|
|
25
|
+
unless ::File.exist?(file)
|
|
26
|
+
return unless default
|
|
27
|
+
|
|
28
|
+
FileUtils.mkdir_p(::File.dirname(file))
|
|
29
|
+
::File.write(file, default)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
instance = new_instance(file, *args, **kwargs)
|
|
33
|
+
instance.initialize_from_file(file)
|
|
34
|
+
instance
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Monitor changes done on the file and call a callback for each update.
|
|
38
|
+
#
|
|
39
|
+
# @param file [String] File path to be monitored
|
|
40
|
+
# @param on_change [#call] Block called each time there is an update.
|
|
41
|
+
# * Param mtime [Time, nil] New file modification time, or nil if no file
|
|
42
|
+
# @param monitoring_interval_secs [Float] The monitoring interval in seconds
|
|
43
|
+
# @yield Optional code called while monitoring is in place.
|
|
44
|
+
# If used then monitoring is stopped at the end of the block's execution.
|
|
45
|
+
# @return [Utils::FileMonitor, nil] If no block has been given, return the monitor that needs to be
|
|
46
|
+
# stopped by the caller when monitoring should end.
|
|
47
|
+
def monitor_file_changes(file, on_change:, monitoring_interval_secs: 1, &)
|
|
48
|
+
monitor = Utils::FileMonitor.new(file, on_change:, monitoring_interval_secs:)
|
|
49
|
+
monitor.start(&)
|
|
50
|
+
monitor unless block_given?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Monitor changes and call a callback for each update on the updated instance.
|
|
54
|
+
#
|
|
55
|
+
# @param file [String] File path to be monitored
|
|
56
|
+
# @param args [Array] Extra parameters to give to the instance's constructor
|
|
57
|
+
# @param on_change [#call] Block called each time there is an update.
|
|
58
|
+
# * Param instance [Object, nil] New instance with updates, or nil if no instance
|
|
59
|
+
# @param monitoring_interval_secs [Float] The monitoring interval in seconds
|
|
60
|
+
# @param kwargs [Hash] Extra kwargs to give to the instance's constructor
|
|
61
|
+
# @yield Optional code called while monitoring is in place.
|
|
62
|
+
# If used then monitoring is stopped at the end of the block's execution.
|
|
63
|
+
# @return [Utils::FileMonitor, nil] If no block has been given, return the monitor that needs to be
|
|
64
|
+
# stopped by the caller when monitoring should end.
|
|
65
|
+
def monitor_updates(file, *args, on_change:, monitoring_interval_secs: 1, **kwargs, &)
|
|
66
|
+
monitor_file_changes(
|
|
67
|
+
file,
|
|
68
|
+
on_change: proc do |_mtime|
|
|
69
|
+
on_change.call(self.open(file, *args, default: nil, **kwargs))
|
|
70
|
+
end,
|
|
71
|
+
monitoring_interval_secs:,
|
|
72
|
+
&
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Default factory for instances.
|
|
77
|
+
# This could be overriden by some classes that need to instantiate differently.
|
|
78
|
+
#
|
|
79
|
+
# @param _file [String] File to initialize from.
|
|
80
|
+
# @param args [Array] Extra parameters to give to the instance's constructor.
|
|
81
|
+
# @param kwargs [Hash] Extra kwargs to give to the instance's constructor.
|
|
82
|
+
# @return [Object] A new instance.
|
|
83
|
+
def new_instance(_file, *args, **kwargs)
|
|
84
|
+
new(*args, **kwargs)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Hook used when this mixin is included in a base class
|
|
89
|
+
#
|
|
90
|
+
# @param base [Class] The base class
|
|
91
|
+
def self.included(base)
|
|
92
|
+
base.extend(ClassMethods)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# @return [String] The file used for the object's initialization
|
|
96
|
+
attr_reader :file
|
|
97
|
+
|
|
98
|
+
# Initialize this instance from a file
|
|
99
|
+
#
|
|
100
|
+
# @param file [String] The file to be used to initialize this instance
|
|
101
|
+
def initialize_from_file(file)
|
|
102
|
+
@file = file
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|