summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.mdown43
-rw-r--r--scripts/helpers.sh19
-rwxr-xr-xvgstash164
3 files changed, 144 insertions, 82 deletions
diff --git a/README.mdown b/README.mdown
index 9425866..ace50e6 100644
--- a/README.mdown
+++ b/README.mdown
@@ -150,29 +150,44 @@ about after you've added your massive 500 game collection? Nobody wants to sort
through screenfulls of text. That's where filtering comes in. The `list` command
accepts the following filters:
-* **completed**
- List games that have been completed.
+* **arcade**
+ Games you own that cannot be beaten.
+
+* **backlog**
+ Games you own that have not been beaten or completed.
+
+* **borrowing**
+ Games you don't own and are playing.
+
+* **complete**
+ Games that have been completed.
+
+* **digital**
+ Games you own digitally.
* **done**
-List games that have been beaten *or* completed.
+ Games that have been beaten *or* completed.
-* **owned**
-List games that you own.
+* **incomplete**
+ Games you own that have been beaten, but not completed.
-* **unowned**
-List games that you don't own.
+* **new**
+ Games you own and haven't played yet.
-* **wishlist**
-List games that you don't own AND are fresh.
+* **owned**
+ Games you own.
+
+* **physical**
+ Games you own physically.
* **playlog**
-List games that are marked 'in-progress', that you also own.
+ Games you own that are marked 'playing'.
-* **incomplete**
-List games that have been beaten, but not completed, that you also own.
+* **unowned**
+ Games you don't own.
-* **backlog**
-List games that have not been beaten or completed, that you also own.
+* **wishlist**
+ Games you don't own *and* haven't played yet.
## Update
diff --git a/scripts/helpers.sh b/scripts/helpers.sh
index 174305f..3b2d335 100644
--- a/scripts/helpers.sh
+++ b/scripts/helpers.sh
@@ -3,14 +3,6 @@
# This is a set of helper bash functions that are too small for their own file,
# but useful enough to be worth `source`ing in your bashrc.
-# Reports how many physical games you own
-function vgphys() {
- # Note: This assumes the system has an asterisk, "VC", or "Steam" in its name
- # Change this to reflect how you differentiate physical and digital games.
- # TODO: Add 'digital' ownership option to obsolete the regex
- vgstash list owned | cut -d '|' -f 1-3,5 | grep -viE '\*| VC|Steam' | wc -l
-}
-
# Faster general searching
function vgsrc() {
case $# in
@@ -21,7 +13,10 @@ function vgsrc() {
vgstash list "$1" | grep -iE "$2"
;;
*)
- echo "Dumbass, search for something."
+ echo "usage: vgsrc [game name]"
+ echo " or vgsrc [view name] [game name]"
+ echo
+ echo "Ex: vgsrc physical Borderlands"
;;
esac
}
@@ -30,9 +25,3 @@ function vgsrc() {
function vgadd() {
vgstash add "$@"
}
-
-# Quick way to update a game
-# TODO: Put this in a better place
-function vgup() {
- "$HOME"/projects/vgstash/scripts/updater.sh "$@"
-}
diff --git a/vgstash b/vgstash
index 54101f1..365140f 100755
--- a/vgstash
+++ b/vgstash
@@ -2,6 +2,7 @@
# TODO: Consider putting all help messages into a dict for easier management
# TODO: Decide on docstring delimiter and stick to it
+# TODO: refactor
import sys
import os
@@ -10,6 +11,8 @@ import argparse
import yaml
import subprocess # to handle the piping use case
+__version__ = '0.2'
+
DB_LOCATION = ''
OWNERSHIP = 1
PROGRESS = 1
@@ -29,15 +32,15 @@ def set_env():
# This makes outside-scope referencing and assignment possible
global DB_LOCATION, OWNERSHIP, PROGRESS, TABLE_WIDTH
- # Precedence = $VGSTASH_DB_LOCATION, $HOME/.vgstash.db
- DB_LOCATION = os.getenv('VGSTASH_DB_LOCATION', os.path.join(os.getenv('HOME'), '.vgstash.db'))
- print(DB_LOCATION)
+ # Precedence = $VGSTASH_DB_LOCATION, $HOME/.vgstash.db, ./.vgstash.db
+ DB_LOCATION = os.getenv('VGSTASH_DB_LOCATION', os.path.join(os.getenv('HOME', '.'), '.vgstash.db'))
- try:
- assert(os.path.isfile(DB_LOCATION) and os.path.exists(DB_LOCATION))
- except AssertionError:
- print("VGSTASH_DB_LOCATION is not a file. Unset it to fall back to $HOME/.vgstash.db, or correct the environment variable.")
- sys.exit()
+ # Can't decide what to do with this yet; the first run of vgstash doesn't let you init the db if we uncomment this
+ # try:
+ # assert(os.path.isfile(DB_LOCATION) and os.path.exists(DB_LOCATION))
+ # except AssertionError:
+ # print("VGSTASH_DB_LOCATION is not a file. Unset it to fall back to $HOME/.vgstash.db, or correct the environment variable.")
+ # sys.exit()
OWNERSHIP = int(os.getenv('VGSTASH_DEFAULT_OWNERSHIP', OWNERSHIP))
try:
@@ -73,7 +76,7 @@ def init_db(args):
print("The database could not be created and/or connected to.")
sys.exit()
except AssertionError:
- print("The database already exists! Delete '%s' and init the database again." % DB_LOCATION)
+ print("The database already exists! Delete or move '%s' and init the database again." % DB_LOCATION)
sys.exit()
# Now let's run a bunch of queries! Fun...
@@ -87,15 +90,19 @@ def init_db(args):
print("Table created.")
# TODO: Consider executescript()
conn.execute("CREATE VIEW allgames AS SELECT rowid,* FROM games ORDER BY system,title ASC;")
+ conn.execute("CREATE VIEW arcade AS SELECT rowid,* FROM games WHERE ownership >= 1 AND progress = -1 ORDER BY system,title ASC;")
+ conn.execute("CREATE VIEW backlog AS SELECT rowid,* FROM games WHERE ownership >= 1 AND progress < 2 ORDER BY system,title ASC;")
+ conn.execute("CREATE VIEW borrowing AS SELECT rowid,* FROM games WHERE ownership = 0 AND progress = 1 ORDER BY system,title ASC;")
conn.execute("CREATE VIEW complete AS SELECT rowid,* FROM games WHERE progress = 3 ORDER BY system,title ASC;")
- conn.execute("CREATE VIEW backlog AS SELECT rowid,* FROM games WHERE ownership = 1 AND progress < 2 ORDER BY system,title ASC;")
+ conn.execute("CREATE VIEW digital AS SELECT rowid,* FROM games WHERE ownership >= 2 ORDER BY system,title ASC;")
conn.execute("CREATE VIEW done AS SELECT rowid,* FROM games WHERE progress >= 2 ORDER BY system,title ASC;")
- conn.execute("CREATE VIEW fresh AS SELECT rowid,* FROM games WHERE progress = 0 ORDER BY system,title ASC;")
- conn.execute("CREATE VIEW incomplete AS SELECT rowid,* FROM games WHERE ownership = 1 AND progress = 2 ORDER BY system,title ASC;")
- conn.execute("CREATE VIEW owned AS SELECT rowid,* FROM games WHERE ownership = 1 ORDER BY system,title ASC;")
+ conn.execute("CREATE VIEW incomplete AS SELECT rowid,* FROM games WHERE ownership >= 1 AND progress = 2 ORDER BY system,title ASC;")
+ conn.execute("CREATE VIEW new AS SELECT rowid,* FROM games WHERE progress = 0 ORDER BY system,title ASC;")
+ conn.execute("CREATE VIEW owned AS SELECT rowid,* FROM games WHERE ownership >= 1 ORDER BY system,title ASC;")
+ conn.execute("CREATE VIEW physical AS SELECT rowid,* FROM games WHERE ownership = 1 OR ownership = 3 ORDER BY system,title ASC;")
+ conn.execute("CREATE VIEW playlog AS SELECT rowid,* FROM games WHERE ownership >= 1 AND progress = 1 ORDER BY system,title ASC;")
conn.execute("CREATE VIEW unowned AS SELECT rowid,* FROM games WHERE ownership = 0 ORDER BY system,title ASC;")
- conn.execute("CREATE VIEW wishlist AS SELECT rowid,* FROM games WHERE ownership = 0 AND progress < 2 ORDER BY system,title ASC;")
- conn.execute("CREATE VIEW playlog AS SELECT rowid,* FROM games WHERE ownership = 1 AND progress = 1 ORDER BY system,title ASC;")
+ conn.execute("CREATE VIEW wishlist AS SELECT rowid,* FROM games WHERE ownership = 0 AND progress = 0 ORDER BY system,title ASC;")
print("Views created.")
conn.commit()
except sqlite3.OperationalError as e:
@@ -117,7 +124,7 @@ def export_db(args):
conn = sqlite3.connect(DB_LOCATION)
conn.row_factory = sqlite3.Row
c = conn.cursor()
- fp.write("# vgstash DB file version 0.1\n")
+ fp.write("# vgstash DB file version 0.2\n")
db_tree = []
# The .format() call supports the optional -i flag
for row in c.execute("SELECT {}title,system,ownership,progress FROM games".format("rowid," if args.ids else '')):
@@ -153,14 +160,14 @@ def import_db(args):
conn.close()
print("Imported {} games.".format(c.rowcount))
-
def add_game(args):
'''Adds a game to the database. Requires at least a title and
system.
- Ownership can be 0 (not owned) or 1 (owned). The default is 1.
+ Ownership can be 0 to 3:(not owned) or 1 (owned). The default is 1.
- Completion can be 0 to 3:
+ Completion can be -1 to 3:
+ -1 (unbeatable)
0 (fresh, never played)
1 (in-progress) (default)
2 (beaten)
@@ -171,19 +178,28 @@ def add_game(args):
if args.progress == '-':
args.progress = PROGRESS
# Translate our args so they can be added and reflected correctly
- args.ownership = translate_arg(args.ownership)
- args.progress = translate_arg(args.progress)
+ args.ownership = translate_ownership(args.ownership)
+ args.progress = translate_progress(args.progress)
conn = sqlite3.connect(DB_LOCATION)
game = (args.title, args.system, args.ownership, args.progress)
conn.execute("INSERT INTO games VALUES(:title, :system, :ownership, :progress)", game)
conn.commit()
conn.close()
- qual = "don't own" if args.ownership == 0 else 'own'
- comp = ("fresh", "in-progress", "beaten", "completed")
- # I shouldn't have to coerce that last argument but I don't know a better
- # solution.
- print("Added {0} for {1}. You {2} it and it's {3}.".format(args.title, args.system, qual, comp[int(args.progress)]))
+ qual = (
+ "don't own it",
+ "own it physically",
+ "own it digitally",
+ "own it physically and digitally"
+ )
+ comp = {
+ -1: "it's unbeatable",
+ 0: "it's new",
+ 1: "you're playing it",
+ 2: "it's been beaten",
+ 3: "it's been completed"
+ }
+ print("Added {0} for {1}. You {2} and {3}.".format(args.title, args.system, qual[int(args.ownership)], comp[int(args.progress)]))
def delete_game(args):
'''Removes a game from the database.'''
@@ -202,27 +218,52 @@ def delete_game(args):
else:
print("That game ID does not exist in the database.")
-def translate_arg(arg):
- '''Translate approximate argument names to their internal values for
- inserting into the database.'''
- # Verify we're working with a string.
+def translate_progress(arg):
+ '''Translate a letter progress value into a numeric value for the database.'''
try:
if len(arg) != 1:
print("Argument must be a single character.")
sys.exit()
if not arg.isnumeric():
- # We know it can't be coerced to a number, so it must be a string
- vals = {'f': 0, 'n': 0, 'i': 1, 'y': 1, 'b': 2, 'c': 3}
+ vals = {
+ 'u': -1,
+ 'n': 0,
+ 'p': 1,
+ 'b': 2,
+ 'c': 3,
+ }
try:
return vals[arg]
except KeyError:
# Note to self, doubling a brace escapes it. This was tucked away in the Python docs...
- print("Value '{}' not valid. Try one of {{y, n, f, i, b, c}}.".format(arg))
+ print("Value '{}' not valid. Try one of {{{}}}.".format(arg, ', '.join(vals)))
+ sys.exit()
+ else:
+ return arg
+ except TypeError:
+ return arg
+
+def translate_ownership(arg):
+ '''Translate a letter ownership value into a numeric value for the database.'''
+ try:
+ if len(arg) != 1:
+ print("Argument must be a single character.")
+ sys.exit()
+ if not arg.isnumeric():
+ vals = {
+ 'n': 0,
+ 'p': 1,
+ 'd': 2,
+ 'b': 3
+ }
+ try:
+ return vals[arg]
+ except KeyError:
+ print("Value '{}' not valid. Try one of {{{}}}.".format(arg, ', '.join(vals)))
sys.exit()
else:
return arg
except TypeError:
- # It's a number, go ahead and return it
return arg
def update_game(args):
@@ -238,8 +279,10 @@ def update_game(args):
print("Invalid field name indicated! Please choose from {}".format(', '.join(opts)))
sys.exit()
# Translate y, n, f, i, b, and c
- if args.field == 'ownership' or args.field == 'progress':
- args.value = translate_arg(args.value)
+ if args.field == 'ownership':
+ args.value = translate_ownership(args.value)
+ if args.field == 'progress':
+ args.value = translate_progress(args.value)
# We need this workaround because execute() doesn't like variable column names
update_stmt = "UPDATE games SET {} = :val WHERE rowid = :id".format(args.field)
conn = sqlite3.connect(DB_LOCATION)
@@ -247,14 +290,20 @@ def update_game(args):
c.execute(update_stmt, {'id': args.gid, 'val': args.value})
conn.commit()
if c.rowcount == 1:
- own_msg = ('not owned', 'owned')
- prog_msg = ('fresh', 'in-progress', 'beaten', 'complete')
+ own_msg = ('not owned', 'physically owned', 'digitally owned', 'physically and digitally owned')
+ prog_msg = {
+ -1: 'unbeatable',
+ 0: 'new',
+ 1: 'playing',
+ 2: 'beaten',
+ 3: 'complete'
+ }
update_msg = {
- 'title': "{ot} on {os} is now named {val}.",
- 'system': "{ot} on {os} is now on {val}.",
- 'ownership': "{ot} on {os} is now marked {val}.",
- 'progress': "{ot} on {os} is now marked {val}."
- }
+ 'title': "{ot} on {os} is now named {val}.",
+ 'system': "{ot} on {os} is now on {val}.",
+ 'ownership': "{ot} on {os} is now marked {val}.",
+ 'progress': "{ot} on {os} is now marked {val}."
+ }
print(update_msg[args.field].format(ot = target['title'], os = target['system'], val = args.value if args.field == 'title' or args.field == 'system' else own_msg[int(args.value)] if args.field == 'ownership' else prog_msg[int(args.value)]))
else:
print("Could not update game information. Check the DB's permissions.")
@@ -284,18 +333,21 @@ def list_games(args):
# works around that limitation.
conn = sqlite3.connect(DB_LOCATION)
conn.row_factory = sqlite3.Row
- # TODO: turn this into an object. The command parser must match filter names, so there's no reason to duplicate work.
select_stmts = {
'allgames': "SELECT * FROM allgames",
+ 'arcade': "SELECT * FROM arcade",
'backlog': "SELECT * FROM backlog",
+ 'borrowing': "SELECT * FROM borrowing",
'complete': "SELECT * FROM complete",
+ 'digital': "SELECT * FROM digital",
'done': "SELECT * FROM done",
- 'fresh': "SELECT * FROM fresh",
'incomplete': "SELECT * FROM incomplete",
+ 'new': "SELECT * FROM new",
'owned': "SELECT * FROM owned",
+ 'physical': "SELECT * FROM physical",
+ 'playlog': "SELECT * FROM playlog",
'unowned': "SELECT * FROM unowned",
'wishlist': "SELECT * FROM wishlist",
- 'playlog': "SELECT * FROM playlog"
}
# We're emulating a do-while loop
first_pass = True
@@ -336,7 +388,7 @@ def row_format(args, header):
with open('/dev/tty') as tty:
curwidth = int(subprocess.check_output(['stty', 'size']).split()[1])
maxwidth = curwidth
- twidth = maxwidth - 35
+ twidth = maxwidth - 37
if header == True:
print("{:^4s} | {:<{w}s} | {:<8s} | {:^3s} | {:<7s}".format("ID", "Title", "System", "Own", "Status", w=twidth))
print("-" * maxwidth)
@@ -344,13 +396,20 @@ def row_format(args, header):
gidstr = "{: >4d}".format(args['rowid'])
titlestr = "{: <{w}s}".format(args['title'][:twidth], w=twidth)
systemstr = "{: ^8s}".format(args['system'][:8])
- ownstr = "{: ^3s}".format("*" if args['ownership'] == 1 else " ")
- statltr = ['F', 'I', 'B', 'C']
+ ownltr = [' ', 'P', ' D', 'P D']
+ ownstr = "{: <3s}".format(ownltr[args['ownership']])
+ statltr = {
+ -1: 'U',
+ 0: 'N',
+ 1: 'P',
+ 2: 'B',
+ 3: 'C'
+ }
statstr = "{: <7s}".format((" " * args['progress'] * 2) + statltr[args['progress']])
"""
ID | Title | System | Own | Status
- -------------------------------------------------
- 1234 | This is a title | Wii U VC | * | F I B C
+ ---------------------------------------------------
+ 1234 | This is a title | Wii U VC | * | U N P B C
"""
safe_print(" | ".join((gidstr, titlestr, systemstr, ownstr, statstr)))
@@ -382,8 +441,7 @@ def main():
# 'list' command
parser_list = subparsers.add_parser('list', help="List your games with preset views")
- # TODO: don't repeat yourself! The filter list is essentially copied in list_games()
- list_filters = ['all', 'backlog', 'complete', 'done', 'fresh', 'incomplete', 'owned', 'unowned', 'wishlist', 'playlog']
+ list_filters = ['all', 'arcade', 'backlog', 'borrowing', 'complete', 'digital', 'done', 'incomplete', 'new', 'owned', 'physical', 'unowned', 'wishlist', 'playlog']
parser_list.add_argument('filter', nargs='?', choices=list_filters, default='all', help='Filter games accerding to preset queries. Valid filters: {}'.format(', '.join(list_filters)))
parser_list.add_argument('-r', '--raw', action="store_true", help="Output the list in a machine-readable format, separated by pipe characters.")
parser_list.set_defaults(func=list_games)