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 +4 -4
 - data/bin/sitedog_cli +202 -1
 - data/lib/service.rb +4 -2
 - data/lib/service_factory.rb +71 -11
 - data/lib/sitedog_parser/version.rb +1 -1
 - data/lib/sitedog_parser.rb +48 -3
 - data/lib/url_checker.rb +6 -1
 - metadata +2 -2
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: d0eb9da31315637d91c4b4b312cc6382a88f9c1daf094c3296b59a47ecb23169
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: ea11849351936255636f6c62786d24ee00a9d3a0dbff3dfbf2bb96b517ea4f26
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 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 
     | 
    
         
            -
              #  
     | 
| 
      
 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
         
     | 
    
        data/lib/service_factory.rb
    CHANGED
    
    | 
         @@ -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 
     | 
| 
      
 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 =  
     | 
| 
      
 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 
     | 
    
         
            -
                  #  
     | 
| 
       232 
     | 
    
         
            -
                  if  
     | 
| 
       233 
     | 
    
         
            -
                     
     | 
| 
       234 
     | 
    
         
            -
             
     | 
| 
       235 
     | 
    
         
            -
             
     | 
| 
       236 
     | 
    
         
            -
             
     | 
| 
      
 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 
     | 
    
         
            -
                  #  
     | 
| 
       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
         
     | 
    
        data/lib/sitedog_parser.rb
    CHANGED
    
    | 
         @@ -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. 
     | 
| 
      
 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- 
     | 
| 
      
 11 
     | 
    
         
            +
            date: 2025-05-20 00:00:00.000000000 Z
         
     | 
| 
       12 
12 
     | 
    
         
             
            dependencies:
         
     | 
| 
       13 
13 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       14 
14 
     | 
    
         
             
              name: bundler
         
     |