atlasq 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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