diff --git a/wilty.py b/wilty.py index d28437d..f8e2f71 100644 --- a/wilty.py +++ b/wilty.py @@ -11,28 +11,50 @@ class Wilty(): self.save_dir = ".botany" self.plant_file = "_plant_data.json" self.visit_file = "visitors.json" + self.harvest_file = "harvest_file.json" self.plants = {} - self.loadData() + self.stats_model = { + "total": 0, + "live": 0, + "seed": 0, + "young": 0, + "mature": 0, + "succ": 0, + "tree": 0, + "nonflower": 0, + "flower": 0, + "gen": 0, + "avg_gen": 0, + "harv": 0, + "avg_harv": 0, + } + self.border = "-" * 47 - def convertTS(self, ts): - """Convert a timestamp in seconds to a legible format, e.g. 12d 12h - 12m.""" + def formatTime(self, ts): + """Format an integer timestamp (seconds) to a timestamp string (days, + hours and minutes), e.g. 12d 12h 12m. + """ days = int(ts / 86400) hours = int((ts % 86400) / 3600) mins = round(((ts % 86400) % 3600) / 60) return str(days) + "d " + str(hours) + "h " + str(mins) + "m" def getUsers(self): - """Load usernames into the plants dictionary as keys.""" + """Load usernames into the plants dictionary as keys. Only users whose + plant data is peer-readable will be added. + """ sys_users = pwd.getpwall() for u in sys_users: - if os.path.exists("/home/" + u.pw_name + "/" + self.save_dir): + sv_dir = "/home/" + u.pw_name + "/" + self.save_dir + "/" + if os.path.exists(sv_dir) and \ + os.access(sv_dir + u.pw_name + self.plant_file, os.R_OK): self.plants[u.pw_name] = {} def checkPlant(self, user): """Given the username, calculates the last time since the user's plant was watered, estimates whether it is dead and updates the plants - dictionary.""" + dictionary. + """ # Get last visitor timestamp and check if later than user's visitor_ts = 0 if self.plants[user]["visitors"] != []: @@ -40,24 +62,20 @@ class Wilty(): watered = max(self.plants[user]["last_watered"], visitor_ts) time_since = int(time.time()) - watered self.plants[user]["time_since"] = time_since - # >432000 (5 days) = dead plant + # >5 days = dead plant if time_since > 432000: self.plants[user]["is_dead"] = True - self.plants[user]["time_since_fmt"] = self.convertTS(time_since) + self.plants[user]["time_since_fmt"] = self.formatTime(time_since) - def loadData(self): - """Load plant and visitor data into plants dictionary.""" + def getRawData(self): + """Load plant data into the plants dictionary.""" self.getUsers() for u in self.plants: sv_dir = "/home/" + u + "/" + self.save_dir + "/" # Get plant data - if os.access(sv_dir + u + self.plant_file, os.R_OK): - with open(sv_dir + u + self.plant_file) as plant_fh: - plant_json = plant_fh.read() - self.plants[u] = json.loads(plant_json) - self.plants[u]["allow_query"] = True - else: - self.plants[u]["allow_query"] = False + with open(sv_dir + u + self.plant_file) as plant_fh: + plant_json = plant_fh.read() + self.plants[u] = json.loads(plant_json) # Get visitor data if os.access(sv_dir + self.visit_file, os.R_OK) and \ os.access(sv_dir + self.visit_file, os.W_OK): @@ -68,37 +86,146 @@ class Wilty(): else: self.plants[u]["allow_visit"] = False self.plants[u]["visitors"] = [] - # Update plant watered state if plant data is readable - if self.plants[u]["allow_query"]: - self.checkPlant(u) + # Get harvest data + if os.access(sv_dir + self.harvest_file, os.R_OK): + with open(sv_dir + self.harvest_file) as harvest_fh: + harvest_json = harvest_fh.read() + self.plants[u]["harvests"] = json.loads(harvest_json) + else: + self.plants[u]["harvests"] = {} + # Update plant watered state + self.checkPlant(u) def listLivePlants(self, *args, **kwargs): - """List living plants open to visitors.""" + """Prints a list of living plants open to visitors. Optionally specify + a sort flag to sort the list alphabetically by username or plant type, + e.g. sort=\"plant\". By default, the list is sorted by the longest + interval since the last watered time descending. + """ sort_l = [] for p in self.plants: - if self.plants[p]["allow_query"] and \ - self.plants[p]["allow_visit"] and \ - not self.plants[p]["is_dead"]: + if self.plants[p]["allow_visit"] and not self.plants[p]["is_dead"]: sort_l.append((p, self.plants[p]["description"].split()[-1], \ self.plants[p]["time_since_fmt"], \ self.plants[p]["time_since"])) # Sort by column - column = kwargs.get("sort", "") - if column == "user": + if kwargs.get("sort", "") == "user": sort_l.sort(key=lambda c: c[0]) - elif column == "plant": + elif kwargs.get("sort", "") == "plant": sort_l.sort(key=lambda c: c[1]) else: sort_l.sort(key=lambda c: c[3], reverse=True) # Format output - border = "-" * 47 - print(border) + print(self.border) print("{:<20s}{:<15s}{:<15s}".format("User", "Plant", "Last Watered")) - print(border) + print(self.border) for i in sort_l: print("{:<20s}{:<15s}{:<15s}".format(i[0], i[1], i[2])) - print(border) + print(self.border) + + def countPlantStages(self, key, count): + """Count the plants in different stages (seedling, young, mature). Take + as arguments a key for plants dictionary lookup and a dictionary to + append the results.""" + if "stage" in self.plants[key]: + if self.plants[key]["stage"] == ("mature" or "flowering" or \ + "seed-bearing"): + count["mature"] += 1 + elif self.plants[key]["stage"] == "young": + count["young"] += 1 + else: + count["seed"] += 1 + else: + count["seed"] += 1 + + def countPlantGroups(self, key, count): + """Count plants in type groups.""" + succulents = ["agave", "aloe", "cactus", "lithops", "pachypodium"] + trees = ["baobab", "ficus", "palm"] + noflowers = ["fern", "moss"] + plant_type = self.plants[key]["description"].split()[-1] + if plant_type in succulents: + count["succ"] += 1 + elif plant_type in trees: + count["tree"] += 1 + elif plant_type in noflowers: + count["nonflower"] += 1 + elif plant_type != ("seed" or "seedling"): + count["flower"] += 1 + + def countGenRate(self, key, count): + """Count total generation rates.""" + if "generation" in self.plants[key]: + count["gen"] += self.plants[key]["generation"] + else: + count["gen"] += 1 + + def avgGenRate(self, count): + """Calculate the average plant generation.""" + count["avg_gen"] = round(count["gen"] / count["total"], \ + 3) + + def countHarvests(self, key, count): + """Count the total number of harvests.""" + if self.plants[key]["harvests"] != {}: + count["harv"] += len(self.plants[key]["harvests"]) + else: + count["harv"] += 0 + + def avgHarvests(self, count): + """Calculate the average number of harvests per user.""" + count["avg_harv"] = round(count["harv"] / count["total"], 3) + + def genStats(self, subset): + """Generate garden stats of all plants or only live plants.""" + stats = dict(self.stats_model) + if subset == "live": + for p in self.plants: + if not self.plants[p]["is_dead"]: + stats["live"] += 1 + self.countPlantStages(p, stats) + self.countPlantGroups(p, stats) + self.countGenRate(p, stats) + self.countHarvests(p, stats) + stats["total"] = stats["live"] + else: + for p in self.plants: + self.countPlantStages(p, stats) + self.countPlantGroups(p, stats) + self.countGenRate(p, stats) + self.countHarvests(p, stats) + stats["total"] = len(self.plants) + self.avgGenRate(stats) + self.avgHarvests(stats) + return stats + + def listStats(self, *args, **kwargs): + """Print plant stats. Optionally use a subset flag to get stats on live + plants only, e.g. subset=\"live\". By default, show stats based on the + total number of plants. + """ + if kwargs.get("subset", "all") == "live": + stats = self.genStats("live") + else: + stats = self.genStats("all") + print("Garden Stats (" + kwargs.get("subset", "all") + " plants)") + print(self.border) + print( + "Avg. generation: " + str(stats["avg_gen"]) + + "\nAvg. harvests per user: " + str(stats["avg_harv"]) + + "\nSeedlings: " + str(stats["seed"]) + + "\nYoung plants: " + str(stats["young"]) + + "\nMature plants: " + str(stats["mature"]) + + "\nFlowering shrubs: " + str(stats["flower"]) + + "\nNon-flowering plants: " + str(stats["nonflower"]) + + "\nSucculents: " + str(stats["succ"]) + + "\nTrees: " + str(stats["tree"]) + + "\nTotal plants: " + str(stats["total"]) + ) + print(self.border) instance = Wilty() +instance.getRawData() instance.listLivePlants() +instance.listStats()