Skip to content

Commit cb31fe7

Browse files
Robust CLI parsing, eclipse checks, and 'next' search
Remove IDE CMakeSettings.json and expand README with detailed output field and API docs. Harden CLI option parsing by treating -h/--help and other --* tokens as missing values (arg_parser.hpp, cli_common.cpp) and adding missing-value helpers (entry.cpp, global_context.cpp). Add current_jd_utc() and missing_opt_value_token() utilities and improve find_opt_val/infer_jd_interval to default search start to current time when appropriate. Strengthen validation for cmd_eclipse (require positive sample, require paired point lat/lon, require grid/global options only with global visibility or geojson, validate positive grid steps, handle point-height/point-refine dependencies). Implement next/search support and helpers (cmd_next_range_search.cpp) to collect, filter and format upcoming events. Misc: small prep_cmd_args --bsp empty check and other related adjustments; tests updated to match behavior changes.
1 parent cfbf3a9 commit cb31fe7

File tree

10 files changed

+952
-141
lines changed

10 files changed

+952
-141
lines changed

CMakeSettings.json

Lines changed: 0 additions & 26 deletions
This file was deleted.

README.md

Lines changed: 320 additions & 0 deletions
Large diffs are not rendered by default.

include/lunar/arg_parser.hpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,12 @@ class ArgParser{
6262
static std::string require_value(const std::vector<std::string>&args,
6363
std::size_t&idx,const std::string&opt){
6464
if(idx+1>=args.size()){
65-
throw std::invalid_argument("missing value for "+opt);
65+
throw std::invalid_argument("missing value for option: "+opt);
6666
}
6767
++idx;
68+
if(args[idx]=="-h"||args[idx]=="--help"||args[idx].rfind("--",0)==0){
69+
throw std::invalid_argument("missing value for option: "+opt);
70+
}
6871
return args[idx];
6972
}
7073

src/cli_common.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ std::string req_val(const std::vector<std::string>&args,std::size_t&idx,
5151
throw std::invalid_argument("missing value for option: "+opt);
5252
}
5353
++idx;
54+
if(args[idx]=="-h"||args[idx]=="--help"||args[idx].rfind("--",0)==0){
55+
throw std::invalid_argument("missing value for option: "+opt);
56+
}
5457
return args[idx];
5558
}
5659

src/entry.cpp

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,18 @@
1414

1515
namespace{
1616

17+
std::string req_global_opt_value(const std::vector<std::string>&args,
18+
std::size_t&i,const std::string&opt){
19+
if(i+1>=args.size()){
20+
throw std::invalid_argument("missing value for option: "+opt);
21+
}
22+
++i;
23+
if(args[i]=="-h"||args[i]=="--help"||args[i].rfind("--",0)==0){
24+
throw std::invalid_argument("missing value for option: "+opt);
25+
}
26+
return args[i];
27+
}
28+
1729
std::vector<std::string> parse_global_opts(const std::vector<std::string>&args,
1830
const std::string&default_lang){
1931
LunarEclipseCalcMethod method=LunarEclipseCalcMethod::Modern;
@@ -27,12 +39,8 @@ std::vector<std::string> parse_global_opts(const std::vector<std::string>&args,
2739
for(std::size_t i=0;i<args.size();++i){
2840
const std::string&arg=args[i];
2941
if(arg=="--eclipse-method"){
30-
if(i+1>=args.size()){
31-
throw std::invalid_argument(
32-
"--eclipse-method requires: modern|legacy");
33-
}
3442
LunarEclipseCalcMethod parsed=LunarEclipseCalcMethod::Modern;
35-
const std::string&value=args[++i];
43+
const std::string&value=req_global_opt_value(args,i,arg);
3644
if(!parse_lunar_eclipse_calc_method(value,&parsed)){
3745
throw std::invalid_argument(
3846
"invalid --eclipse-method: "+value+
@@ -44,6 +52,10 @@ std::vector<std::string> parse_global_opts(const std::vector<std::string>&args,
4452
if(arg.rfind(prefix,0)==0){
4553
LunarEclipseCalcMethod parsed=LunarEclipseCalcMethod::Modern;
4654
std::string value=arg.substr(prefix.size());
55+
if(value.empty()){
56+
throw std::invalid_argument(
57+
"missing value for option: --eclipse-method");
58+
}
4759
if(!parse_lunar_eclipse_calc_method(value,&parsed)){
4860
throw std::invalid_argument(
4961
"invalid --eclipse-method: "+value+
@@ -53,10 +65,7 @@ std::vector<std::string> parse_global_opts(const std::vector<std::string>&args,
5365
continue;
5466
}
5567
if(arg=="--lang"){
56-
if(i+1>=args.size()){
57-
throw std::invalid_argument("--lang requires: zh|zht|en|ja|ko");
58-
}
59-
const std::string&value=args[++i];
68+
const std::string&value=req_global_opt_value(args,i,arg);
6069
if(!lunar::i18n::try_parse_lang(value,&lang)){
6170
throw std::invalid_argument(
6271
"invalid --lang: "+value+" (expected zh|zht|en|ja|ko)");
@@ -65,6 +74,9 @@ std::vector<std::string> parse_global_opts(const std::vector<std::string>&args,
6574
}
6675
if(arg.rfind(lang_prefix,0)==0){
6776
std::string value=arg.substr(lang_prefix.size());
77+
if(value.empty()){
78+
throw std::invalid_argument("missing value for option: --lang");
79+
}
6880
if(!lunar::i18n::try_parse_lang(value,&lang)){
6981
throw std::invalid_argument(
7082
"invalid --lang: "+value+" (expected zh|zht|en|ja|ko)");

src/global_context.cpp

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include<algorithm>
44
#include<cctype>
55
#include<cmath>
6+
#include<ctime>
67
#include<filesystem>
78
#include<limits>
89
#include<optional>
@@ -52,6 +53,10 @@ std::string trim_copy(const std::string&s){
5253
return s.substr(beg,end-beg);
5354
}
5455

56+
bool missing_opt_value_token(const std::string&s){
57+
return s=="-h"||s=="--help"||s.rfind("--",0)==0;
58+
}
59+
5560
bool ends_with_bsp(const std::string&s){
5661
if(s.size()<4){
5762
return false;
@@ -122,6 +127,19 @@ void add_unique_existing(std::vector<std::string>&out,
122127
}
123128
}
124129

130+
double current_jd_utc(){
131+
const std::time_t now=std::time(nullptr);
132+
std::tm utc_tm{};
133+
#if defined(_WIN32)
134+
gmtime_s(&utc_tm,&now);
135+
#else
136+
gmtime_r(&now,&utc_tm);
137+
#endif
138+
return greg2jd(utc_tm.tm_year+1900,utc_tm.tm_mon+1,utc_tm.tm_mday,
139+
utc_tm.tm_hour,utc_tm.tm_min,
140+
static_cast<double>(utc_tm.tm_sec));
141+
}
142+
125143
std::optional<std::pair<int,int>> parse_ym(const std::string&s){
126144
std::size_t pos=s.find('-');
127145
if(pos==std::string::npos){
@@ -161,7 +179,7 @@ std::optional<std::string> find_opt_val(const std::vector<std::string>&args,
161179
for(std::size_t i=0;i<args.size();++i){
162180
const std::string&arg=args[i];
163181
if(arg==opt){
164-
if(i+1<args.size()){
182+
if(i+1<args.size()&&!missing_opt_value_token(args[i+1])){
165183
return args[i+1];
166184
}
167185
return std::nullopt;
@@ -249,14 +267,17 @@ std::optional<std::pair<double,double>> infer_jd_interval(
249267
return std::make_pair(std::min(f.jd_utc,t.jd_utc),
250268
std::max(f.jd_utc,t.jd_utc));
251269
}
252-
if(command=="search"&&from){
253-
IsoTime f=parse_iso(*from,default_tz);
270+
if(command=="search"){
271+
double jd_from=current_jd_utc();
272+
if(from){
273+
jd_from=parse_iso(*from,default_tz).jd_utc;
274+
}
254275
int count=1;
255276
if(auto c=find_opt_val(args,"--count")){
256277
count=std::max(1,std::stoi(*c));
257278
}
258279
double span=std::max(30.0,static_cast<double>(count)*45.0);
259-
return std::make_pair(f.jd_utc,f.jd_utc+span);
280+
return std::make_pair(jd_from,jd_from+span);
260281
}
261282
return std::nullopt;
262283
}
@@ -507,6 +528,9 @@ std::vector<std::string> prep_cmd_args(const std::string&command,
507528
}
508529
if(arg.rfind("--bsp=",0)==0){
509530
explicit_bsp=arg.substr(6);
531+
if(explicit_bsp.empty()){
532+
throw std::invalid_argument("missing value for option: --bsp");
533+
}
510534
continue;
511535
}
512536
stripped.push_back(arg);

src/query/cmd_eclipse.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,16 @@ int cmd_eclipse(const std::vector<std::string>&args){
2828
double point_height_m=0.0;
2929
bool has_point_lat=false;
3030
bool has_point_lon=false;
31+
bool has_point_height=false;
32+
bool point_refine_set=false;
3133

3234
bool global_vis=false;
3335
std::string global_format="json";
3436
double grid_lat_step=10.0;
3537
double grid_lon_step=10.0;
38+
bool global_format_set=false;
39+
bool grid_lat_step_set=false;
40+
bool grid_lon_step_set=false;
3641

3742
const OptMap handlers={
3843
{"--kind",[&](const std::vector<std::string>&src,std::size_t&idx,
@@ -54,6 +59,7 @@ int cmd_eclipse(const std::vector<std::string>&args){
5459
{"--point-refine",[&](const std::vector<std::string>&src,std::size_t&idx,
5560
const std::string&opt){
5661
point_refine=parse_bool01(req_val(src,idx,opt),opt);
62+
point_refine_set=true;
5763
}},
5864
{"--point-lat",[&](const std::vector<std::string>&src,std::size_t&idx,
5965
const std::string&opt){
@@ -68,6 +74,7 @@ int cmd_eclipse(const std::vector<std::string>&args){
6874
{"--point-height",[&](const std::vector<std::string>&src,std::size_t&idx,
6975
const std::string&opt){
7076
point_height_m=parse_double(req_val(src,idx,opt),opt);
77+
has_point_height=true;
7178
}},
7279
{"--global-vis",[&](const std::vector<std::string>&src,std::size_t&idx,
7380
const std::string&opt){
@@ -80,14 +87,17 @@ int cmd_eclipse(const std::vector<std::string>&args){
8087
{"--global-format",[&](const std::vector<std::string>&src,
8188
std::size_t&idx,const std::string&opt){
8289
global_format=to_low(req_val(src,idx,opt));
90+
global_format_set=true;
8391
}},
8492
{"--grid-lat-step",[&](const std::vector<std::string>&src,
8593
std::size_t&idx,const std::string&opt){
8694
grid_lat_step=parse_double(req_val(src,idx,opt),opt);
95+
grid_lat_step_set=true;
8796
}},
8897
{"--grid-lon-step",[&](const std::vector<std::string>&src,
8998
std::size_t&idx,const std::string&opt){
9099
grid_lon_step=parse_double(req_val(src,idx,opt),opt);
100+
grid_lon_step_set=true;
91101
}},
92102
{"--tz",[&](const std::vector<std::string>&src,std::size_t&idx,
93103
const std::string&opt){ tz=req_val(src,idx,opt); }},
@@ -116,17 +126,34 @@ int cmd_eclipse(const std::vector<std::string>&args){
116126
if(kind_opt!="lunar"&&kind_opt!="solar"){
117127
throw std::invalid_argument("--kind must be lunar or solar");
118128
}
129+
if(!(sample_minutes>0.0)){
130+
throw std::invalid_argument("--sample-min must be > 0");
131+
}
119132
if(has_point_lat!=has_point_lon){
120133
throw std::invalid_argument(
121134
"point visibility requires both --point-lat and --point-lon");
122135
}
136+
if((has_point_height||point_refine_set)&&!has_point_lat){
137+
throw std::invalid_argument(
138+
"--point-height/--point-refine require --point-lat and --point-lon");
139+
}
123140
if(global_format!="json"&&global_format!="geojson"){
124141
throw std::invalid_argument("--global-format must be json or geojson");
125142
}
126143
chk_fmt(format,{"json","txt","geojson"},"eclipse");
127144
if(format=="geojson"){
128145
global_vis=true;
129146
}
147+
if((global_format_set||grid_lat_step_set||grid_lon_step_set)&&!global_vis){
148+
throw std::invalid_argument(
149+
"--global-format/--grid-lat-step/--grid-lon-step require --global-vis 1 or --format geojson");
150+
}
151+
if(grid_lat_step_set&&!(grid_lat_step>0.0)){
152+
throw std::invalid_argument("--grid-lat-step must be > 0");
153+
}
154+
if(grid_lon_step_set&&!(grid_lon_step>0.0)){
155+
throw std::invalid_argument("--grid-lon-step must be > 0");
156+
}
130157

131158
int y=0;
132159
int m=0;

0 commit comments

Comments
 (0)