Skip to content

Commit b92a997

Browse files
authored
Add Windows and Output File Support
- Add ability to execute from windows by dragging CSV file onto progress.py script. - Add ability to write output to a file. - Minor cleanup
1 parent e27e84a commit b92a997

File tree

2 files changed

+163
-60
lines changed

2 files changed

+163
-60
lines changed

README.md

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,38 @@
11
# Description
2-
This repository constains a script for generating Scouts BSA progress reports
2+
This repository constains a python script for generating Scouts BSA progress reports
33

44
# Requirements
5-
The script has been tested with python 3.11.
5+
The script has been written for python 3.11 and utilizes the pandas and psutil modules.
6+
7+
Before running the script for the first time, ensure python 3.11 or later is installed on your computer and install the requried modules by typing the following from the command line:
8+
9+
`pip install pandas`
10+
`pip install psutil`
611

712
# Usage
8-
python {--date=[MM/DD/YYYY]} {--id=[scoutid]} {--cubs} [scoutbook.csv]
13+
The script can be launched from the command line or from Windows.
14+
15+
To lanuch from windows drag the desired CSV file and drop it onto the progress.py icon. When the script executes, it will prompt the user for all optional arguments.
16+
17+
To launch from the command line type the following, where {} denotes optional portions and [] denotes portions to be specified by the user.
18+
19+
`python progress.py {--date=[MM/DD/YYYY]} {--id=[scoutid]} {--cubs} [scoutbook.csv]`
920

1021
## Required Arguments
1122
### [scoutbook.csv]
1223
This outlines the path to the CSV file exported from Scoutbook.
1324

25+
1426
## Optional Arguments
27+
### {--out=[outfile.txt]}
28+
Specifies the file name where all output will be written.
29+
If omitted, output will be displayed to the screen.
1530
### --date=[MM/DD/YYYY]
16-
This defines the date of of the last progress report. All progress made since this date will be considered new.
31+
Defines the date of of the last progress report. All progress made since this date will be considered new.
32+
If omitted, a default date of 1/1/1980 wil be used.
1733
### --id=[scoutid]
18-
This is used to tell the script to only generate a progress report for the indicated scout (rather than for all scouts).
34+
This is used to tell the script to only generate a progress report for a specific scout.
35+
If omitted, a progress report will be generated for all scouts listed in the CSV file.
1936
### --cubs
20-
This is used to indicate that the progress report is generated for Cub Scouts. When this flag is set, the script will not display progress towards eagle, and will indicate Cub Scout and Webelos specific advancement and awards.
21-
37+
This is used to indicate that the progress report is generated for Cub Scouts. When this flag is set, the script will not display progress towards eagle, and will indicate Cub Scout and Webelos specific advancement and awards.
38+
If omitted, only Scouts BSA advancement and awards will be displayed.

progress.py

Lines changed: 139 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,36 @@
88
__url__ = "http://github.com/sgreasby/Scout-Progress-Report"
99
__maintainer__ = "Steven Greasby"
1010
###########################################################
11+
import os
1112
import sys
12-
import pandas
13-
import re
1413
import datetime
1514
import builtins
15+
import re
16+
import builtins
1617

17-
warnings=False
18+
# The following modules are required, tell non python users how to get them.
19+
try:
20+
import pandas
21+
except:
22+
builtins.print("type \"pip install pandas\" from the command line then try again")
23+
try:
24+
import psutil
25+
except:
26+
builtins.print("type \"pip install psutil\" from the command line then try again")
27+
28+
# Figure out if user ran from command line or clicked icon
29+
windows= 1 if psutil.Process(os.getpid()).parent().name() == 'py.exe' else 0
1830

31+
# Debugging code
32+
warnings=False
1933
PRINT_COLS=50
20-
21-
pandas.set_option('display.max_rows', None)
34+
# If launched via windows, the window may close before the error can be read
35+
# If this happens, just set windows=1 and run from the command line to see error.
36+
#windows=1
37+
#pandas.set_option('display.max_rows', None)
2238
#pandas.set_option('display.max_columns', None)
2339

40+
2441
eagle_mbs=[['Camping'],
2542
['Citizenship in the Community'],
2643
['Citizenship in the Nation'],
@@ -81,10 +98,25 @@
8198
'Life' :['1','2','3','4','5','6','7','8'],
8299
'Eagle' :['1','2','3','4','5','6','7']}
83100

101+
102+
####################################
103+
# Global Variables
104+
####################################
84105
indent=0
106+
scoutbook=pandas.DataFrame()
107+
cols=pandas.DataFrame()
108+
last_review="1/1/1980"
109+
last_review=pandas.to_datetime([last_review])[0]
110+
specificScout=False
111+
cubs=False
112+
outFile=sys.stdout
113+
114+
####################################
115+
# Functions
116+
####################################
85117
def print(*args, **kwargs):
86-
builtins.print(" "*indent,end="")
87-
return builtins.print(*args, **kwargs)
118+
builtins.print(" "*indent,end="",file=outFile)
119+
return builtins.print(*args, **kwargs,file=outFile)
88120

89121
def warn(*args, **kwargs):
90122
if warnings:
@@ -98,59 +130,108 @@ def error(*args, **kwargs):
98130
return builtins.print(*args, **kwargs)
99131

100132
def usage():
101-
builtins.print("Usage: %s {--date=[MM/DD/YYYY]} {--id=[scoutid]} {--cubs} [scoutbook.csv]\n\n" %(sys.argv[0]))
133+
if windows:
134+
if scoutbook.empty:
135+
builtins.print("Drag CSV file on top of %s icon" %(sys.argv[0]))
136+
input("\nPress any key to continue...")
137+
else:
138+
builtins.print("Usage: %s {--out=[outfile.txt]} {--date=[MM/DD/YYYY]} {--id=[scoutid]} {--cubs} [scoutbook.csv]\n\n" %(sys.argv[0]))
102139
sys.exit()
103140

104-
# Parse Arguments
105-
csv_open=False
106-
# Use pandas to convert last_review to datetime object
107-
last_review="1/1/1980"
108-
last_review=pandas.to_datetime([last_review])[0]
109-
specificScout=False
110-
cubs=False
111-
if len(sys.argv)<2:
112-
usage()
113-
for arg in sys.argv[1:]:
114-
if arg.startswith('--date='):
115-
junk,last_review = arg.split('=')
116-
try:
117-
# Use pandas to convert last_review to datetime object
118-
last_review=pandas.to_datetime([last_review])[0]
119-
except:
120-
builtins.print("%s does not appear to be a valid date."%last_review)
121-
usage()
122-
elif arg.startswith('--id='):
123-
junk,specificScout = arg.split('=')
124-
try:
125-
specificScout=int(specificScout)
126-
except:
127-
builtins.print("%s does not appear to be a valid scout ID"%specificScout)
128-
usage()
129-
elif arg == '--cubs':
130-
cubs=True
131-
elif not arg.startswith('--'):
132-
builtins.print("Opening %s" %(arg))
133-
134-
try:
135-
# First row of CSV contains column names.
136-
# However some rows have an undocumnted column
137-
# Add dummy column to avoid errors when reading in data
138-
cols = pandas.read_csv(arg, nrows=1,header=None).values.flatten().tolist()+['Undocumented']
139-
except:
140-
builtins.print("Failed to read %s." %(arg))
141-
usage()
142-
else:
143-
csv_open=True
144-
141+
def csv_open(csv_file):
142+
col_names=pandas.DataFrame()
143+
data=pandas.DataFrame()
144+
builtins.print("Opening %s" %(csv_file))
145+
try:
146+
# First row of CSV contains column names.
147+
# However some rows have an undocumnted column
148+
# Add dummy column to avoid errors when reading in data
149+
col_names = pandas.read_csv(csv_file, nrows=1,header=None).values.flatten().tolist()+['Undocumented']
150+
except:
151+
builtins.print("Failed to read %s." %(csv_file))
152+
usage()
153+
else:
145154
# read the rest of the CSV file
146155
# Skip first row (col names) and use names determined above
147-
scoutbook = pandas.read_csv(arg, skiprows=1,names=cols)
148-
else:
156+
data = pandas.read_csv(csv_file, skiprows=1,names=col_names)
157+
return data,col_names
158+
159+
160+
def convert_date(date):
161+
try:
162+
# Use pandas to convert date to datetime object
163+
date=pandas.to_datetime([date])[0]
164+
except:
165+
error("%s does not appear to be a valid date."%date)
149166
usage()
167+
return date
150168

151-
if not csv_open:
169+
def id_check(scoutID):
170+
try:
171+
scoutID=int(scoutID)
172+
except:
173+
error("%s does not appear to be a valid scout ID"%scoutID)
152174
usage()
175+
return scoutID
176+
177+
def out_check(name):
178+
handle=None
179+
if os.path.exists(name):
180+
error("%s already exists"%name)
181+
usage()
182+
try:
183+
handle=open(name,'a')
184+
except:
185+
error("Unable to open %s for writing."%name)
186+
usage()
187+
return handle
188+
153189

190+
if len(sys.argv)<2:
191+
usage()
192+
193+
if windows:
194+
scoutbook,cols=csv_open(sys.argv[1]);
195+
196+
response=input("Enter date of last progress report or leave blank for none (MM/DD/YYYY): ")
197+
if response:
198+
last_review=convert_date(response)
199+
200+
response=input("Enter ID of a specfic scout or leave blank to run for all scouts: ")
201+
if response:
202+
specificScout=id_check(response)
203+
204+
response='unknown'
205+
while not response in ['y','Y','n','N','']:
206+
response=input("Run script for Cub Scouts? y/N: ")
207+
208+
if response in ['y','Y']:
209+
cubs=True
210+
211+
response=input("Enter ouput file or leave blank to print to screen: ")
212+
if response:
213+
outFile=out_check(response)
214+
215+
else:
216+
for arg in sys.argv[1:]:
217+
if arg.startswith('--out='):
218+
junk,value = arg.split('=')
219+
outFile=out_check(value)
220+
elif arg.startswith('--date='):
221+
junk,value = arg.split('=')
222+
last_review=convert_date(value)
223+
elif arg.startswith('--id='):
224+
junk,value = arg.split('=')
225+
specificScout=id_check(value)
226+
elif arg == '--cubs':
227+
cubs=True
228+
elif not arg.startswith('--'):
229+
scoutbook,cols=csv_open(arg);
230+
else:
231+
usage()
232+
233+
if scoutbook.empty:
234+
usage()
154235

155236
#######################################
156237
# Clean up imported data before using it
@@ -211,6 +292,9 @@ def usage():
211292
rankup_date = approved_ranks['Date Completed'].max()
212293
rank = approved_ranks['Advancement'].loc[approved_ranks['Date Completed']==rankup_date].to_string(index=False)
213294

295+
if outFile != sys.stdout:
296+
builtins.print("Processing %s, %s."%(last_name, first_name))
297+
214298
indent=0
215299
print("%s, %s: %s (%s)" %(last_name,first_name,rank,str(rankup_date.date())))
216300

@@ -486,7 +570,7 @@ def usage():
486570
else:
487571
all_reqs = bsa_rank_reqs[rank[:-17]]
488572
except:
489-
builtins.print("Warning: %s rank not yet supported"%rank[:-17])
573+
warn("%s rank not yet supported"%rank[:-17])
490574
s=set(new_reqs)
491575
reqs_remaining=[req for req in all_reqs if req not in s]
492576
s=set(prev_reqs)
@@ -666,3 +750,5 @@ def usage():
666750
builtins.print("Scout %d not found" % specificScout)
667751
usage()
668752

753+
if windows:
754+
input("\nPress any key to continue...")

0 commit comments

Comments
 (0)