% \iffalse meta-comment
%
% Copyright (C) 2026 Valentin Dao
% 
% This file may be distributed and/or modified under the
% conditions of the LaTeX Project Public License, either
% version 1.3c of this license or (at your option) any later
% version. The latest version of this license is in:
% 
%   http://www.latex-project.org/lppl.txt
% 
% and version 1.3c or later is part of all distributions of
% LaTeX version 2008-05-04 or later.
% 
% This work has the LPPL maintenance status 'maintained'
% and the current maintainer is Valentin Dao (vdao.texdev@gmail.com).
% 
% This work consists of the files contained in the file 'MANIFEST.md'
% 
% The repository of this work can be found at:
% 
%   https://github.com/TeXackers/query-astro
%
%<*driver>
% \fi
% \iffalse
\documentclass[11pt, a4paper]{l3doc}

\usepackage{fontspec}

\setmainfont{ChronicleTextG3}[
  Extension             = .otf,
  Renderer              = HarfBuzz,
  UprightFont           = *-Roman-Pro,
  UprightFeatures       = { Kerning = On },
  ItalicFont            = *-Italic-Pro,
  BoldFont              = *-Bold-Pro,
  BoldItalicFont        = *-BoldIta-Pro,
]

\setsansfont{Whitney}[
  Extension        = .otf,
  UprightFont      = *-Medium,
  ItalicFont       = *-MediumItalic,
  BoldFont         = *-Bold,
  BoldItalicFont   = *-BoldItalic,
  UprightFeatures  = 
    {
      StylisticSet = 1, 
      StylisticSet = 10, 
      StylisticSet = 11, 
    },
  BoldFeatures     = 
    {
      StylisticSet = 1, 
      StylisticSet = 10, 
      StylisticSet = 11, 
    },
  ItalicFeatures   = 
    {
      StylisticSet = 1, 
      StylisticSet = 10, 
      StylisticSet = 11, 
      StylisticSet = 8, 
      StylisticSet = 9  
    }
]

\setmonofont{MonaspaceNeon}[
  Extension              = .otf,
  Renderer               = HarfBuzz,
  Scale                  = 0.8,
  UprightFont            = *-Regular,
  UprightFeatures        = { Kerning = On },
  ItalicFont             = *-Italic,
  BoldFont               = *-Bold,
  BoldItalicFont         = *-BoldItalic,
]

\usepackage{microtype}

\usepackage{fancyhdr}
\fancyhf{}
\fancyfoot[C]{\small\sffamily\itshape---\space\thepage\space\kern0.4mm\/---}
\renewcommand{\headrulewidth}{0pt}
\pagestyle{fancy}

\usepackage{query-astro}

\usepackage[silence]{codedescribe}
\usepackage{hologo}

\usepackage[svgnames]{xcolor}

\definecolor{RoyalBlue}{RGB}{0, 35, 102}
\definecolor{RoyalRed}{RGB}{157, 16, 45}
\definecolor{RoyalGreen}{HTML}{48845F}
\definecolor{urlcolour}{HTML}{B42F5E}

\definecolor{packagecolour}{HTML}{B86B00}
\definecolor{keycolour}{HTML}{004766}
\definecolor{macrocolour}{HTML}{047658}
\definecolor{macrocolour2}{HTML}{630476}

\defobjectfmt{macro}{code}{color=macrocolour}
\defobjectfmt{pkg}{pkg}{color=packagecolour, font=\sffamily}

\newcodekey{fits}{
  ruleht        = 0,
  texcsstyle    = \bfseries\color{RoyalBlue},
  texcs         =
    [2]{
      loadheader,
      showheader,
      headerkey,
      setheaderkey,
      gsetheaderkey,
    },
  texcs         = 
    [3]{
      flameHalpha,
      exptime,
      observer,
    },
  texcsstyle   = [2]{\color{macrocolour}},
  texcsstyle   = [3]{\color{macrocolour2}},
  codeprefix    = {},
  resultprefix  = {}
}

\lstset{
  gobble=2,
}

\directlua{
  target_list = {}
  local path = '../../build.lua'
  dofile(path)
  token.set_macro('pkgversion', uploadconfig.version)
  token.set_macro('pkgname', uploadconfig.pkg)
  token.set_macro('pkgauthor', uploadconfig.author)
  token.set_macro('pkgemail', uploadconfig.email)
  texio.write_nl(uploadconfig.author)
}

\usepackage{titlesec}
\titleformat*{\section}{\sffamily\LARGE\bfseries}
\titleformat*{\subsection}{\sffamily\Large\bfseries}

\usepackage{changelog}
\usepackage{enumitem}
\renewenvironment{changelogitemize}
  {\begin{itemize}[label=\raisebox{0.5ex}{$\scriptscriptstyle\blacktriangleright$}]}
  {\end{itemize}}

\setlength{\parindent}{0pt}

\usepackage{polyglossia}
\setmainlanguage{english}

\usepackage{siunitx}
\sisetup{
  propagate-math-font     = true,
  retain-explicit-plus    = true,
  uncertainty-mode        = separate,
  round-mode              = places,
  round-precision         = 2,
}

\usepackage[
  backend       = biber,
  casechanger   = expl3,
  sorting       = none,
  url           = true,
  doi           = true,
  maxcitenames  = 1
  ]{biblatex}
\addbibresource{query-astro.bib}

\usepackage[perpage]{footmisc}

\usepackage{hyperref}
\hypersetup{
  pdfauthor   = {Valentin Dao},
  pdftitle    = {The query-astro package},
  pdfcreator  = {LuaLaTeX with hyperref package},
  colorlinks  = true,
  urlcolor    = urlcolour,
  linkcolor   = RoyalBlue,
  citecolor   = RoyalGreen,
}

\title{The \textsf{\pkgname} package\medbreak{\Large\pkgversion}}
\author{\pkgauthor\footnote{E-mail: \href{mailto:vdao.texdev@gmail.com}{\ttfamily\pkgemail}}}
\date{\today}

\begin{document}
  \DocInput{query-astro.dtx}
\end{document}
%</driver>
% \fi
% 
% \begin{documentation}
% 
% \maketitle
% 
% The \tsobj[pkg]{\pkgname} package provides a minimalist interface to mimic Python's \href{https://astroquery.readthedocs.io/en/latest/}{Astroquery} library, using cURL to send ADQL queries to the TAP services of major astronomical databases. It therefore requires \textbf{shell escape} to be enabled. Note that all queries are performed \emph{synchronously}: a resource-intensive query (e.g.\ \texttt{SELECT TOP 100 *} against the Gaia catalogue) may significantly slow down compilation.\footnotemark[1] Simple queries are thus strongly recommended. For now, \tsobj[pkg]{\pkgname} supports two databases (Simbad and Gaia) which should cover most use cases; future versions will allow users to register custom TAP service URLs. As of now, only raw ADQL queries are supported. No higher-level interface is provided, though this is planned for future versions. The package is still in active development, so some rough edges are to be expected. Finally, users are reminded that properly crediting the queried databases remains their own responsibility; this package is merely a convenience wrapper for \LaTeX.
% \footnotetext[1]{Asynchronous queries are not currently supported, as I am unaware of a straightforward way to implement async TAP services in \TeX\dots if it is even possible at all.}
%
% \section{Functioning of the package}
% 
% The package essentially works in three steps:
% 
% \begin{enumerate}
%   \item The ADQL query is written in a auxiliary file named \texttt{query.ADQL}.
%   \item A cURL configuration file named \texttt{curl.cfg} is generated.
%   \item The query is executed by calling cURL with the generated configuration file. The result is then stored in a auxiliary file named \texttt{query-result.txt}.
% \end{enumerate}
% 
% Every time you run a query, these files get overwritten.
% 
% \section{Package macro}
% 
% \begin{codedescribe}[macro]{\ADQLquery}
% \begin{codesyntax}
%   \tsobj[macro]{\ADQLquery}\tsargs[marg]{macro}\tsargs[marg]{ADQL query}\tsargs[oarg]{database}
% \end{codesyntax}
% This macro executes the ADQL request and stores the result in the provided \tsargs[marg]{macro}. The nature of this macro depends on the number of queried keys with \texttt{SELECT}. If only one key was submitted, then the macro will be defined without arguments. If a list of keys was provided, then the individual results may be retrieved by specifying their index as an argument. In both cases, the macro will be expandable. Although \tsobj[macro]{\ADQLquery} can support storing multiple columns, it \emph{does not} support storing multiple rows. Hence, any query that goes along the line of \texttt{SELECT TOP...} will, in theory, work, but only the first row from the text file will be inspected. If you don't mind this limitation, you can safely ignore this warning. The optional argument \tsargs[oarg]{database} specifies the database to query. For now, here is the list of supported databases:
% \begin{describelist*}{option}
%    \describe{simbad}{The Simbad astronomical database.~\cite{simbad}}
%    \describe{gaia}{The Gaia astronomical database.~\cite{GaiaCollaboration2016}~\cite{GaiaDR3_2023}}
% \end{describelist*}
% If it isn't specified, Simbad will always be used. Here are a few examples of use:
% \end{codedescribe}
% 
% \begin{codestore}[simbad-mag]
% \ADQLquery\SimbadVMag{
%   SELECT V
%   FROM allfluxes
%   JOIN ident ON allfluxes.oidref = ident.oidref
%   WHERE ident.id = 'Betelgeuse'
% }
% The V-band magnitude of Betelgeuse is \SimbadVMag.
% \end{codestore}
% 
% \tsdemo*[fits]{simbad-mag}
% 
% \begin{codestore}[simbad-coords]
% \ADQLquery\SimbadCoords{
%   SELECT ROUND(ra, 5), ROUND(dec, 5)
%   FROM basic
%   WHERE main_id = 'M31'
% }
% Right ascension: \SimbadCoords{1}\par
% Declination: \SimbadCoords{2}
% \end{codestore}
% 
% \tsdemo*[fits]{simbad-coords}
% 
% \begin{codestore}[gaia-parallax]
% \DeclareSIUnit\milliarcsecond{mas}
% \ADQLquery\SiriusParallax{
%   SELECT ROUND(parallax, 5), ROUND(parallax_error, 5)
%   FROM gaiadr3.gaia_source_lite
%   WHERE source_id = 2947050466531873024
% }[gaia]
% The parallax of Sirius is $\SiriusParallax{1} \pm \SiriusParallax{2} \unit{\milliarcsecond}$.
% \end{codestore}
% 
% \tsdemo*[fits]{gaia-parallax}
% 
% \begin{changelog}[sectioncmd=\section*]
% \shortversion{v=0.1.0, date=20-04-2026, changes=Initial version.}
% \end{changelog}
% 
% \end{documentation}
% 
% \printbibliography
% 
% \newpage
% 
% \section{Implementation}
% 
% \begin{implementation}
% 
%    \begin{macrocode}
%<*package>
%<@@=query_astro>
\NeedsTeXFormat{LaTeX2e}

\def\queryastro@module{query-astro}
\def\queryastro@version{v0.1.0}
\def\queryastro@date{2026-04-20}
\def\queryastro@description{Querying astronomical databases with ADQL in LaTeX}

\ProvidesExplPackage
  \queryastro@module
  \queryastro@date
  \queryastro@version
  \queryastro@description

\sys_if_shell_unrestricted:F { \PackageError{query-astro}{query-astro~requires~shell~escape} }

\cs_generate_variant:Nn \seq_gset_split:NnV { cnV }

\iow_new:N \g_@@_ADQL_query_iow
\iow_new:N \g_@@_curl_config_iow
\ior_new:N \g_@@_ADQL_result_ior

\tl_new:N \l_@@_TAP_url_tl
\tl_const:Nn \c_@@_simbad_TAP_url_tl { https://simbad.u-strasbg.fr/simbad/sim-tap/sync }
\tl_const:Nn \c_@@_gaia_TAP_url_tl { https://gea.esac.esa.int/tap-server/tap/sync }

\cs_new_protected:Npn \@@_write_curl_configuration:n #1
  {
    \iow_open:Nn \g_@@_curl_config_iow { curl.cfg }
    \iow_now:Ne \g_@@_curl_config_iow { #1 }
    \iow_close:N \g_@@_curl_config_iow
  }

\cs_new_protected:Npn \@@_write_ADQL_query:n #1
  {
    \iow_open:Nn \g_@@_ADQL_query_iow { query.ADQL }
    \iow_now:Ne \g_@@_ADQL_query_iow { #1 }
    \iow_close:N \g_@@_ADQL_query_iow
  }

\cs_new_protected:Npn \@@_execute_ADQL_query:n #1
  {
    \@@_write_ADQL_query:n { #1 }
    \@@_write_curl_configuration:n
      {
        data-urlencode~=~"QUERY@query.ADQL"^^J
        data~=~"REQUEST=doQuery"^^J
        data~=~"LANG=ADQL"^^J
        data~=~"FORMAT=csv"^^J
        url~=~"\l_@@_TAP_url_tl"^^J
        output~=~"query-result.txt"^^J
      }
    \sys_shell_now:n {curl~--config~curl.cfg}
  }

\cs_new_protected:Npn \@@_retrieve_ADQL_result:n #1
  {
    \ior_open:Nn \g_@@_ADQL_result_ior { query-result.txt }
    \ior_get:NN \g_@@_ADQL_result_ior \l_tmpa_tl % ignore header
    \ior_get:NN \g_@@_ADQL_result_ior \l_tmpb_tl
    \seq_new:c { g_@@_ADQL_#1_result_seq }
    \seq_gset_split:cnV { g_@@_ADQL_#1_result_seq } { , } \l_tmpb_tl
    \ior_close:N \g_@@_ADQL_result_ior
    \int_compare:nNnTF { \seq_count:c { g_@@_ADQL_#1_result_seq } } > { 1 }
      { \cs_new:cpn { #1 } ##1 { \seq_item:cn { g_@@_ADQL_#1_result_seq } { ##1 } } }
      { \cs_new:cpn { #1 } { \seq_item:cn { g_@@_ADQL_#1_result_seq } { 1 } } }
  }

\NewDocumentCommand\ADQLquery{ m m O{simbad} }
  {
    \str_case:nnF {#3}
      {
        {simbad} { \tl_set_eq:NN \l_@@_TAP_url_tl \c_@@_simbad_TAP_url_tl }
        {gaia}   { \tl_set_eq:NN \l_@@_TAP_url_tl \c_@@_gaia_TAP_url_tl }
      }
      { \PackageError{query-astro}{Unknown~database:~'#3'}{} }
    \@@_execute_ADQL_query:n {#2}
    \exp_args:Ne \@@_retrieve_ADQL_result:n { \cs_to_str:N #1 }
  }
%</package>
%    \end{macrocode}
% \end{implementation}
