atlasq 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1 @@
1
+ {"978":["eur"],"eur":["eur"],"€":["eur"],"euro":["eur"],"784":["aed"],"aed":["aed"],"د.ا":["aed","jod"],"united":["aed","usd"],"arab":["aed"],"emirates":["aed"],"dirham":["aed","mad"],"971":["afn"],"afn":["afn"],"؋":["afn"],"afghan":["afn"],"afghani":["afn"],"951":["xcd"],"xcd":["xcd"],"$":["xcd","usd","ars","aud","bbd","bmd","bnd","bsd","bzd","cad","nzd","clp","cop","cup","cve","dop","fjd","gyd","hkd","jmd","kyd","lrd","mxn","nad","sbd","sgd","srd","ttd","twd"],"east":["xcd"],"caribbean":["xcd"],"dollar":["xcd","usd","aud","bbd","bmd","bnd","bsd","bzd","cad","nzd","fjd","gyd","hkd","jmd","kyd","lrd","nad","sbd","sgd","srd","ttd","twd"],"008":["all"],"all":["all"],"l":["all","hnl","lsl","mdl"],"albanian":["all"],"lek":["all"],"051":["amd"],"amd":["amd"],"դր.":["amd"],"armenian":["amd"],"dram":["amd"],"973":["aoa"],"aoa":["aoa"],"kz":["aoa"],"angolan":["aoa"],"kwanza":["aoa"],"840":["usd"],"usd":["usd"],"states":["usd"],"032":["ars"],"ars":["ars"],"argentine":["ars"],"peso":["ars","clp","cop","cup","dop","mxn","php","uyu"],"036":["aud"],"aud":["aud"],"australian":["aud"],"533":["awg"],"awg":["awg"],"f":["awg","ang"],"aruban":["awg"],"florin":["awg"],"944":["azn"],"azn":["azn"],"₼":["azn"],"azerbaijani":["azn"],"manat":["azn","tmt"],"977":["bam"],"bam":["bam"],"км":["bam"],"bosnia":["bam"],"and":["bam","std","ttd"],"herzegovina":["bam"],"convertible":["bam"],"mark":["bam"],"052":["bbd"],"bbd":["bbd"],"barbadian":["bbd"],"050":["bdt"],"bdt":["bdt"],"৳":["bdt"],"bangladeshi":["bdt"],"taka":["bdt"],"952":["xof"],"xof":["xof"],"fr":["xof","bif","cdf","gnf","kmf","xpf"],"west":["xof"],"african":["xof","xaf","zar"],"cfa":["xof","xaf"],"franc":["xof","bif","cdf","xaf","chf","djf","gnf","kmf","xpf","rwf"],"975":["bgn"],"bgn":["bgn"],"лв.":["bgn"],"bulgarian":["bgn"],"lev":["bgn"],"048":["bhd"],"bhd":["bhd"],"د.ب":["bhd"],"bahraini":["bhd"],"dinar":["bhd","dzd","iqd","jod","kwd","lyd","rsd","tnd"],"108":["bif"],"bif":["bif"],"burundian":["bif"],"060":["bmd"],"bmd":["bmd"],"bermudian":["bmd"],"096":["bnd"],"bnd":["bnd"],"brunei":["bnd"],"068":["bob"],"bob":["bob"],"bs.":["bob"],"bolivian":["bob"],"boliviano":["bob"],"986":["brl"],"brl":["brl"],"r$":["brl"],"brazilian":["brl"],"real":["brl"],"044":["bsd"],"bsd":["bsd"],"bahamian":["bsd"],"064":["btn"],"btn":["btn"],"nu.":["btn"],"bhutanese":["btn"],"ngultrum":["btn"],"578":["nok"],"nok":["nok"],"kr":["nok","sek"],"norwegian":["nok"],"krone":["nok","dkk"],"072":["bwp"],"bwp":["bwp"],"p":["bwp","mop"],"botswana":["bwp"],"pula":["bwp"],"933":["byn"],"byn":["byn"],"br":["byn","etb"],"belarusian":["byn"],"ruble":["byn","rub"],"084":["bzd"],"bzd":["bzd"],"belize":["bzd"],"124":["cad"],"cad":["cad"],"canadian":["cad"],"976":["cdf"],"cdf":["cdf"],"congolese":["cdf"],"950":["xaf"],"xaf":["xaf"],"central":["xaf"],"756":["chf"],"chf":["chf"],"swiss":["chf"],"554":["nzd"],"nzd":["nzd"],"new":["nzd","ils","pgk","twd"],"zealand":["nzd"],"152":["clp"],"clp":["clp"],"chilean":["clp"],"156":["cny"],"cny":["cny"],"¥":["cny","jpy"],"chinese":["cny"],"renminbi":["cny"],"yuan":["cny"],"170":["cop"],"cop":["cop"],"colombian":["cop"],"188":["crc"],"crc":["crc"],"₡":["crc"],"costa":["crc"],"rican":["crc"],"colon":["crc"],"192":["cup"],"cup":["cup"],"cuban":["cup"],"132":["cve"],"cve":["cve"],"cape":["cve"],"verdean":["cve"],"escudo":["cve"],"532":["ang"],"ang":["ang"],"netherlands":["ang"],"antillean":["ang"],"gulden":["ang"],"203":["czk"],"czk":["czk"],"kc":["czk"],"czech":["czk"],"koruna":["czk"],"262":["djf"],"djf":["djf"],"fdj":["djf"],"djiboutian":["djf"],"208":["dkk"],"dkk":["dkk"],"kr.":["dkk","isk"],"danish":["dkk"],"214":["dop"],"dop":["dop"],"dominican":["dop"],"012":["dzd"],"dzd":["dzd"],"د.ج":["dzd"],"algerian":["dzd"],"818":["egp"],"egp":["egp"],"ج.م":["egp"],"egyptian":["egp"],"pound":["egp","fkp","gbp","gip","lbp","sdg","shp","ssp","syp"],"504":["mad"],"mad":["mad"],"د.م.":["mad"],"moroccan":["mad"],"230":["etb"],"etb":["etb"],"ethiopian":["etb"],"birr":["etb"],"242":["fjd"],"fjd":["fjd"],"fijian":["fjd"],"238":["fkp"],"fkp":["fkp"],"£":["fkp","gbp","gip","sdg","shp","ssp"],"falkland":["fkp"],"826":["gbp"],"gbp":["gbp"],"british":["gbp"],"981":["gel"],"gel":["gel"],"ლ":["gel"],"georgian":["gel"],"lari":["gel"],"936":["ghs"],"ghs":["ghs"],"₵":["ghs"],"ghanaian":["ghs"],"cedi":["ghs"],"292":["gip"],"gip":["gip"],"gibraltar":["gip"],"270":["gmd"],"gmd":["gmd"],"d":["gmd"],"gambian":["gmd"],"dalasi":["gmd"],"324":["gnf"],"gnf":["gnf"],"guinean":["gnf","pgk"],"320":["gtq"],"gtq":["gtq"],"q":["gtq"],"guatemalan":["gtq"],"quetzal":["gtq"],"328":["gyd"],"gyd":["gyd"],"guyanese":["gyd"],"344":["hkd"],"hkd":["hkd"],"hong":["hkd"],"kong":["hkd"],"340":["hnl"],"hnl":["hnl"],"honduran":["hnl"],"lempira":["hnl"],"332":["htg"],"htg":["htg"],"g":["htg"],"haitian":["htg"],"gourde":["htg"],"348":["huf"],"huf":["huf"],"ft":["huf"],"hungarian":["huf"],"forint":["huf"],"360":["idr"],"idr":["idr"],"rp":["idr"],"indonesian":["idr"],"rupiah":["idr"],"376":["ils"],"ils":["ils"],"₪":["ils"],"israeli":["ils"],"sheqel":["ils"],"356":["inr"],"inr":["inr"],"₹":["inr"],"indian":["inr"],"rupee":["inr","lkr","mur","npr","pkr","scr"],"368":["iqd"],"iqd":["iqd"],"ع.د":["iqd"],"iraqi":["iqd"],"364":["irr"],"irr":["irr"],"ریال":["irr","yer"],"iranian":["irr"],"rial":["irr","omr","yer"],"352":["isk"],"isk":["isk"],"icelandic":["isk"],"krona":["isk","sek"],"388":["jmd"],"jmd":["jmd"],"jamaican":["jmd"],"400":["jod"],"jod":["jod"],"jordanian":["jod"],"392":["jpy"],"jpy":["jpy"],"japanese":["jpy"],"yen":["jpy"],"404":["kes"],"kes":["kes"],"ksh":["kes"],"kenyan":["kes"],"shilling":["kes","sos","tzs","ugx"],"417":["kgs"],"kgs":["kgs"],"som":["kgs","uzs"],"kyrgyzstani":["kgs"],"116":["khr"],"khr":["khr"],"៛":["khr"],"cambodian":["khr"],"riel":["khr"],"174":["kmf"],"kmf":["kmf"],"comorian":["kmf"],"408":["kpw"],"kpw":["kpw"],"₩":["kpw","krw"],"north":["kpw"],"korean":["kpw","krw"],"won":["kpw","krw"],"410":["krw"],"krw":["krw"],"south":["krw","ssp","zar"],"414":["kwd"],"kwd":["kwd"],"د.ك":["kwd"],"kuwaiti":["kwd"],"136":["kyd"],"kyd":["kyd"],"cayman":["kyd"],"islands":["kyd","sbd"],"398":["kzt"],"kzt":["kzt"],"₸":["kzt"],"kazakhstani":["kzt"],"tenge":["kzt"],"418":["lak"],"lak":["lak"],"₭":["lak"],"lao":["lak"],"kip":["lak"],"422":["lbp"],"lbp":["lbp"],"ل.ل":["lbp"],"lebanese":["lbp"],"144":["lkr"],"lkr":["lkr"],"Rs":["lkr","mur","pkr","scr"],"sri":["lkr"],"lankan":["lkr"],"430":["lrd"],"lrd":["lrd"],"liberian":["lrd"],"426":["lsl"],"lsl":["lsl"],"lesotho":["lsl"],"loti":["lsl"],"434":["lyd"],"lyd":["lyd"],"ل.د":["lyd"],"libyan":["lyd"],"498":["mdl"],"mdl":["mdl"],"moldovan":["mdl"],"leu":["mdl","ron"],"969":["mga"],"mga":["mga"],"ar":["mga"],"malagasy":["mga"],"ariary":["mga"],"807":["mkd"],"mkd":["mkd"],"ден":["mkd"],"macedonian":["mkd"],"denar":["mkd"],"104":["mmk"],"mmk":["mmk"],"k":["mmk","pgk","zmw"],"myanmar":["mmk"],"kyat":["mmk"],"496":["mnt"],"mnt":["mnt"],"₮":["mnt"],"mongolian":["mnt"],"togrog":["mnt"],"446":["mop"],"mop":["mop"],"macanese":["mop"],"pataca":["mop"],"929":["mru"],"mru":["mru"],"um":["mru"],"mauritanian":["mru"],"ouguiya":["mru"],"480":["mur"],"mur":["mur"],"mauritian":["mur"],"462":["mvr"],"mvr":["mvr"],"maldivian":["mvr"],"rufiyaa":["mvr"],"454":["mwk"],"mwk":["mwk"],"mk":["mwk"],"malawian":["mwk"],"kwacha":["mwk","zmw"],"484":["mxn"],"mxn":["mxn"],"mexican":["mxn"],"458":["myr"],"myr":["myr"],"rm":["myr"],"malaysian":["myr"],"ringgit":["myr"],"943":["mzn"],"mzn":["mzn"],"mtn":["mzn"],"mozambican":["mzn"],"metical":["mzn"],"516":["nad"],"nad":["nad"],"namibian":["nad"],"953":["xpf"],"xpf":["xpf"],"cfp":["xpf"],"566":["ngn"],"ngn":["ngn"],"₦":["ngn"],"nigerian":["ngn"],"naira":["ngn"],"558":["nio"],"nio":["nio"],"c$":["nio"],"nicaraguan":["nio"],"cordoba":["nio"],"524":["npr"],"npr":["npr"],"rs.":["npr"],"nepalese":["npr"],"512":["omr"],"omr":["omr"],"ر.ع.":["omr"],"omani":["omr"],"590":["pab"],"pab":["pab"],"b/.":["pab"],"panamanian":["pab"],"balboa":["pab"],"604":["pen"],"pen":["pen"],"s/":["pen"],"peruvian":["pen"],"sol":["pen"],"598":["pgk"],"pgk":["pgk"],"papua":["pgk"],"kina":["pgk"],"608":["php"],"php":["php"],"₱":["php"],"philippine":["php"],"586":["pkr"],"pkr":["pkr"],"pakistani":["pkr"],"985":["pln"],"pln":["pln"],"zl":["pln"],"polish":["pln"],"zloty":["pln"],"600":["pyg"],"pyg":["pyg"],"₲":["pyg"],"paraguayan":["pyg"],"guarani":["pyg"],"634":["qar"],"qar":["qar"],"ر.ق":["qar"],"qatari":["qar"],"riyal":["qar","sar"],"946":["ron"],"ron":["ron"],"lei":["ron"],"romanian":["ron"],"941":["rsd"],"rsd":["rsd"],"рсд":["rsd"],"serbian":["rsd"],"643":["rub"],"rub":["rub"],"₽":["rub"],"russian":["rub"],"646":["rwf"],"rwf":["rwf"],"frw":["rwf"],"rwandan":["rwf"],"682":["sar"],"sar":["sar"],"ر.س":["sar"],"saudi":["sar"],"090":["sbd"],"sbd":["sbd"],"solomon":["sbd"],"690":["scr"],"scr":["scr"],"seychellois":["scr"],"938":["sdg"],"sdg":["sdg"],"sudanese":["sdg","ssp"],"752":["sek"],"sek":["sek"],"swedish":["sek"],"702":["sgd"],"sgd":["sgd"],"singapore":["sgd"],"654":["shp"],"shp":["shp"],"saint":["shp"],"helenian":["shp"],"694":["sll"],"sll":["sll"],"le":["sll"],"sierra":["sll"],"leonean":["sll"],"leone":["sll"],"706":["sos"],"sos":["sos"],"sh":["sos","tzs"],"somali":["sos"],"968":["srd"],"srd":["srd"],"surinamese":["srd"],"728":["ssp"],"ssp":["ssp"],"678":["std"],"std":["std"],"db":["std"],"sao":["std"],"tome":["std"],"principe":["std"],"dobra":["std"],"760":["syp"],"syp":["syp"],"£s":["syp"],"syrian":["syp"],"748":["szl"],"szl":["szl"],"e":["szl"],"swazi":["szl"],"lilangeni":["szl"],"764":["thb"],"thb":["thb"],"฿":["thb"],"thai":["thb"],"baht":["thb"],"972":["tjs"],"tjs":["tjs"],"ѕм":["tjs"],"tajikistani":["tjs"],"somoni":["tjs"],"934":["tmt"],"tmt":["tmt"],"t":["tmt","wst"],"turkmenistani":["tmt"],"788":["tnd"],"tnd":["tnd"],"د.ت":["tnd"],"tunisian":["tnd"],"776":["top"],"top":["top"],"t$":["top"],"tongan":["top"],"paʻanga":["top"],"949":["try"],"try":["try"],"₺":["try"],"turkish":["try"],"lira":["try"],"780":["ttd"],"ttd":["ttd"],"trinidad":["ttd"],"tobago":["ttd"],"901":["twd"],"twd":["twd"],"taiwan":["twd"],"834":["tzs"],"tzs":["tzs"],"tanzanian":["tzs"],"980":["uah"],"uah":["uah"],"₴":["uah"],"ukrainian":["uah"],"hryvnia":["uah"],"800":["ugx"],"ugx":["ugx"],"ush":["ugx"],"ugandan":["ugx"],"858":["uyu"],"uyu":["uyu"],"$u":["uyu"],"uruguayan":["uyu"],"860":["uzs"],"uzs":["uzs"],"so'm":["uzs"],"uzbekistan":["uzs"],"928":["ves"],"ves":["ves"],"bs":["ves"],"venezuelan":["ves"],"bolivar":["ves"],"soberano":["ves"],"704":["vnd"],"vnd":["vnd"],"₫":["vnd"],"vietnamese":["vnd"],"dong":["vnd"],"548":["vuv"],"vuv":["vuv"],"vt":["vuv"],"vanuatu":["vuv"],"vatu":["vuv"],"882":["wst"],"wst":["wst"],"samoan":["wst"],"tala":["wst"],"886":["yer"],"yer":["yer"],"yemeni":["yer"],"710":["zar"],"zar":["zar"],"r":["zar"],"rand":["zar"],"967":["zmw"],"zmw":["zmw"],"zambian":["zmw"]}
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Atlasq
6
+ module Cache
7
+ CACHE_DIR = File.expand_path("../../cache", __dir__).freeze
8
+ private_constant :CACHE_DIR
9
+
10
+ # @param full_name [String] namespace + file name (ex. "info/countries.json")
11
+ # @return file
12
+ def self.get(full_name)
13
+ @get ||= {}
14
+ @get.fetch(full_name) do
15
+ path = "#{CACHE_DIR}/#{full_name}"
16
+ content = File.read(path)
17
+
18
+ case full_name
19
+ when /\.json$/
20
+ JSON.parse(content)
21
+ else
22
+ content
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -7,11 +7,13 @@ module Atlasq
7
7
  class Any < Base
8
8
  def content
9
9
  search_terms.map do |term|
10
- data = Data.any(term)
11
- empty = data.empty? if data.respond_to?(:empty?)
12
-
13
- if data && !empty
14
- Format.any(data, term)
10
+ if (country = Data.country(term))
11
+ Format.country(country, term)
12
+ elsif (countries = Data.countries_by_region(term))
13
+ region_name = Util::String.titleize(term)
14
+ Format.countries(countries, title: "Region: #{region_name}")
15
+ elsif (currencies = Data.countries_by_currencies(term))
16
+ Format.currencies(currencies)
15
17
  else
16
18
  Atlasq.failed!
17
19
  "Unknown search term: #{term}"
@@ -12,10 +12,13 @@ module Atlasq
12
12
  Format.countries(countries, title: "All Countries")
13
13
  else
14
14
  search_terms.map do |term|
15
- country = Data.country(term)
16
-
17
- if country
15
+ if (country = Data.country(term))
18
16
  Format.country(country, term)
17
+ elsif (country_codes = PartialMatch.countries(term)).any?
18
+ countries = country_codes.map do |code|
19
+ Data.country_by_code(code)
20
+ end
21
+ Format.countries(countries, title: "Countries (Partial Match)")
19
22
  else
20
23
  Atlasq.failed!
21
24
  "Unknown country: #{term}"
@@ -41,6 +41,7 @@ module Atlasq
41
41
  2. alpha3 (ISO3166 standard 3 letter code)
42
42
  3. number (ISO3166 standard 3 digit code)
43
43
  4. name (common, localized, unofficial)
44
+ 5. partial match on name
44
45
 
45
46
  -r/--region
46
47
  : Display all countries by subregion
@@ -58,6 +59,7 @@ module Atlasq
58
59
  1. code (ISO4127 standard 3 letter code)
59
60
  2. name (ISO4127 standard name)
60
61
  3. symbol
62
+ 4. partial match on name
61
63
 
62
64
  -h/--help
63
65
  : Display this page
@@ -11,13 +11,14 @@ module Atlasq
11
11
  Format.currencies(currencies)
12
12
  else
13
13
  search_terms.map do |term|
14
- currencies = Data.currencies(term)
15
-
16
- if currencies.empty?
14
+ if (currencies = Data.countries_by_currencies(term))
15
+ Format.currencies(currencies)
16
+ elsif (currency_codes = PartialMatch.currencies(term)).any?
17
+ currencies = Data.countries_by_currencies(currency_codes)
18
+ Format.currencies(currencies, partial_match: true)
19
+ else
17
20
  Atlasq.failed!
18
21
  "Unknown currency: #{term}"
19
- else
20
- Format.currencies(currencies)
21
22
  end
22
23
  end.join("\n\n")
23
24
  end
@@ -11,10 +11,9 @@ module Atlasq
11
11
  Format.subregions(subregions)
12
12
  else
13
13
  search_terms.map do |term|
14
- region = Data.region(term)
15
-
16
- if region
17
- Format.region(region)
14
+ if (countries = Data.countries_by_region(term))
15
+ region_name = Util::String.titleize(term)
16
+ Format.countries(countries, title: "Region: #{region_name}")
18
17
  else
19
18
  Atlasq.failed!
20
19
  "Unknown region: #{term}"
@@ -17,6 +17,7 @@ module Atlasq
17
17
  def self.parse(args)
18
18
  command = parse_command(args.first)
19
19
  args.shift unless command.to_s == "Atlasq::Command::Any"
20
+ args.map!(&:strip)
20
21
 
21
22
  Options.new(command: command, args: args).freeze
22
23
  end
data/lib/atlasq/data.rb CHANGED
@@ -3,48 +3,28 @@
3
3
  require "countries"
4
4
  require "iso-639"
5
5
  require "money"
6
- require "money-heuristics"
7
6
 
8
- ISO3166.configure do |config|
9
- # Needed to allow us to access the `ISO3166::Country#currency`
10
- # object which ends up being an instance of `Money::Currency`.
11
- config.enable_currency_extension!
12
-
13
- # Needed to allow us to search by localized country name.
14
- config.locales = %i[
15
- af am ar as az be bg bn br bs
16
- ca cs cy da de dz el en eo es
17
- et eu fa fi fo fr ga gl gu he
18
- hi hr hu hy ia id is it ja ka
19
- kk km kn ko ku lt lv mi mk ml
20
- mn mr ms mt nb ne nl nn oc or
21
- pa pl ps pt ro ru rw si sk sl
22
- so sq sr sv sw ta te th ti tk
23
- tl tr tt ug uk ve vi wa wo xh
24
- zh zu
25
- ]
26
- end
7
+ # Needed to allow us to access the `ISO3166::Country#currency`
8
+ # object which ends up being an instance of `Money::Currency`.
9
+ ISO3166.configure(&:enable_currency_extension!)
27
10
 
28
11
  module Atlasq
29
12
  module Data
30
13
  autoload :Currency, "atlasq/data/currency"
31
- autoload :Region, "atlasq/data/region"
32
14
 
33
15
  # @param term [String]
34
- # @return [ISO3166::Country, Atlasq::Data::Region, Atlasq::Data::Currency, nil]
35
- def self.any(term)
36
- Data.country(term) ||
37
- Data.region(term) ||
38
- Data.currencies(term)
16
+ # @return [ISO3166::Country, nil]
17
+ def self.country(term)
18
+ Cache
19
+ .get("search_index/direct_match_country.json")
20
+ .dig(Util::String.normalize(term))
21
+ &.then { |key| ISO3166::Country.new(key) }
39
22
  end
40
23
 
41
- # @param term [String]
42
- # @return [ISO3166::Country|nil]
43
- def self.country(term)
44
- ISO3166::Country.find_country_by_alpha2(term) ||
45
- ISO3166::Country.find_country_by_alpha3(term) ||
46
- ISO3166::Country.find_country_by_number(term) ||
47
- ISO3166::Country.find_country_by_any_name(term)
24
+ # @param code [String] alpha2 country code
25
+ # @return [ISO3166::Country, nil]
26
+ def self.country_by_code(code)
27
+ ISO3166::Country.find_country_by_alpha2(code)
48
28
  end
49
29
 
50
30
  # @return [Array<ISO3166::Country>]
@@ -52,18 +32,13 @@ module Atlasq
52
32
  @all_countries ||= ISO3166::Country.all
53
33
  end
54
34
 
55
- # Region types for querying ISO3166::Country
56
- REGION_TYPES = %i[region subregion world_region continent].freeze
57
-
58
- # @return [Atlasq::Data::Region|nil]
59
- def self.region(term)
60
- REGION_TYPES.each do |region_type|
61
- countries = ISO3166::Country.find_all_by(region_type, term)
62
- next if countries.empty?
63
-
64
- return Region.new(countries: countries.values, type: region_type)
65
- end
66
- nil
35
+ # @param term [String]
36
+ # @return [Array<ISO3166::Country>, nil]
37
+ def self.countries_by_region(term)
38
+ Cache
39
+ .get("search_index/countries_by_region.json")
40
+ .dig(Util::String.normalize(term))
41
+ &.map { |key| ISO3166::Country.new(key) }
67
42
  end
68
43
 
69
44
  # @return [Hash<String, Array<ISO3166::Country>>] Ex. { "Central Asia" => [...], ... }
@@ -79,24 +54,37 @@ module Atlasq
79
54
  end
80
55
  end
81
56
 
82
- # @param term [String]
83
- # @return [Array<Atlasq::Data::Currency>]
84
- def self.currencies(term)
85
- currency_codes = Money::Currency.analyze(term)
86
- currency_codes.filter_map do |currency_code|
87
- countries = ISO3166::Country.find_all_by(:currency_code, currency_code)
88
- next if countries.empty?
89
-
90
- Currency.new(countries: countries.values, currency_code: currency_code)
91
- end
57
+ # @param terms [String, Array<String>]
58
+ # @return [Hash<Money::Currency, Array<ISO3166::Country>>]
59
+ def self.countries_by_currencies(terms)
60
+ terms = Array(terms).map(&Util::String.method(:normalize))
61
+ currency_codes = Cache
62
+ .get("search_index/direct_match_currency.json")
63
+ .values_at(*terms)
64
+ .compact
65
+
66
+ return if currency_codes.empty?
67
+
68
+ Cache
69
+ .get("search_index/countries_by_currency.json")
70
+ .slice(*currency_codes)
71
+ .to_h do |currency_code, country_codes|
72
+ [
73
+ Money::Currency.new(currency_code),
74
+ country_codes.map(&Data.method(:country_by_code)),
75
+ ]
76
+ end
92
77
  end
93
78
 
94
- # @return [Array<Atlasq::Data::Currency>]
79
+ # @return [Hash<Money::Currency, Array<ISO3166::Country>>]
95
80
  def self.all_currencies
96
- all_countries
97
- .group_by(&:currency_code)
98
- .map do |currency_code, countries|
99
- Currency.new(countries: countries, currency_code: currency_code)
81
+ Cache
82
+ .get("search_index/countries_by_currency.json")
83
+ .to_h do |currency_code, country_codes|
84
+ [
85
+ Money::Currency.new(currency_code),
86
+ country_codes.map(&Data.method(:country_by_code)),
87
+ ]
100
88
  end
101
89
  end
102
90
 
data/lib/atlasq/format.rb CHANGED
@@ -13,7 +13,7 @@ module Atlasq
13
13
  [
14
14
  "*",
15
15
  "* #{title}",
16
- "*#{" *" * ((title.size / 2) + 2)}"
16
+ "*#{" *" * ((title.size / 2) + 2)}",
17
17
  ].join("\n")
18
18
  end
19
19
 
@@ -56,7 +56,7 @@ module Atlasq
56
56
 
57
57
  [
58
58
  Format.title(title),
59
- *elements
59
+ *elements,
60
60
  ].join("\n")
61
61
  end
62
62
 
@@ -83,26 +83,10 @@ module Atlasq
83
83
  [
84
84
  Format.title(title),
85
85
  attributes,
86
- *info_ladder
86
+ *info_ladder,
87
87
  ].join("\n")
88
88
  end
89
89
 
90
- # @param value
91
- # @param search_term [String]
92
- # @return [String]
93
- def self.any(value, search_term)
94
- case value
95
- in ISO3166::Country
96
- Format.country(value, search_term)
97
- in Atlasq::Data::Region
98
- Format.region(value)
99
- in [Atlasq::Data::Currency, *]
100
- Format.currencies(value)
101
- else
102
- raise Error, "Unknown format type: #{value.class}"
103
- end
104
- end
105
-
106
90
  # @param country [ISO3166::Country]
107
91
  # @param search_term [String]
108
92
  # @return [String]
@@ -116,7 +100,7 @@ module Atlasq
116
100
  "Nationality" => country.nationality,
117
101
  "Region" => country.subregion,
118
102
  "Continent" => country.continent,
119
- "Currency" => "#{country.currency.symbol} #{country.currency.name}"
103
+ "Currency" => "#{country.currency.symbol} #{country.currency.name}",
120
104
  }.reject do |_, value|
121
105
  # "countries" like Antarctica can have missing language, nationality,
122
106
  # and region data so we remove that missing data beforehand.
@@ -159,7 +143,7 @@ module Atlasq
159
143
  country.number,
160
144
  country.alpha2,
161
145
  country.alpha3,
162
- country.iso_short_name
146
+ country.iso_short_name,
163
147
  ]
164
148
  when Hash
165
149
  values = country.slice(
@@ -171,7 +155,7 @@ module Atlasq
171
155
 
172
156
  [
173
157
  Data.emoji_flag(country.fetch("number")),
174
- *values
158
+ *values,
175
159
  ]
176
160
  else
177
161
  raise Error, "Unknown country type: #{country.class}"
@@ -180,15 +164,6 @@ module Atlasq
180
164
  end
181
165
  end
182
166
 
183
- # @param region [Atlasq::Data::Region]
184
- # @return [String]
185
- def self.region(region)
186
- type = Util::String.titleize(region.type)
187
- title = "#{type}: #{region.name}"
188
-
189
- Format.countries(region.countries, title: title)
190
- end
191
-
192
167
  # @param subregions [Hash<String, Array<ISO3166::Country>>]
193
168
  # @return [String]
194
169
  def self.subregions(subregions)
@@ -199,24 +174,24 @@ module Atlasq
199
174
  Format.brief_template(title: "All Subregions", elements: subregions)
200
175
  end
201
176
 
202
- # @param currencies [Array<Atlasq::Data::Currencies]
177
+ # @param currencies [Hash<Money::Currency, Array<ISO3166::Country>>]
178
+ # @param partial_match [Boolean] defaults to false
203
179
  # @return [String]
204
- def self.currencies(currencies)
205
- currencies = currencies.to_h do |currency_class|
206
- currency = Money::Currency.new(currency_class.currency_code)
207
-
180
+ def self.currencies(currencies, partial_match: false)
181
+ currencies = currencies.to_h do |currency, countries|
208
182
  [
209
183
  "[#{currency.iso_code}] #{currency.symbol} #{currency.name}",
210
- currency_class.countries.map(&Format.method(:one_line_country))
184
+ countries.map(&Format.method(:one_line_country)),
211
185
  ]
212
186
  end
213
187
 
214
- if currencies.size == 1
188
+ if !partial_match && currencies.size == 1
215
189
  title, elements = currencies.first
216
190
  title = "Currency: #{title}"
217
191
  Format.brief_template(title: title, elements: elements)
218
192
  else
219
- Format.brief_template(title: "Currencies", elements: currencies)
193
+ title = partial_match ? "Currencies (Partial Match)" : "Currencies"
194
+ Format.brief_template(title: title, elements: currencies)
220
195
  end
221
196
  end
222
197
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Atlasq
4
+ module PartialMatch
5
+ # @param term [String]
6
+ # @return [Array<String>] 3 digit country codes (ISO3166)
7
+ def self.countries(term)
8
+ @countries ||= Util::WordMap.new(index: Cache.get("search_index/partial_match_country.json"))
9
+ @countries.search(term)
10
+ end
11
+
12
+ # @param term [String]
13
+ # @return [Array<String>] 3 letter currency codes (ISO4217)
14
+ def self.currencies(term)
15
+ @currencies ||= Util::WordMap.new(index: Cache.get("search_index/partial_match_currency.json"))
16
+ @currencies.search(term)
17
+ end
18
+ end
19
+ end
@@ -1,16 +1,51 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "unaccent"
4
+
3
5
  module Atlasq
4
6
  module Util
5
7
  module String
6
- # @param string [#to_s]
8
+ ABBREVIATIONS = {
9
+ "AMER" => "North, Central and South America",
10
+ "APAC" => "Asia-Pacific",
11
+ "EMEA" => "Europe, Middle East, and Africa",
12
+ }.freeze
13
+
14
+ # @param string [String]
7
15
  # @return [String]
8
16
  def self.titleize(string)
9
- string
10
- .to_s
11
- .split(/[-_ \t]+/)
12
- .map(&:capitalize)
13
- .join(" ")
17
+ abbreviation = string.upcase
18
+
19
+ if (full_name = ABBREVIATIONS[abbreviation])
20
+ "#{full_name} (#{abbreviation})"
21
+ else
22
+ words = string.split(/[-_ \t]+/)
23
+ words.map! do |word|
24
+ if word.match?(/^(?:of|the|and)$/i)
25
+ word.downcase
26
+ else
27
+ word.capitalize
28
+ end
29
+ end
30
+ words.join(" ")
31
+ end
32
+ end
33
+
34
+ # Make string lowercase and remove accents.
35
+ #
36
+ # @param string [String]
37
+ # @return [String]
38
+ def self.normalize(string)
39
+ @normalize ||= {}
40
+ @normalize[string] ||= Unaccent.unaccent(string.downcase)
41
+ end
42
+
43
+ # Split a sentence on words ignoring irrelevant punctuation.
44
+ #
45
+ # @param sentence [String]
46
+ # @return [Array<String>]
47
+ def self.word_split(sentence)
48
+ sentence.split(/[ \t,;:()]+/).reject(&:empty?)
14
49
  end
15
50
  end
16
51
  end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+ require "unaccent"
5
+
6
+ module Atlasq
7
+ module Util
8
+ # This data structure provides partial match support for words in sentences.
9
+ #
10
+ # For example, if we take a country like "Bonaire, Sint Eustatius and Saba"
11
+ # the searches "Bonaire", "Sint Eustatius" and "saba bonaire" should all match
12
+ # while the searches "bonaire france" and "saba bolivia" should not because
13
+ # they include words not found in the original sentence.
14
+ class WordMap
15
+ # @param index [Hash<String, Array<String>>]
16
+ def initialize(index:)
17
+ @index = index
18
+ end
19
+
20
+ # Search for ids that include all of the search term words.
21
+ #
22
+ # @param search_term [String]
23
+ # @return [Array<String>] list of ids
24
+ def search(search_term)
25
+ search_term
26
+ .then { |string| split(string) }
27
+ .map { |word| Util::String.normalize(word) }
28
+ .uniq
29
+ .map { |word| @index.fetch(word, []) }
30
+ .inject(&:intersection)
31
+ .sort
32
+ end
33
+
34
+ private
35
+
36
+ # Split on spaces, tabs and punctuation separators.
37
+ # Note: Some punctuation can be connectors or separators based on the language.
38
+ #
39
+ # @param sentence [String]
40
+ # @return [Array<String>]
41
+ def split(sentence)
42
+ sentence.split(/[ \t,;:()]+/).reject(&:empty?)
43
+ end
44
+ end
45
+ end
46
+ end
data/lib/atlasq/util.rb CHANGED
@@ -3,5 +3,6 @@
3
3
  module Atlasq
4
4
  module Util
5
5
  autoload :String, "atlasq/util/string"
6
+ autoload :WordMap, "atlasq/util/word_map"
6
7
  end
7
8
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Atlasq
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/atlasq.rb CHANGED
@@ -6,9 +6,11 @@ module Atlasq
6
6
  class Error < StandardError; end
7
7
  DEBUG = ENV.key?("ATLASQ_DEBUG")
8
8
 
9
+ autoload :Cache, "atlasq/cache"
9
10
  autoload :Command, "atlasq/command"
10
11
  autoload :Data, "atlasq/data"
11
12
  autoload :Format, "atlasq/format"
13
+ autoload :PartialMatch, "atlasq/partial_match"
12
14
  autoload :Shell, "atlasq/shell"
13
15
  autoload :Util, "atlasq/util"
14
16