module AisleriotCatalog
Constants
- DEFAULT_ENGINE_DIR
- DEFAULT_HELP_DIR
- GameEntry
- LIST_PER_PAGE
- SKIP_HELP_FILES
Public Instance Methods
Source
# File lib/aisleriot_catalog.rb, line 20 def catalog(engine_dir: DEFAULT_ENGINE_DIR, help_dir: DEFAULT_HELP_DIR, coded_ids: []) coded = coded_ids.map { |id| loose_id(id) }.to_set engines = scan_engines(engine_dir) helps = scan_help(help_dir) ids = (engines.keys + helps.keys).map { |id| normalize_id(id) }.uniq.sort ids.filter_map do |id| module_id = resolve_module_id(id, engines, helps) help_id = resolve_help_id(id, engines, helps) next unless module_id || help_id canonical = module_id || help_to_module_id(help_id, engine_dir: engine_dir) help_path = helps[help_id] || helps[canonical] || helps[help_id_for_module(canonical, help_dir: help_dir)] engine_path = engines[canonical] || engines[module_id] meta = help_path ? parse_help_xml(help_path) : { title: titleize_id(canonical), goal: nil, setup: {} } GameEntry.new( id: canonical, module: canonical, title: meta[:title], goal: meta[:goal], setup: meta[:setup], help_path: help_path, engine_path: engine_path, coded: coded.include?(loose_id(canonical)) ) end end
Source
# File lib/aisleriot_catalog.rb, line 160 def engine_file?(engine_dir, module_id) File.file?(File.join(engine_dir.to_s, "#{module_id}.go")) end
Source
# File lib/aisleriot_catalog.rb, line 49 def find(id, engine_dir: DEFAULT_ENGINE_DIR, help_dir: DEFAULT_HELP_DIR, coded_ids: []) needle = loose_id(id) catalog(engine_dir: engine_dir, help_dir: help_dir, coded_ids: coded_ids) .find { |entry| loose_id(entry.id) == needle } end
Source
# File lib/aisleriot_catalog.rb, line 86 def format_coded_list(filtered, playable) return "No games are playable in IRC yet." if playable.zero? filtered.map { |entry| "[playable] #{entry.title} (#{entry.id})" }.join("\n") end
Source
# File lib/aisleriot_catalog.rb, line 92 def format_info(entry) lines = ["#{entry.title} (#{entry.id})"] lines << "Play: !solitaire #{entry.id}" lines << "Status: playable in IRC" if entry.coded lines << "Status: catalogued — not ported to IRC yet" unless entry.coded lines << "Goal: #{entry.goal}" if entry.goal && !entry.goal.empty? entry.setup.each do |label, text| lines << "#{label}: #{text}" end lines.join("\n") end
Source
# File lib/aisleriot_catalog.rb, line 55 def format_list(entries, page: 1, per_page: LIST_PER_PAGE, filter: :all) filtered = case filter when :coded then entries.select(&:coded) else entries end playable = entries.count(&:coded) total = entries.length pages = [(filtered.length.to_f / per_page).ceil, 1].max page = page.to_i page = 1 if page < 1 page = pages if page > pages summary = "#{total} games (#{playable} playable in IRC). " \ "Use !solitaire list all [page] or !solitaire list coded." return summary if filter == :summary return format_coded_list(filtered, playable) if filter == :coded && filtered.empty? start = (page - 1) * per_page slice = filtered[start, per_page] || [] lines = [] lines << summary if filter == :all lines << "#{playable} playable:" if filter == :coded lines << "Page #{page}/#{pages}:" if pages > 1 slice.each do |entry| marker = entry.coded ? "[playable]" : "[spec]" lines << "#{marker} #{entry.title} (#{entry.id})" end lines.join("\n") end
Source
# File lib/aisleriot_catalog.rb, line 164 def help_file?(help_dir, help_id) File.file?(File.join(help_dir.to_s, "#{help_id}.xml")) end
Source
# File lib/aisleriot_catalog.rb, line 154 def help_id_for_module(module_id, help_dir: DEFAULT_HELP_DIR) return module_id if help_file?(help_dir, module_id) module_id.tr("-", "_") end
Source
# File lib/aisleriot_catalog.rb, line 147 def help_to_module_id(help_id, engine_dir: DEFAULT_ENGINE_DIR) hyphen = help_id.tr("_", "-") return hyphen if engine_file?(engine_dir, hyphen) help_id end
Source
# File lib/aisleriot_catalog.rb, line 108 def loose_id(id) normalize_id(id).gsub("-", "") end
Source
# File lib/aisleriot_catalog.rb, line 104 def normalize_id(id) id.to_s.strip.downcase.gsub(/[\s_]+/, "-") end
Source
# File lib/aisleriot_catalog.rb, line 172 def parse_help_xml(path) xml = File.read(path) doc = REXML::Document.new(xml) title = doc.elements["//title"]&.text.to_s.strip goal = doc.elements["//sect2[title='Goal']/para"]&.text.to_s.strip setup = {} doc.elements.each("//sect2[title='Setup']//row") do |row| label = row.elements["entry[1]"]&.text.to_s.strip value = row.elements["entry[2]"]&.text.to_s.strip next if label.empty? setup[label] = value end { title: title.empty? ? titleize_id(File.basename(path, ".xml")) : title, goal: goal, setup: setup } rescue StandardError { title: titleize_id(File.basename(path, ".xml")), goal: nil, setup: {} } end
Source
# File lib/aisleriot_catalog.rb, line 142 def resolve_help_id(id, engines, helps) normalized = normalize_id(id) helps.keys.find { |key| normalize_id(help_to_module_id(key)) == normalized || normalize_id(key) == normalized } end
Source
# File lib/aisleriot_catalog.rb, line 137 def resolve_module_id(id, engines, helps) normalized = normalize_id(id) engines.keys.find { |key| normalize_id(key) == normalized } end
Source
# File lib/aisleriot_catalog.rb, line 112 def scan_engines(engine_dir) return {} unless engine_dir && Dir.exist?(engine_dir) Dir.children(engine_dir).each_with_object({}) do |name, memo| next unless name.end_with?(".go") next if name == "api.go" module_id = File.basename(name, ".go") memo[module_id] = File.join(engine_dir, name) end end
Source
# File lib/aisleriot_catalog.rb, line 124 def scan_help(help_dir) return {} unless help_dir && Dir.exist?(help_dir) Dir.children(help_dir).each_with_object({}) do |name, memo| next unless name.end_with?(".xml") help_id = File.basename(name, ".xml") next if SKIP_HELP_FILES.include?(help_id) memo[help_id] = File.join(help_dir, name) end end
Source
# File lib/aisleriot_catalog.rb, line 168 def titleize_id(id) TextUtil.title_words(id.tr("-", " ")) end