From 50ea018252ce69542eab6a107b99ea8179810d1e Mon Sep 17 00:00:00 2001 From: Martin Fischer Date: Fri, 11 Apr 2025 16:33:59 +0200 Subject: refactor: introduce lex-serve package --- .gitignore | 1 - assets/logo.svg | 1 - assets/script.js | 56 --------- assets/style.css | 40 ------- countries.json | 218 ----------------------------------- lex-serve/assets/logo.svg | 1 + lex-serve/assets/script.js | 56 +++++++++ lex-serve/assets/style.css | 40 +++++++ lex-serve/countries.json | 218 +++++++++++++++++++++++++++++++++++ lex-serve/main.go | 144 +++++++++++++++++++++++ lex-serve/main_test.go | 144 +++++++++++++++++++++++ lex-serve/templates/index.html.tmpl | 17 +++ lex-serve/templates/search.html.tmpl | 30 +++++ lexsurf.go | 144 ----------------------- lexsurf_test.go | 144 ----------------------- templates/index.html.tmpl | 17 --- templates/search.html.tmpl | 30 ----- 17 files changed, 650 insertions(+), 651 deletions(-) delete mode 100644 .gitignore delete mode 100644 assets/logo.svg delete mode 100644 assets/script.js delete mode 100644 assets/style.css delete mode 100644 countries.json create mode 100644 lex-serve/assets/logo.svg create mode 100644 lex-serve/assets/script.js create mode 100644 lex-serve/assets/style.css create mode 100644 lex-serve/countries.json create mode 100644 lex-serve/main.go create mode 100644 lex-serve/main_test.go create mode 100644 lex-serve/templates/index.html.tmpl create mode 100644 lex-serve/templates/search.html.tmpl delete mode 100644 lexsurf.go delete mode 100644 lexsurf_test.go delete mode 100644 templates/index.html.tmpl delete mode 100644 templates/search.html.tmpl diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 2718737..0000000 --- a/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/lexsurf diff --git a/assets/logo.svg b/assets/logo.svg deleted file mode 100644 index 45de364..0000000 --- a/assets/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/script.js b/assets/script.js deleted file mode 100644 index 95b8393..0000000 --- a/assets/script.js +++ /dev/null @@ -1,56 +0,0 @@ -const searchInput = document.getElementById('search'); -const suggestionsDiv = document.getElementById('suggestions'); - -async function enableAutocomplete() { - const res = await fetch('/laws.json'); - const laws = await res.json(); - // TODO: strip accents before searching - - searchInput.addEventListener('input', (e) => { - if (searchInput.value == '') { - suggestionsDiv.innerHTML = ''; - return; - } - - const titleRegex = new RegExp(searchInput.value, 'i'); - const abbrRegex = new RegExp('^' + searchInput.value, 'i'); - const suggestions = []; - - laws.map(l => { - const titleMatch = titleRegex.exec(l.title); - const abbrMatch = abbrRegex.exec(l.abbr); - - return { - law: l, - titleScore: titleMatch ? titleMatch.index : Number.MAX_VALUE, - abbrScore: abbrMatch ? abbrMatch.index: Number.MAX_VALUE, - } - }) - .filter(l => l.titleScore < Number.MAX_VALUE || l.abbrScore < Number.MAX_VALUE) - .sort((a,b) => { - let abbrDiff = a.abbrScore - b.abbrScore; - if (a.law.abbr && b.law.abbr && abbrDiff == 0 && a.abbrScore != Number.MAX_VALUE) { - abbrDiff = a.law.abbr.length - b.law.abbr.length; - } - return abbrDiff || a.titleScore - b.titleScore; - }) - .slice(0, 30) - .forEach(x => { - const l = x.law; - const a = document.createElement('a'); - if (l.redir) - a.href = '/' + l.redir; - else - a.href = l.url; - a.textContent = l.title; - const li = document.createElement('li'); - li.appendChild(a); - suggestions.push(li); - }); - - suggestionsDiv.replaceChildren(...suggestions); - }); -} - -if ('json' in searchInput.dataset) - enableAutocomplete(); \ No newline at end of file diff --git a/assets/style.css b/assets/style.css deleted file mode 100644 index 3b064c2..0000000 --- a/assets/style.css +++ /dev/null @@ -1,40 +0,0 @@ -body { - font-family: Roboto, Helvetica, Arial, sans-serif; - max-width: 500px; - margin: 0.5em auto; - text-align: center; -} - -.countries { - text-align: left; - border: 1px solid #ccc; - border-radius: 5px; - - display: grid; - grid-template-columns: repeat(auto-fit, minmax(50px, 1fr)); -} - -.cc-link { - display: inline-block; - line-height: 50px; - text-align: center; - color: inherit; - text-decoration: navajowhite; - font-size: 1.2em; - border: 2px solid transparent; - box-sizing: border-box; -} - -.cc-link:hover { - background: #e6f3ff; - border-radius: 5px; -} - -h2 { - font-weight: normal; - margin-top: 0.5em; -} - -#suggestions { - text-align: left; -} \ No newline at end of file diff --git a/countries.json b/countries.json deleted file mode 100644 index d7f8cc1..0000000 --- a/countries.json +++ /dev/null @@ -1,218 +0,0 @@ -{ - "al": { - "name": "Albania", - "search_url": "https://qbz.gov.al/search;q=%s" - }, - "am": { - "name": "Armenia", - "search_url": "http://www.parliament.am/search.php?where=laws&what=%s" - }, - "ar": { - "name": "Argentina", - "search_url": "https://www.boletinoficial.gob.ar/busquedaAvanzada/primera" - }, - "at": { - "name": "Austria", - "search_url": "https://www.ris.bka.gv.at/Ergebnis.wxe?Abfrage=Gesamtabfrage&Suchworte=%s" - }, - "au": { - "name": "Australia", - "search_url": "https://www.legislation.gov.au/Search/%s" - }, - "az": { - "name": "Azerbaijan", - "search_url": "https://meclis.gov.az/axtar-qanun.php?cat=72&soz=%s" - }, - "be": { - "name": "Belgium", - "search_url": "https://www.ejustice.just.fgov.be/cgi_loi/loi_rech.pl?liste.x=&liste.y=&language=nl&text1=%s" - }, - "bg": { - "name": "Bulgaria", - "search_url": "https://dv.parliament.bg/DVWeb/broeveList.faces" - }, - "br": { - "name": "Brazil", - "search_url": "https://legislacao.presidencia.gov.br/" - }, - "by": { - "name": "Belarus", - "search_url": "https://etalonline.by/kodeksy/" - }, - "ca": { - "name": "Canada", - "search_url": "https://laws-lois.justice.gc.ca/Search/Search.aspx?txtS3archA11=%s" - }, - "ch": { - "name": "Switzerland", - "search_url": "https://www.fedlex.admin.ch/de/search?text=%s" - }, - "cy": { - "name": "Cyprus", - "search_url": "http://www.cylaw.org/cgi-bin/sinocgi.pl?directories=9&query=%s" - }, - "cz": { - "name": "Czech Republic", - "search_url": "https://aplikace.mvcr.cz/sbirka-zakonu/SearchResult.aspx?typeLaw=zakon&what=Text_v_anotaci&q=%s" - }, - "de": { - "name": "Germany", - "search_url": "https://www.gesetze-im-internet.de/cgi-bin/htsearch?config=Titel_bmjhome2005&method=and&words=%s" - }, - "dk": { - "name": "Denmark", - "search_url": "https://www.retsinformation.dk/documents?t=%s" - }, - "ee": { - "name": "Estonia", - "search_url": "https://www.riigiteataja.ee/otsingu_tulemus.html?otsisona=%s" - }, - "es": { - "name": "Spain", - "search_url": "https://www.boe.es/buscar/legislacion.php?campo%5B0%5D=ID_SRC&dato%5B0%5D=&operador%5B0%5D=and&campo%5B1%5D=NOVIGENTE&operador%5B1%5D=and&campo%5B3%5D=CONSO&operador%5B3%5D=and&campo%5B2%5D=TIT&dato%5B2%5D=%s&checkbox_solo_tit=S&operador%5B2%5D=and&page_hits=50&sort_field%5B0%5D=PESO&sort_order%5B0%5D=desc&sort_field%5B1%5D=ref&sort_order%5B1%5D=asc&accion=Buscar" - }, - "fi": { - "name": "Finland", - "search_url": "https://finlex.fi/fi/laki/haku/?search[type]=pika&search[pika]=%s" - }, - "fr": { - "name": "France", - "search_url": "https://www.legifrance.gouv.fr/search/all?tab_selection=all&searchField=ALL&query=%s" - }, - "gr": { - "name": "Greece", - "search_url": "http://www.et.gr/index.php/nomoi-proedrika-diatagmata" - }, - "hr": { - "name": "Croatia", - "search_url": "https://sredisnjikatalogrh.gov.hr/cadial/search.php?action=search&query=%s" - }, - "hu": { - "name": "Hungary", - "search_url": "https://njt.hu/" - }, - "ie": { - "name": "Ireland", - "search_url": "http://www.irishstatutebook.ie/eli/ResultsTitle.html?q=%s" - }, - "in": { - "name": "India", - "search_url": "https://www.indiacode.nic.in/handle/123456789/1362/simple-search?query=%s" - }, - "is": { - "name": "Iceland", - "search_url": "https://www.stjornartidindi.is/" - }, - "it": { - "name": "Italy", - "search_url": "https://www.normattiva.it/ricerca/veloce/0?testoRicerca=%s" - }, - "jp": { - "name": "Japan", - "search_url": "https://elaws.e-gov.go.jp/result?allDocument=0&searchTargetAll=1&searchTextBox=%s&serchSelect=and&searchTarget_array=Constitution%2CAct%2CCabinetOrder%2CImperialOrder%2CMinisterialOrdinance%2CRule&classification_array=%E6%86%B2%E6%B3%95%2C%E5%88%91%E4%BA%8B%2C%E8%B2%A1%E5%8B%99%E9%80%9A%E5%89%87%2C%E6%B0%B4%E7%94%A3%E6%A5%AD%2C%E8%A6%B3%E5%85%89%2C%E5%9B%BD%E4%BC%9A%2C%E8%AD%A6%E5%AF%9F%2C%E5%9B%BD%E6%9C%89%E8%B2%A1%E7%94%A3%2C%E9%89%B1%E6%A5%AD%2C%E9%83%B5%E5%8B%99%2C%E8%A1%8C%E6%94%BF%E7%B5%84%E7%B9%94%2C%E6%B6%88%E9%98%B2%2C%E5%9B%BD%E7%A8%8E%2C%E5%B7%A5%E6%A5%AD%2C%E9%9B%BB%E6%B0%97%E9%80%9A%E4%BF%A1%2C%E5%9B%BD%E5%AE%B6%E5%85%AC%E5%8B%99%E5%93%A1%2C%E5%9B%BD%E5%9C%9F%E9%96%8B%E7%99%BA%2C%E4%BA%8B%E6%A5%AD%2C%E5%95%86%E6%A5%AD%2C%E5%8A%B4%E5%83%8D%2C%E8%A1%8C%E6%94%BF%E6%89%8B%E7%B6%9A%2C%E5%9C%9F%E5%9C%B0%2C%E5%9B%BD%E5%82%B5%2C%E9%87%91%E8%9E%8D%E3%83%BB%E4%BF%9D%E9%99%BA%2C%E7%92%B0%E5%A2%83%E4%BF%9D%E5%85%A8%2C%E7%B5%B1%E8%A8%88%2C%E9%83%BD%E5%B8%82%E8%A8%88%E7%94%BB%2C%E6%95%99%E8%82%B2%2C%E5%A4%96%E5%9B%BD%E7%82%BA%E6%9B%BF%E3%83%BB%E8%B2%BF%E6%98%93%2C%E5%8E%9A%E7%94%9F%2C%E5%9C%B0%E6%96%B9%E8%87%AA%E6%B2%BB%2C%E9%81%93%E8%B7%AF%2C%E6%96%87%E5%8C%96%2C%E9%99%B8%E9%81%8B%2C%E7%A4%BE%E4%BC%9A%E7%A6%8F%E7%A5%89%2C%E5%9C%B0%E6%96%B9%E8%B2%A1%E6%94%BF%2C%E6%B2%B3%E5%B7%9D%2C%E7%94%A3%E6%A5%AD%E9%80%9A%E5%89%87%2C%E6%B5%B7%E9%81%8B%2C%E7%A4%BE%E4%BC%9A%E4%BF%9D%E9%99%BA%2C%E5%8F%B8%E6%B3%95%2C%E7%81%BD%E5%AE%B3%E5%AF%BE%E7%AD%96%2C%E8%BE%B2%E6%A5%AD%2C%E8%88%AA%E7%A9%BA%2C%E9%98%B2%E8%A1%9B%2C%E6%B0%91%E4%BA%8B%2C%E5%BB%BA%E7%AF%89%E3%83%BB%E4%BD%8F%E5%AE%85%2C%E6%9E%97%E6%A5%AD%2C%E8%B2%A8%E7%89%A9%E9%81%8B%E9%80%81%2C%E5%A4%96%E4%BA%8B&lawNo1=&lawNo2=&lawNo3=&lawNo4=&dayPromulgation1_0=&dayPromulgation1_1=&dayPromulgation1_2=&dayPromulgation1_3=&dayPromulgation2_0=&dayPromulgation2_1=&dayPromulgation2_2=&dayPromulgation2_3=&dayPromulgation1=&dayPromulgation2=&lawDayFrom1=0&selectMenu=0&searchBtn=1" - }, - "lt": { - "name": "Lithuania", - "search_url": "https://e-seimas.lrs.lt/portal/simpleSearch/lt" - }, - "lu": { - "name": "Luxembourg", - "search_url": "http://legilux.public.lu/search/?fulltext=%s" - }, - "lv": { - "name": "Latvia", - "search_url": "https://www.vestnesis.lv/rezultati/atbilstiba/on/locijums/on/lapa/1/skaits/20/kartot/datums/ta/on/izdeveji/saeima.likumi/teksts/%s" - }, - "ma": { - "name": "Malta", - "search_url": "https://legislation.mt/Legislation" - }, - "mk": { - "name": "Macedonia", - "search_url": "https://vlada.mk/search/node/%s%20language%3Amk%2Cund" - }, - "mx": { - "name": "Mexico", - "search_url": "http://www.diputados.gob.mx/LeyesBiblio/index.htm" - }, - "nl": { - "name": "Netherlands", - "search_url": "https://wetten.overheid.nl/zoeken/zoekresultaat/rs/2,3,4/titel/%s/titelf/1/tekstf/1/artnrb/0/d//dx/0" - }, - "no": { - "name": "Norway", - "search_url": "https://lovdata.no/sok?q=%s" - }, - "nz": { - "name": "New Zealand", - "search_url": "https://www.legislation.govt.nz/all/results.aspx?search=ts_act%40bill%40regulation%40deemedreg_%s_resel_25_a" - }, - "pe": { - "name": "Peru", - "search_url": "https://leyes.congreso.gob.pe/LeyNume_1p.aspx?xEstado=2&xTipoBusqueda=3&xTexto=%s" - }, - "ph": { - "name": "Philippines", - "search_url": "https://www.officialgazette.gov.ph/the-philippine-legal-codes/" - }, - "pk": { - "name": "Pakistan", - "search_url": "http://www.punjablaws.gov.pk/index6.html" - }, - "pl": { - "name": "Poland", - "search_url": "http://isap.sejm.gov.pl/isap.nsf/search.xsp?status=O&title=%s" - }, - "pt": { - "name": "Portugal", - "search_url": "https://dre.pt/web/guest/pesquisa/-/search/basic?q=%s" - }, - "py": { - "name": "Paraguay", - "search_url": "http://digesto.senado.gov.py/buscar/buscar?buscar=%s" - }, - "ro": { - "name": "Romania", - "search_url": "http://legislatie.just.ro/Public/RezultateCautare?titlu=%s" - }, - "rs": { - "name": "Serbia", - "search_url": "https://www.pravno-informacioni-sistem.rs/reg-search?q=%s" - }, - "ru": { - "name": "Russia", - "search_url": "http://pravo.gov.ru/proxy/ips/?searchres=&bpas=cd00000&sort=-1&intelsearch=%s" - }, - "se": { - "name": "Sweden", - "search_url": "https://www.riksdagen.se/sv/global/sok/?doktyp=sfs&q=%s" - }, - "si": { - "name": "Slovenia", - "search_url": "http://www.pisrs.si/Pis.web/indexSearch?search=%s" - }, - "sk": { - "name": "Slovakia", - "search_url": "https://www.slov-lex.sk/vyhladavanie-pravnych-predpisov?text=%s" - }, - "tk": { - "name": "Turkey", - "search_url": "https://www.mevzuat.gov.tr/aramasonuc?AranacakMetin=%s" - }, - "ua": { - "name": "Ukraine", - "search_url": "https://zakon.rada.gov.ua/laws/main?find=2&dat=00000000&lang=en&user=a&text=%s&textl=2&bool=and&org=0&typ=1&datl=0&yer=0000&mon=00&day=00&numl=2&num=&minjustl=2&minjust=" - }, - "uk": { - "name": "the United Kingdom", - "search_url": "https://www.legislation.gov.uk/primary+secondary?title=%s" - }, - "us": { - "name": "the United States", - "search_url": "https://www.loc.gov/search/?fa=original-format:legislation&q=%s" - }, - "uy": { - "name": "Uruguay", - "search_url": "https://parlamento.gub.uy/documentosyleyes/leyes?Searchtext=%s" - } -} diff --git a/lex-serve/assets/logo.svg b/lex-serve/assets/logo.svg new file mode 100644 index 0000000..45de364 --- /dev/null +++ b/lex-serve/assets/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lex-serve/assets/script.js b/lex-serve/assets/script.js new file mode 100644 index 0000000..95b8393 --- /dev/null +++ b/lex-serve/assets/script.js @@ -0,0 +1,56 @@ +const searchInput = document.getElementById('search'); +const suggestionsDiv = document.getElementById('suggestions'); + +async function enableAutocomplete() { + const res = await fetch('/laws.json'); + const laws = await res.json(); + // TODO: strip accents before searching + + searchInput.addEventListener('input', (e) => { + if (searchInput.value == '') { + suggestionsDiv.innerHTML = ''; + return; + } + + const titleRegex = new RegExp(searchInput.value, 'i'); + const abbrRegex = new RegExp('^' + searchInput.value, 'i'); + const suggestions = []; + + laws.map(l => { + const titleMatch = titleRegex.exec(l.title); + const abbrMatch = abbrRegex.exec(l.abbr); + + return { + law: l, + titleScore: titleMatch ? titleMatch.index : Number.MAX_VALUE, + abbrScore: abbrMatch ? abbrMatch.index: Number.MAX_VALUE, + } + }) + .filter(l => l.titleScore < Number.MAX_VALUE || l.abbrScore < Number.MAX_VALUE) + .sort((a,b) => { + let abbrDiff = a.abbrScore - b.abbrScore; + if (a.law.abbr && b.law.abbr && abbrDiff == 0 && a.abbrScore != Number.MAX_VALUE) { + abbrDiff = a.law.abbr.length - b.law.abbr.length; + } + return abbrDiff || a.titleScore - b.titleScore; + }) + .slice(0, 30) + .forEach(x => { + const l = x.law; + const a = document.createElement('a'); + if (l.redir) + a.href = '/' + l.redir; + else + a.href = l.url; + a.textContent = l.title; + const li = document.createElement('li'); + li.appendChild(a); + suggestions.push(li); + }); + + suggestionsDiv.replaceChildren(...suggestions); + }); +} + +if ('json' in searchInput.dataset) + enableAutocomplete(); \ No newline at end of file diff --git a/lex-serve/assets/style.css b/lex-serve/assets/style.css new file mode 100644 index 0000000..3b064c2 --- /dev/null +++ b/lex-serve/assets/style.css @@ -0,0 +1,40 @@ +body { + font-family: Roboto, Helvetica, Arial, sans-serif; + max-width: 500px; + margin: 0.5em auto; + text-align: center; +} + +.countries { + text-align: left; + border: 1px solid #ccc; + border-radius: 5px; + + display: grid; + grid-template-columns: repeat(auto-fit, minmax(50px, 1fr)); +} + +.cc-link { + display: inline-block; + line-height: 50px; + text-align: center; + color: inherit; + text-decoration: navajowhite; + font-size: 1.2em; + border: 2px solid transparent; + box-sizing: border-box; +} + +.cc-link:hover { + background: #e6f3ff; + border-radius: 5px; +} + +h2 { + font-weight: normal; + margin-top: 0.5em; +} + +#suggestions { + text-align: left; +} \ No newline at end of file diff --git a/lex-serve/countries.json b/lex-serve/countries.json new file mode 100644 index 0000000..d7f8cc1 --- /dev/null +++ b/lex-serve/countries.json @@ -0,0 +1,218 @@ +{ + "al": { + "name": "Albania", + "search_url": "https://qbz.gov.al/search;q=%s" + }, + "am": { + "name": "Armenia", + "search_url": "http://www.parliament.am/search.php?where=laws&what=%s" + }, + "ar": { + "name": "Argentina", + "search_url": "https://www.boletinoficial.gob.ar/busquedaAvanzada/primera" + }, + "at": { + "name": "Austria", + "search_url": "https://www.ris.bka.gv.at/Ergebnis.wxe?Abfrage=Gesamtabfrage&Suchworte=%s" + }, + "au": { + "name": "Australia", + "search_url": "https://www.legislation.gov.au/Search/%s" + }, + "az": { + "name": "Azerbaijan", + "search_url": "https://meclis.gov.az/axtar-qanun.php?cat=72&soz=%s" + }, + "be": { + "name": "Belgium", + "search_url": "https://www.ejustice.just.fgov.be/cgi_loi/loi_rech.pl?liste.x=&liste.y=&language=nl&text1=%s" + }, + "bg": { + "name": "Bulgaria", + "search_url": "https://dv.parliament.bg/DVWeb/broeveList.faces" + }, + "br": { + "name": "Brazil", + "search_url": "https://legislacao.presidencia.gov.br/" + }, + "by": { + "name": "Belarus", + "search_url": "https://etalonline.by/kodeksy/" + }, + "ca": { + "name": "Canada", + "search_url": "https://laws-lois.justice.gc.ca/Search/Search.aspx?txtS3archA11=%s" + }, + "ch": { + "name": "Switzerland", + "search_url": "https://www.fedlex.admin.ch/de/search?text=%s" + }, + "cy": { + "name": "Cyprus", + "search_url": "http://www.cylaw.org/cgi-bin/sinocgi.pl?directories=9&query=%s" + }, + "cz": { + "name": "Czech Republic", + "search_url": "https://aplikace.mvcr.cz/sbirka-zakonu/SearchResult.aspx?typeLaw=zakon&what=Text_v_anotaci&q=%s" + }, + "de": { + "name": "Germany", + "search_url": "https://www.gesetze-im-internet.de/cgi-bin/htsearch?config=Titel_bmjhome2005&method=and&words=%s" + }, + "dk": { + "name": "Denmark", + "search_url": "https://www.retsinformation.dk/documents?t=%s" + }, + "ee": { + "name": "Estonia", + "search_url": "https://www.riigiteataja.ee/otsingu_tulemus.html?otsisona=%s" + }, + "es": { + "name": "Spain", + "search_url": "https://www.boe.es/buscar/legislacion.php?campo%5B0%5D=ID_SRC&dato%5B0%5D=&operador%5B0%5D=and&campo%5B1%5D=NOVIGENTE&operador%5B1%5D=and&campo%5B3%5D=CONSO&operador%5B3%5D=and&campo%5B2%5D=TIT&dato%5B2%5D=%s&checkbox_solo_tit=S&operador%5B2%5D=and&page_hits=50&sort_field%5B0%5D=PESO&sort_order%5B0%5D=desc&sort_field%5B1%5D=ref&sort_order%5B1%5D=asc&accion=Buscar" + }, + "fi": { + "name": "Finland", + "search_url": "https://finlex.fi/fi/laki/haku/?search[type]=pika&search[pika]=%s" + }, + "fr": { + "name": "France", + "search_url": "https://www.legifrance.gouv.fr/search/all?tab_selection=all&searchField=ALL&query=%s" + }, + "gr": { + "name": "Greece", + "search_url": "http://www.et.gr/index.php/nomoi-proedrika-diatagmata" + }, + "hr": { + "name": "Croatia", + "search_url": "https://sredisnjikatalogrh.gov.hr/cadial/search.php?action=search&query=%s" + }, + "hu": { + "name": "Hungary", + "search_url": "https://njt.hu/" + }, + "ie": { + "name": "Ireland", + "search_url": "http://www.irishstatutebook.ie/eli/ResultsTitle.html?q=%s" + }, + "in": { + "name": "India", + "search_url": "https://www.indiacode.nic.in/handle/123456789/1362/simple-search?query=%s" + }, + "is": { + "name": "Iceland", + "search_url": "https://www.stjornartidindi.is/" + }, + "it": { + "name": "Italy", + "search_url": "https://www.normattiva.it/ricerca/veloce/0?testoRicerca=%s" + }, + "jp": { + "name": "Japan", + "search_url": "https://elaws.e-gov.go.jp/result?allDocument=0&searchTargetAll=1&searchTextBox=%s&serchSelect=and&searchTarget_array=Constitution%2CAct%2CCabinetOrder%2CImperialOrder%2CMinisterialOrdinance%2CRule&classification_array=%E6%86%B2%E6%B3%95%2C%E5%88%91%E4%BA%8B%2C%E8%B2%A1%E5%8B%99%E9%80%9A%E5%89%87%2C%E6%B0%B4%E7%94%A3%E6%A5%AD%2C%E8%A6%B3%E5%85%89%2C%E5%9B%BD%E4%BC%9A%2C%E8%AD%A6%E5%AF%9F%2C%E5%9B%BD%E6%9C%89%E8%B2%A1%E7%94%A3%2C%E9%89%B1%E6%A5%AD%2C%E9%83%B5%E5%8B%99%2C%E8%A1%8C%E6%94%BF%E7%B5%84%E7%B9%94%2C%E6%B6%88%E9%98%B2%2C%E5%9B%BD%E7%A8%8E%2C%E5%B7%A5%E6%A5%AD%2C%E9%9B%BB%E6%B0%97%E9%80%9A%E4%BF%A1%2C%E5%9B%BD%E5%AE%B6%E5%85%AC%E5%8B%99%E5%93%A1%2C%E5%9B%BD%E5%9C%9F%E9%96%8B%E7%99%BA%2C%E4%BA%8B%E6%A5%AD%2C%E5%95%86%E6%A5%AD%2C%E5%8A%B4%E5%83%8D%2C%E8%A1%8C%E6%94%BF%E6%89%8B%E7%B6%9A%2C%E5%9C%9F%E5%9C%B0%2C%E5%9B%BD%E5%82%B5%2C%E9%87%91%E8%9E%8D%E3%83%BB%E4%BF%9D%E9%99%BA%2C%E7%92%B0%E5%A2%83%E4%BF%9D%E5%85%A8%2C%E7%B5%B1%E8%A8%88%2C%E9%83%BD%E5%B8%82%E8%A8%88%E7%94%BB%2C%E6%95%99%E8%82%B2%2C%E5%A4%96%E5%9B%BD%E7%82%BA%E6%9B%BF%E3%83%BB%E8%B2%BF%E6%98%93%2C%E5%8E%9A%E7%94%9F%2C%E5%9C%B0%E6%96%B9%E8%87%AA%E6%B2%BB%2C%E9%81%93%E8%B7%AF%2C%E6%96%87%E5%8C%96%2C%E9%99%B8%E9%81%8B%2C%E7%A4%BE%E4%BC%9A%E7%A6%8F%E7%A5%89%2C%E5%9C%B0%E6%96%B9%E8%B2%A1%E6%94%BF%2C%E6%B2%B3%E5%B7%9D%2C%E7%94%A3%E6%A5%AD%E9%80%9A%E5%89%87%2C%E6%B5%B7%E9%81%8B%2C%E7%A4%BE%E4%BC%9A%E4%BF%9D%E9%99%BA%2C%E5%8F%B8%E6%B3%95%2C%E7%81%BD%E5%AE%B3%E5%AF%BE%E7%AD%96%2C%E8%BE%B2%E6%A5%AD%2C%E8%88%AA%E7%A9%BA%2C%E9%98%B2%E8%A1%9B%2C%E6%B0%91%E4%BA%8B%2C%E5%BB%BA%E7%AF%89%E3%83%BB%E4%BD%8F%E5%AE%85%2C%E6%9E%97%E6%A5%AD%2C%E8%B2%A8%E7%89%A9%E9%81%8B%E9%80%81%2C%E5%A4%96%E4%BA%8B&lawNo1=&lawNo2=&lawNo3=&lawNo4=&dayPromulgation1_0=&dayPromulgation1_1=&dayPromulgation1_2=&dayPromulgation1_3=&dayPromulgation2_0=&dayPromulgation2_1=&dayPromulgation2_2=&dayPromulgation2_3=&dayPromulgation1=&dayPromulgation2=&lawDayFrom1=0&selectMenu=0&searchBtn=1" + }, + "lt": { + "name": "Lithuania", + "search_url": "https://e-seimas.lrs.lt/portal/simpleSearch/lt" + }, + "lu": { + "name": "Luxembourg", + "search_url": "http://legilux.public.lu/search/?fulltext=%s" + }, + "lv": { + "name": "Latvia", + "search_url": "https://www.vestnesis.lv/rezultati/atbilstiba/on/locijums/on/lapa/1/skaits/20/kartot/datums/ta/on/izdeveji/saeima.likumi/teksts/%s" + }, + "ma": { + "name": "Malta", + "search_url": "https://legislation.mt/Legislation" + }, + "mk": { + "name": "Macedonia", + "search_url": "https://vlada.mk/search/node/%s%20language%3Amk%2Cund" + }, + "mx": { + "name": "Mexico", + "search_url": "http://www.diputados.gob.mx/LeyesBiblio/index.htm" + }, + "nl": { + "name": "Netherlands", + "search_url": "https://wetten.overheid.nl/zoeken/zoekresultaat/rs/2,3,4/titel/%s/titelf/1/tekstf/1/artnrb/0/d//dx/0" + }, + "no": { + "name": "Norway", + "search_url": "https://lovdata.no/sok?q=%s" + }, + "nz": { + "name": "New Zealand", + "search_url": "https://www.legislation.govt.nz/all/results.aspx?search=ts_act%40bill%40regulation%40deemedreg_%s_resel_25_a" + }, + "pe": { + "name": "Peru", + "search_url": "https://leyes.congreso.gob.pe/LeyNume_1p.aspx?xEstado=2&xTipoBusqueda=3&xTexto=%s" + }, + "ph": { + "name": "Philippines", + "search_url": "https://www.officialgazette.gov.ph/the-philippine-legal-codes/" + }, + "pk": { + "name": "Pakistan", + "search_url": "http://www.punjablaws.gov.pk/index6.html" + }, + "pl": { + "name": "Poland", + "search_url": "http://isap.sejm.gov.pl/isap.nsf/search.xsp?status=O&title=%s" + }, + "pt": { + "name": "Portugal", + "search_url": "https://dre.pt/web/guest/pesquisa/-/search/basic?q=%s" + }, + "py": { + "name": "Paraguay", + "search_url": "http://digesto.senado.gov.py/buscar/buscar?buscar=%s" + }, + "ro": { + "name": "Romania", + "search_url": "http://legislatie.just.ro/Public/RezultateCautare?titlu=%s" + }, + "rs": { + "name": "Serbia", + "search_url": "https://www.pravno-informacioni-sistem.rs/reg-search?q=%s" + }, + "ru": { + "name": "Russia", + "search_url": "http://pravo.gov.ru/proxy/ips/?searchres=&bpas=cd00000&sort=-1&intelsearch=%s" + }, + "se": { + "name": "Sweden", + "search_url": "https://www.riksdagen.se/sv/global/sok/?doktyp=sfs&q=%s" + }, + "si": { + "name": "Slovenia", + "search_url": "http://www.pisrs.si/Pis.web/indexSearch?search=%s" + }, + "sk": { + "name": "Slovakia", + "search_url": "https://www.slov-lex.sk/vyhladavanie-pravnych-predpisov?text=%s" + }, + "tk": { + "name": "Turkey", + "search_url": "https://www.mevzuat.gov.tr/aramasonuc?AranacakMetin=%s" + }, + "ua": { + "name": "Ukraine", + "search_url": "https://zakon.rada.gov.ua/laws/main?find=2&dat=00000000&lang=en&user=a&text=%s&textl=2&bool=and&org=0&typ=1&datl=0&yer=0000&mon=00&day=00&numl=2&num=&minjustl=2&minjust=" + }, + "uk": { + "name": "the United Kingdom", + "search_url": "https://www.legislation.gov.uk/primary+secondary?title=%s" + }, + "us": { + "name": "the United States", + "search_url": "https://www.loc.gov/search/?fa=original-format:legislation&q=%s" + }, + "uy": { + "name": "Uruguay", + "search_url": "https://parlamento.gub.uy/documentosyleyes/leyes?Searchtext=%s" + } +} diff --git a/lex-serve/main.go b/lex-serve/main.go new file mode 100644 index 0000000..d0fb690 --- /dev/null +++ b/lex-serve/main.go @@ -0,0 +1,144 @@ +package main + +import ( + "embed" + "encoding/json" + "io/ioutil" + "log" + "net/http" + "net/url" + "os" + "strings" + "text/template" +) + +//go:embed countries.json +var countriesJSON []byte + +func main() { + domain := os.Getenv("DOMAIN") + if domain == "" { + log.Fatal("DOMAIN environment variable must be set") + } + + var handler = handler{domain: domain, lawsByCC: map[string]map[string]law{}} + err := json.Unmarshal(countriesJSON, &handler.countries) + if err != nil { + log.Fatal("countries.json ", err) + } + + lawFiles, err := ioutil.ReadDir("laws") + if err != nil { + log.Fatal(err) + } + for _, file := range lawFiles { + text, err := ioutil.ReadFile("laws/" + file.Name()) + if err != nil { + log.Fatal(file.Name(), err) + } + var laws []law + err = json.Unmarshal([]byte(text), &laws) + if err != nil { + log.Fatal(file.Name(), err) + } + cc := strings.SplitN(file.Name(), ".", 2)[0] + handler.lawsByCC[cc] = map[string]law{} + for _, law := range laws { + if law.Redir != "" { + handler.lawsByCC[cc][law.Redir] = law + } + } + } + http.HandleFunc("/", handler.handle) + println("listening on 8000") + log.Fatal(http.ListenAndServe(":8000", nil)) +} + +//go:embed templates +var templates embed.FS + +var tpl, _ = template.New("").Funcs(template.FuncMap{ + "ToUpper": strings.ToUpper, +}).ParseFS(templates, "templates/*") + +type handler struct { + domain string + countries map[string]country + lawsByCC map[string]map[string]law +} + +func (h *handler) handle(w http.ResponseWriter, r *http.Request) { + if r.Host == h.domain { + if r.URL.Path != "/" { + w.WriteHeader(http.StatusNotFound) + w.Write([]byte("page not found")) + return + } + err := tpl.ExecuteTemplate(w, "index.html.tmpl", map[string]any{ + "Countries": h.countries, + "Domain": r.Host, + }) + if err != nil { + log.Fatal(err) + } + return + } + + cc, isSubdomain := strings.CutSuffix(r.Host, "."+h.domain) + if !isSubdomain { + w.Write([]byte("unknown host")) + return + } + key := strings.TrimLeft(r.URL.Path, "/") + if len(key) > 0 { + val, ok := h.lawsByCC[cc][key] + if !ok { + w.WriteHeader(http.StatusNotFound) + w.Write([]byte("unknown law")) + return + } + http.Redirect(w, r, val.URL, 302) + } else { + query := r.URL.Query().Get("q") + if query != "" { + country, ok := h.countries[cc] + if !ok { + w.WriteHeader(http.StatusNotFound) + w.Write([]byte("search not implemented for this country")) + return + } + if country.HasPlaceholder() { + http.Redirect(w, r, strings.Replace(country.SearchURL, "%s", url.QueryEscape(query), 1), 302) + return + } else { + w.WriteHeader(http.StatusBadRequest) + } + } + _, hasJSONLaws := h.lawsByCC[cc] + err := tpl.ExecuteTemplate(w, "search.html.tmpl", map[string]any{ + "TLD": cc, + "Domain": h.domain, + "Country": h.countries[cc], + "HasJSONLaws": hasJSONLaws, + }) + if err != nil { + log.Fatal(err) + } + } +} + +type law struct { + URL string + Title string + Abbr string + Redir string +} + +type country struct { + Name string + SearchURL string `json:"search_url"` +} + +func (c country) HasPlaceholder() bool { + return strings.Contains(c.SearchURL, "%s") +} diff --git a/lex-serve/main_test.go b/lex-serve/main_test.go new file mode 100644 index 0000000..e647a2f --- /dev/null +++ b/lex-serve/main_test.go @@ -0,0 +1,144 @@ +package main + +import ( + "net/http/httptest" + "strings" + "testing" + + "github.com/peter-evans/patience" +) + +func newHandler() handler { + return handler{ + domain: "lex.example", + countries: map[string]country{ + "zu": country{ + Name: "Zubrowka", + SearchURL: "https://lex.gov.zu/?search=%s", + }, + }, + lawsByCC: map[string]map[string]law{}, + } +} + +func TestStartPage(t *testing.T) { + h := newHandler() + + req := httptest.NewRequest("GET", "/", nil) + req.Host = "lex.example" + w := httptest.NewRecorder() + h.handle(w, req) + + resp := w.Result() + if resp.StatusCode != 200 { + t.Errorf("expected status 200 OK, got %d", resp.StatusCode) + } + expected := ` + + + Lex.surf: Portal to National Law + + + + + +

The portal to national law.

+
+ ZU +
+ + +` + assertEqual(t, w.Body.String(), expected) +} + +func TestSearch(t *testing.T) { + h := newHandler() + + req := httptest.NewRequest("GET", "/", nil) + req.Host = "zu.lex.example" + w := httptest.NewRecorder() + h.handle(w, req) + + resp := w.Result() + if resp.StatusCode != 200 { + t.Errorf("expected status 200 OK, got %d", resp.StatusCode) + } + expected := ` + + + National Law of Zubrowka + + + + + +

National Law of Zubrowka

+ + +
+ + + + + + +
+ + + + +` + assertEqual(t, w.Body.String(), expected) +} + +func TestSearchRedirect(t *testing.T) { + h := newHandler() + + req := httptest.NewRequest("GET", "/?q=tourism", nil) + req.Host = "zu.lex.example" + w := httptest.NewRecorder() + h.handle(w, req) + + resp := w.Result() + if resp.StatusCode != 302 { + t.Errorf("expected status 302, got %d", resp.StatusCode) + } + + if resp.Header.Get("Location") != "https://lex.gov.zu/?search=tourism" { + t.Errorf("wrong location, got %s", resp.Header.Get("Location")) + } +} + +func TestLawRedirect(t *testing.T) { + h := newHandler() + + h.lawsByCC["zu"] = map[string]law{} + h.lawsByCC["zu"]["zepl"] = law{ + URL: "https://lex.gov.zu/zeppelin-code", + } + + req := httptest.NewRequest("GET", "/zepl", nil) + req.Host = "zu.lex.example" + w := httptest.NewRecorder() + h.handle(w, req) + + resp := w.Result() + if resp.StatusCode != 302 { + t.Errorf("expected status 302, got %d", resp.StatusCode) + } + + if resp.Header.Get("Location") != "https://lex.gov.zu/zeppelin-code" { + t.Errorf("wrong location, got %s", resp.Header.Get("Location")) + } +} + +func assertEqual(t *testing.T, received string, expected string) { + if received != expected { + a := strings.Split(received, "\n") + b := strings.Split(expected, "\n") + diffs := patience.Diff(a, b) + unidiff := patience.UnifiedDiffText(diffs) + t.Error(unidiff) + } +} diff --git a/lex-serve/templates/index.html.tmpl b/lex-serve/templates/index.html.tmpl new file mode 100644 index 0000000..3d8c04f --- /dev/null +++ b/lex-serve/templates/index.html.tmpl @@ -0,0 +1,17 @@ + + + + Lex.surf: Portal to National Law + + + + + +

The portal to national law.

+
+ {{range $key, $c := .Countries -}} + {{$key | ToUpper}} + {{- end}} +
+ + diff --git a/lex-serve/templates/search.html.tmpl b/lex-serve/templates/search.html.tmpl new file mode 100644 index 0000000..70c8c86 --- /dev/null +++ b/lex-serve/templates/search.html.tmpl @@ -0,0 +1,30 @@ + + + + National Law of {{.Country.Name}} + + + + + +

National Law of {{.Country.Name}}

+ {{if not .Country.HasPlaceholder}} +

{{.Country.SearchURL}}

+ + {{if not .HasJSONLaws}} + (No search form here because the search isn't linkable.) + {{end}} + {{end}} + {{if .Country.HasPlaceholder}} +
+ {{end}} + {{if or .Country.HasPlaceholder .HasJSONLaws}} + + + {{end}} + {{if .Country.HasPlaceholder}} +
+ {{end}} + + + diff --git a/lexsurf.go b/lexsurf.go deleted file mode 100644 index d0fb690..0000000 --- a/lexsurf.go +++ /dev/null @@ -1,144 +0,0 @@ -package main - -import ( - "embed" - "encoding/json" - "io/ioutil" - "log" - "net/http" - "net/url" - "os" - "strings" - "text/template" -) - -//go:embed countries.json -var countriesJSON []byte - -func main() { - domain := os.Getenv("DOMAIN") - if domain == "" { - log.Fatal("DOMAIN environment variable must be set") - } - - var handler = handler{domain: domain, lawsByCC: map[string]map[string]law{}} - err := json.Unmarshal(countriesJSON, &handler.countries) - if err != nil { - log.Fatal("countries.json ", err) - } - - lawFiles, err := ioutil.ReadDir("laws") - if err != nil { - log.Fatal(err) - } - for _, file := range lawFiles { - text, err := ioutil.ReadFile("laws/" + file.Name()) - if err != nil { - log.Fatal(file.Name(), err) - } - var laws []law - err = json.Unmarshal([]byte(text), &laws) - if err != nil { - log.Fatal(file.Name(), err) - } - cc := strings.SplitN(file.Name(), ".", 2)[0] - handler.lawsByCC[cc] = map[string]law{} - for _, law := range laws { - if law.Redir != "" { - handler.lawsByCC[cc][law.Redir] = law - } - } - } - http.HandleFunc("/", handler.handle) - println("listening on 8000") - log.Fatal(http.ListenAndServe(":8000", nil)) -} - -//go:embed templates -var templates embed.FS - -var tpl, _ = template.New("").Funcs(template.FuncMap{ - "ToUpper": strings.ToUpper, -}).ParseFS(templates, "templates/*") - -type handler struct { - domain string - countries map[string]country - lawsByCC map[string]map[string]law -} - -func (h *handler) handle(w http.ResponseWriter, r *http.Request) { - if r.Host == h.domain { - if r.URL.Path != "/" { - w.WriteHeader(http.StatusNotFound) - w.Write([]byte("page not found")) - return - } - err := tpl.ExecuteTemplate(w, "index.html.tmpl", map[string]any{ - "Countries": h.countries, - "Domain": r.Host, - }) - if err != nil { - log.Fatal(err) - } - return - } - - cc, isSubdomain := strings.CutSuffix(r.Host, "."+h.domain) - if !isSubdomain { - w.Write([]byte("unknown host")) - return - } - key := strings.TrimLeft(r.URL.Path, "/") - if len(key) > 0 { - val, ok := h.lawsByCC[cc][key] - if !ok { - w.WriteHeader(http.StatusNotFound) - w.Write([]byte("unknown law")) - return - } - http.Redirect(w, r, val.URL, 302) - } else { - query := r.URL.Query().Get("q") - if query != "" { - country, ok := h.countries[cc] - if !ok { - w.WriteHeader(http.StatusNotFound) - w.Write([]byte("search not implemented for this country")) - return - } - if country.HasPlaceholder() { - http.Redirect(w, r, strings.Replace(country.SearchURL, "%s", url.QueryEscape(query), 1), 302) - return - } else { - w.WriteHeader(http.StatusBadRequest) - } - } - _, hasJSONLaws := h.lawsByCC[cc] - err := tpl.ExecuteTemplate(w, "search.html.tmpl", map[string]any{ - "TLD": cc, - "Domain": h.domain, - "Country": h.countries[cc], - "HasJSONLaws": hasJSONLaws, - }) - if err != nil { - log.Fatal(err) - } - } -} - -type law struct { - URL string - Title string - Abbr string - Redir string -} - -type country struct { - Name string - SearchURL string `json:"search_url"` -} - -func (c country) HasPlaceholder() bool { - return strings.Contains(c.SearchURL, "%s") -} diff --git a/lexsurf_test.go b/lexsurf_test.go deleted file mode 100644 index e647a2f..0000000 --- a/lexsurf_test.go +++ /dev/null @@ -1,144 +0,0 @@ -package main - -import ( - "net/http/httptest" - "strings" - "testing" - - "github.com/peter-evans/patience" -) - -func newHandler() handler { - return handler{ - domain: "lex.example", - countries: map[string]country{ - "zu": country{ - Name: "Zubrowka", - SearchURL: "https://lex.gov.zu/?search=%s", - }, - }, - lawsByCC: map[string]map[string]law{}, - } -} - -func TestStartPage(t *testing.T) { - h := newHandler() - - req := httptest.NewRequest("GET", "/", nil) - req.Host = "lex.example" - w := httptest.NewRecorder() - h.handle(w, req) - - resp := w.Result() - if resp.StatusCode != 200 { - t.Errorf("expected status 200 OK, got %d", resp.StatusCode) - } - expected := ` - - - Lex.surf: Portal to National Law - - - - - -

The portal to national law.

-
- ZU -
- - -` - assertEqual(t, w.Body.String(), expected) -} - -func TestSearch(t *testing.T) { - h := newHandler() - - req := httptest.NewRequest("GET", "/", nil) - req.Host = "zu.lex.example" - w := httptest.NewRecorder() - h.handle(w, req) - - resp := w.Result() - if resp.StatusCode != 200 { - t.Errorf("expected status 200 OK, got %d", resp.StatusCode) - } - expected := ` - - - National Law of Zubrowka - - - - - -

National Law of Zubrowka

- - -
- - - - - - -
- - - - -` - assertEqual(t, w.Body.String(), expected) -} - -func TestSearchRedirect(t *testing.T) { - h := newHandler() - - req := httptest.NewRequest("GET", "/?q=tourism", nil) - req.Host = "zu.lex.example" - w := httptest.NewRecorder() - h.handle(w, req) - - resp := w.Result() - if resp.StatusCode != 302 { - t.Errorf("expected status 302, got %d", resp.StatusCode) - } - - if resp.Header.Get("Location") != "https://lex.gov.zu/?search=tourism" { - t.Errorf("wrong location, got %s", resp.Header.Get("Location")) - } -} - -func TestLawRedirect(t *testing.T) { - h := newHandler() - - h.lawsByCC["zu"] = map[string]law{} - h.lawsByCC["zu"]["zepl"] = law{ - URL: "https://lex.gov.zu/zeppelin-code", - } - - req := httptest.NewRequest("GET", "/zepl", nil) - req.Host = "zu.lex.example" - w := httptest.NewRecorder() - h.handle(w, req) - - resp := w.Result() - if resp.StatusCode != 302 { - t.Errorf("expected status 302, got %d", resp.StatusCode) - } - - if resp.Header.Get("Location") != "https://lex.gov.zu/zeppelin-code" { - t.Errorf("wrong location, got %s", resp.Header.Get("Location")) - } -} - -func assertEqual(t *testing.T, received string, expected string) { - if received != expected { - a := strings.Split(received, "\n") - b := strings.Split(expected, "\n") - diffs := patience.Diff(a, b) - unidiff := patience.UnifiedDiffText(diffs) - t.Error(unidiff) - } -} diff --git a/templates/index.html.tmpl b/templates/index.html.tmpl deleted file mode 100644 index 3d8c04f..0000000 --- a/templates/index.html.tmpl +++ /dev/null @@ -1,17 +0,0 @@ - - - - Lex.surf: Portal to National Law - - - - - -

The portal to national law.

-
- {{range $key, $c := .Countries -}} - {{$key | ToUpper}} - {{- end}} -
- - diff --git a/templates/search.html.tmpl b/templates/search.html.tmpl deleted file mode 100644 index 70c8c86..0000000 --- a/templates/search.html.tmpl +++ /dev/null @@ -1,30 +0,0 @@ - - - - National Law of {{.Country.Name}} - - - - - -

National Law of {{.Country.Name}}

- {{if not .Country.HasPlaceholder}} -

{{.Country.SearchURL}}

- - {{if not .HasJSONLaws}} - (No search form here because the search isn't linkable.) - {{end}} - {{end}} - {{if .Country.HasPlaceholder}} -
- {{end}} - {{if or .Country.HasPlaceholder .HasJSONLaws}} - - - {{end}} - {{if .Country.HasPlaceholder}} -
- {{end}} - - - -- cgit v1.2.3