sitedog_parser 0.3.1 → 0.4.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: da833e3fe74bc8bf12d8039a18c7bd0b4bd4e19bbe0cf8c78bfc9e68ae5cb8d4
4
- data.tar.gz: 71f10cafaf8971e6cc2ddadd40847d8f2835c73cdaa530ec0eca2a0a5fd06605
3
+ metadata.gz: d0eb9da31315637d91c4b4b312cc6382a88f9c1daf094c3296b59a47ecb23169
4
+ data.tar.gz: ea11849351936255636f6c62786d24ee00a9d3a0dbff3dfbf2bb96b517ea4f26
5
5
  SHA512:
6
- metadata.gz: bcaf3041c47b219f6b8d10b39cef222a2535e180f274ef1c8cd231d7d3f4400a50ed278e6de6014b53af42987c3b3e1979d842f70918a5b1ecaf3bd4b17a4257
7
- data.tar.gz: 493b359dbfddb4f94b1eaa135f4b81e21029183f052d4777d15abaece5ac244c41e136a5fcd399ad4911d15bea415026308ae2ef4c2f0305f33c441ead4eada5
6
+ metadata.gz: a368008e5bf05584aafcc201a3b36ded643743fc14f6ac4fe32376add4fbdae61e2d475e86a918df3447162526fbeaee2c1b2c68e939ace8f4f11a82a7a119ed
7
+ data.tar.gz: f1421b7c40284499abb0008559f23eb1d3e914387a3f053c1f014a603b96e33503b4459bc9b7f31364bda6bfc4100bc61065d9d78482b04f20194005aa3109fa
data/bin/sitedog_cli CHANGED
@@ -4,7 +4,186 @@ require 'bundler/setup'
4
4
  require 'sitedog_parser'
5
5
  require 'optparse'
6
6
  require 'logger'
7
+ require 'yaml'
8
+ require 'json'
9
+ require 'net/http'
10
+ require 'uri'
11
+ require 'fileutils'
7
12
 
13
+ # Класс клиента для взаимодействия с SiteDog Cloud API
14
+ class SiteDogClient
15
+ API_URL = 'http://localhost:3005/api/v1'
16
+ CONFIG_DIR = File.join(Dir.home, '.sitedog')
17
+ CONFIG_FILE = File.join(CONFIG_DIR, 'config.json')
18
+
19
+ attr_accessor :test_mode
20
+
21
+ def initialize(test_mode = false)
22
+ @config = load_config
23
+ @test_mode = test_mode
24
+ end
25
+
26
+ def login(email, password)
27
+ uri = URI.parse("#{API_URL}/login")
28
+ params = { email: email, password: password }
29
+
30
+ response = Net::HTTP.post(uri, params.to_json, 'Content-Type' => 'application/json')
31
+
32
+ if response.code == '200'
33
+ result = JSON.parse(response.body)
34
+ if result['status'] == 'success'
35
+ @config['api_key'] = result['api_key']
36
+ save_config
37
+ puts "Авторизация успешна! API-ключ сохранен."
38
+ return true
39
+ else
40
+ puts "Ошибка: #{result['message']}"
41
+ end
42
+ else
43
+ puts "Ошибка: #{response.code} - #{response.message}"
44
+ end
45
+
46
+ false
47
+ end
48
+
49
+ def push_card(file_path, title = nil)
50
+ unless authenticated?
51
+ puts "Ошибка: Сначала выполните вход (sitedog_cli login <email> <password>)"
52
+ return false
53
+ end
54
+
55
+ # Используем имя файла как заголовок, если заголовок не указан
56
+ title = File.basename(file_path) if title.nil? || title.empty?
57
+
58
+ unless File.exist?(file_path)
59
+ puts "Ошибка: Файл не найден: #{file_path}"
60
+ return false
61
+ end
62
+
63
+ content = File.read(file_path)
64
+
65
+ # Проверяем, что содержимое является валидным YAML
66
+ begin
67
+ yaml_content = YAML.load(content)
68
+ rescue => e
69
+ puts "Ошибка: Недопустимый YAML-файл: #{e.message}"
70
+ return false
71
+ end
72
+
73
+ # В тестовом режиме просто показываем информацию
74
+ if @test_mode
75
+ puts "=== ТЕСТОВЫЙ РЕЖИМ ==="
76
+ puts "Отправка данных на API..."
77
+ puts "Заголовок: #{title}"
78
+ puts "Файл: #{file_path}"
79
+ puts "Содержимое (сокращено):"
80
+ puts " #{yaml_content.keys.join(", ")}"
81
+ puts "=== УСПЕШНО ==="
82
+ return true
83
+ end
84
+
85
+ uri = URI.parse("#{API_URL}/cards")
86
+ params = { card: { title: title, content: content } }
87
+
88
+ http = Net::HTTP.new(uri.host, uri.port)
89
+ request = Net::HTTP::Post.new(uri.request_uri)
90
+ request['Content-Type'] = 'application/json'
91
+ request['Authorization'] = "Bearer #{@config['api_key']}"
92
+ request.body = params.to_json
93
+
94
+ response = http.request(request)
95
+
96
+ if response.code == '201'
97
+ result = JSON.parse(response.body)
98
+ puts "Карточка успешно отправлена!"
99
+ puts "ID: #{result['data']['id']}"
100
+ puts "Заголовок: #{result['data']['title']}"
101
+ return true
102
+ else
103
+ puts "Ошибка: #{response.code} - #{response.message}"
104
+ puts response.body if response.body
105
+ return false
106
+ end
107
+ end
108
+
109
+ private
110
+
111
+ def authenticated?
112
+ # В тестовом режиме всегда считаем, что аутентификация пройдена
113
+ return true if @test_mode
114
+
115
+ # Проверяем наличие API-ключа
116
+ @config && @config['api_key']
117
+ end
118
+
119
+ def load_config
120
+ return {} unless File.exist?(CONFIG_FILE)
121
+
122
+ begin
123
+ JSON.parse(File.read(CONFIG_FILE))
124
+ rescue
125
+ {}
126
+ end
127
+ end
128
+
129
+ def save_config
130
+ FileUtils.mkdir_p(CONFIG_DIR) unless Dir.exist?(CONFIG_DIR)
131
+ File.write(CONFIG_FILE, @config.to_json)
132
+ end
133
+ end
134
+
135
+ # Проверка, если первый аргумент - команда для API
136
+ if ARGV.size > 0 && ['login', 'push'].include?(ARGV[0])
137
+ command = ARGV.shift
138
+
139
+ # Создаем клиент только для login, для push будет создан позже с test_mode
140
+ if command == 'login'
141
+ client = SiteDogClient.new
142
+
143
+ if ARGV.size < 2
144
+ puts "Ошибка: Требуется email и пароль"
145
+ puts "Использование: sitedog_cli login <email> <password>"
146
+ exit 1
147
+ end
148
+
149
+ email = ARGV[0]
150
+ password = ARGV[1]
151
+ exit(client.login(email, password) ? 0 : 1)
152
+ elsif command == 'push'
153
+ # По умолчанию ищем .sitedog в текущей директории
154
+ default_file = '.sitedog'
155
+
156
+ # Проверяем опции для тестового режима
157
+ test_mode = false
158
+ if ARGV.include?('--test')
159
+ test_mode = true
160
+ ARGV.delete('--test')
161
+ end
162
+
163
+ if ARGV.empty?
164
+ # Если аргументы отсутствуют, используем файл по умолчанию
165
+ if File.exist?(default_file)
166
+ file_path = default_file
167
+ title = File.basename(Dir.pwd) # Используем имя текущей директории как заголовок
168
+ else
169
+ puts "Ошибка: Файл #{default_file} не найден в текущей директории."
170
+ puts "Использование: sitedog_cli push [file_path] [--test]"
171
+ exit 1
172
+ end
173
+ else
174
+ # Первый аргумент - путь к файлу
175
+ file_path = ARGV[0]
176
+ title = File.basename(file_path) # Используем имя файла как заголовок по умолчанию
177
+ end
178
+
179
+ client = SiteDogClient.new(test_mode)
180
+ exit(client.push_card(file_path, title) ? 0 : 1)
181
+ end
182
+
183
+ exit 0
184
+ end
185
+
186
+ # Для стандартной функциональности парсинга YAML -> JSON
8
187
  # Set default options
9
188
  options = {
10
189
  debug: false,
@@ -23,6 +202,13 @@ end
23
202
  # Command line options parser
24
203
  option_parser = OptionParser.new do |opts|
25
204
  opts.banner = "Usage: sitedog_cli [options] <path_to_yaml_file> [output_file]"
205
+ opts.separator ""
206
+ opts.separator "Commands:"
207
+ opts.separator " login <email> <password> - войти в систему и получить API-ключ"
208
+ opts.separator " push [file_path] - отправить YAML-файл как карточку (по умолчанию использует .sitedog)"
209
+ opts.separator " --test - запустить в тестовом режиме без отправки данных на сервер"
210
+ opts.separator ""
211
+ opts.separator "Options:"
26
212
 
27
213
  opts.on("-d", "--debug", "Enable debug output") do
28
214
  options[:debug] = true
@@ -132,10 +318,25 @@ end
132
318
  begin
133
319
  logger.debug "Processing file: #{file_path}"
134
320
 
135
- # Convert YAML to JSON
321
+ # Load YAML to check raw data
322
+ raw_yaml = YAML.load_file(file_path)
323
+ if options[:debug]
324
+ logger.debug "Raw YAML data for debug:"
325
+ logger.debug raw_yaml.inspect
326
+ logger.debug ""
327
+ end
328
+
329
+ # Convert YAML to hash
136
330
  data = SitedogParser::Parser.to_hash(file_path, { logger: logger })
137
331
  logger.debug "Data converted to hash"
138
332
 
333
+ # Debug the parsed data
334
+ if options[:debug]
335
+ logger.debug "Parsed data structure:"
336
+ logger.debug data.inspect
337
+ logger.debug ""
338
+ end
339
+
139
340
  # Convert to JSON based on formatting options
140
341
  json_data = if options[:compact_children]
141
342
  logger.debug "Generating JSON with compact inner objects"
data/lib/service.rb CHANGED
@@ -1,11 +1,13 @@
1
- class Service < Data.define(:service, :url, :children, :image_url)
2
- def initialize(service:, url: nil, children: [], image_url: nil)
1
+ class Service < Data.define(:service, :url, :children, :image_url, :properties, :value)
2
+ def initialize(service:, url: nil, children: [], image_url: nil, properties: {}, value: nil)
3
3
  raise ArgumentError, "Service cannot be empty" if service.nil? || service.empty?
4
4
 
5
5
  service => String
6
6
  url => String if url
7
7
  children => Array if children
8
8
  image_url => String if image_url
9
+ properties => Hash if properties
10
+ # value может быть любого типа, поэтому не проверяем
9
11
 
10
12
  super
11
13
  end
@@ -61,6 +61,10 @@ class ServiceFactory
61
61
  in Hash
62
62
  logger.debug "hash: #{data}"
63
63
 
64
+ # Check if all values are URL-like strings
65
+ all_url_like = data.values.all? { |v| v.is_a?(String) && UrlChecker.url_like?(v) }
66
+ logger.debug "All values are URL-like: #{all_url_like}, values: #{data.values.map { |v| "#{v.class}: #{v}" }.join(', ')}"
67
+
64
68
  # Protection from nil values in key fields
65
69
  if (data.key?(:service) || data.key?("service")) &&
66
70
  (data[:service].nil? || data["service"].nil?)
@@ -77,6 +81,8 @@ class ServiceFactory
77
81
  # Первый приоритет - поиск в словаре по URL
78
82
  child_dict_entry = dictionary.match(url_value)
79
83
 
84
+ logger.debug "Child for #{key}: service_name=#{service_name}, url=#{url_value}, dict_entry=#{child_dict_entry}"
85
+
80
86
  if child_dict_entry && child_dict_entry['name']
81
87
  # Если нашли запись в словаре по URL, используем её имя вместо ключа
82
88
  service_name = child_dict_entry['name']
@@ -103,10 +109,45 @@ class ServiceFactory
103
109
 
104
110
  # Create parent service with child elements
105
111
  if service_type && children.any?
112
+ logger.debug "Returning service for #{service_type} with #{children.size} children"
106
113
  return Service.new(service: service_type.to_s, children: children)
107
114
  elsif children.size == 1
108
- # If only one service and no service_type, return it directly
115
+ # If only one service and no service_type, return it
116
+ logger.debug "Returning single child service (no service_type)"
109
117
  return children.first
118
+ else
119
+ logger.debug "Not returning a service for #{data.inspect}, service_type=#{service_type}, children.size=#{children.size}"
120
+ end
121
+ # 1.5 Check if hash contains at least some URL-like strings
122
+ elsif data.values.any? { |v| v.is_a?(String) && UrlChecker.url_like?(v) }
123
+ logger.debug "hash with some URL-like values: #{data.inspect}"
124
+
125
+ # Debug: Check each value for URL-like
126
+ data.each do |k, v|
127
+ if v.is_a?(String)
128
+ logger.debug " Checking #{k}: #{v} - URL-like? #{UrlChecker.url_like?(v)}"
129
+ else
130
+ logger.debug " Skipping non-string #{k}: #{v.class}"
131
+ end
132
+ end
133
+
134
+ # Сохраняем все значения в properties, сохраняя порядок
135
+ properties = {}
136
+ data.each do |key, value|
137
+ properties[key.to_s] = value
138
+ logger.debug "Added property for #{key}: #{value}"
139
+ end
140
+
141
+ # Create service with properties only
142
+ if !properties.empty?
143
+ service = Service.new(
144
+ service: service_type.to_s,
145
+ url: nil,
146
+ properties: properties,
147
+ children: [] # Пустой массив children
148
+ )
149
+ logger.debug "Returning service with #{properties.size} properties"
150
+ return service
110
151
  end
111
152
  end
112
153
 
@@ -225,19 +266,38 @@ class ServiceFactory
225
266
  in Array
226
267
  logger.debug "array: #{data}"
227
268
 
228
- # Create services from array elements
229
- children = data.map { |item| create(item, service_type, dictionary_path) }.compact
269
+ # Create services from all array elements for children
270
+ children = []
271
+ data.each_with_index do |item, index|
272
+ # Для URL-подобных строк используем стандартный механизм
273
+ if item.is_a?(String) && UrlChecker.url_like?(item)
274
+ child_service = create(item, service_type, dictionary_path, options)
275
+ children << child_service if child_service
276
+ else
277
+ # Для простых значений создаем сервис с value
278
+ child_service = Service.new(
279
+ service: service_type ? service_type.to_s : "value",
280
+ url: nil,
281
+ properties: {},
282
+ value: item # Используем поле value
283
+ )
284
+ children << child_service
285
+ logger.debug "Created service with value for item #{index}: #{item.inspect}"
286
+ end
287
+ end
230
288
 
231
- # If there are child services, create a parent service with them
232
- if children.any? && service_type
233
- return Service.new(service: service_type.to_s, children: children)
234
- elsif children.size == 1
235
- # If only one child service, return it
236
- return children.first
289
+ # Return service with all items as children
290
+ if service_type
291
+ result = Service.new(
292
+ service: service_type.to_s,
293
+ url: nil,
294
+ children: children
295
+ )
296
+ logger.debug "Returning array service with #{children.size} children"
297
+ return result
237
298
  end
238
299
 
239
- # If no child services or no name for parent service,
240
- # return nil
300
+ # Fallback to nil if no service_type
241
301
  return nil
242
302
  else
243
303
  # Handle values that don't match any pattern
@@ -1,3 +1,3 @@
1
1
  module SitedogParser
2
- VERSION = "0.3.1"
2
+ VERSION = "0.4.1"
3
3
  end
@@ -68,9 +68,16 @@ module SitedogParser
68
68
  # Для обычных полей создаем сервис
69
69
  service = ServiceFactory.create(data, service_type, dictionary_path, options)
70
70
 
71
+ # Debug output
72
+ if logger
73
+ logger.debug "ServiceFactory.create for #{service_type}: #{service.inspect}"
74
+ end
75
+
71
76
  if service
72
77
  services[service_type] ||= []
73
78
  services[service_type] << service
79
+ elsif logger
80
+ logger.debug "Service for #{service_type} is nil, field will be skipped"
74
81
  end
75
82
  end
76
83
  end
@@ -103,11 +110,49 @@ module SitedogParser
103
110
  if service_data.is_a?(Array) && service_data.first.is_a?(Service)
104
111
  # Преобразуем массив сервисов в массив хешей
105
112
  result[domain_key][service_type_key] = service_data.map do |service|
106
- {
113
+ service_hash = {
107
114
  'service' => service.service,
108
- 'url' => service.url,
109
- 'children' => service.children.map { |child| {'service' => child.service, 'url' => child.url} }
115
+ 'url' => service.url
110
116
  }
117
+
118
+ # Добавляем image_url если он есть
119
+ if service.image_url
120
+ service_hash['image_url'] = service.image_url
121
+ end
122
+
123
+ # Добавляем children только если они есть
124
+ if service.children && !service.children.empty?
125
+ service_hash['children'] = service.children.map do |child|
126
+ child_hash = {
127
+ 'service' => child.service,
128
+ 'url' => child.url
129
+ }
130
+
131
+ # Добавляем image_url для детей если он есть
132
+ if child.image_url
133
+ child_hash['image_url'] = child.image_url
134
+ end
135
+
136
+ # Добавляем properties для children если они есть
137
+ if child.properties && !child.properties.empty?
138
+ child_hash['properties'] = child.properties
139
+ end
140
+
141
+ # Добавляем value для children если оно есть
142
+ if child.value
143
+ child_hash['value'] = child.value
144
+ end
145
+
146
+ child_hash
147
+ end
148
+ end
149
+
150
+ # Добавляем properties, если они есть
151
+ if service.properties && !service.properties.empty?
152
+ service_hash['properties'] = service.properties
153
+ end
154
+
155
+ service_hash
111
156
  end
112
157
  else
113
158
  # Сохраняем простые поля как есть
data/lib/url_checker.rb CHANGED
@@ -28,7 +28,7 @@ module UrlChecker
28
28
  end
29
29
 
30
30
  # Check for standard URLs
31
- pattern = /^((?:https?|ftp|sftp|ftps|ssh|git|ws|wss):\/\/)?[a-zA-Z0-9][-a-zA-Z0-9.]+\.[a-zA-Z]{2,}(:[0-9]+)?(\/[-a-zA-Z0-9%_.~#+]*)*(\?[-a-zA-Z0-9%_&=.~#+]*)?(#[-a-zA-Z0-9%_&=.~#+\/]*)?$/
31
+ pattern = /^((?:https?|ftp|sftp|ftps|ssh|git|ws|wss):\/\/)?((?:[a-zA-Z0-9][-a-zA-Z0-9.]+\.[a-zA-Z]{2,})|(?:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}))(:[0-9]+)?(\/[-a-zA-Z0-9%_.~#+]*)*(\?[-a-zA-Z0-9%_&=.~#+]*)?(#[-a-zA-Z0-9%_&=.~#+\/]*)?$/
32
32
 
33
33
  !!string.match(pattern)
34
34
  end
@@ -61,6 +61,11 @@ module UrlChecker
61
61
  # Remove protocol and www prefix if present
62
62
  domain = url.gsub(%r{^(?:https?://)?(?:www\.)?}, "")
63
63
 
64
+ # Check if it's an IP address
65
+ if domain.match?(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/)
66
+ return "IP Address"
67
+ end
68
+
64
69
  # Extract domain from URL by removing everything after first / or : or ? or #
65
70
  domain = domain.split(/[:\/?#]/).first
66
71
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sitedog_parser
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Nemytchenko
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-05-07 00:00:00.000000000 Z
11
+ date: 2025-05-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler