1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
|
package moe.nea.ledger
import moe.nea.ledger.database.ANDExpression
import moe.nea.ledger.database.BooleanExpression
import moe.nea.ledger.database.Clause
import moe.nea.ledger.database.DBItemEntry
import moe.nea.ledger.database.DBLogEntry
import moe.nea.ledger.database.Database
import moe.nea.ledger.utils.di.Inject
import net.minecraft.command.CommandBase
import net.minecraft.command.ICommandSender
import net.minecraft.util.BlockPos
import net.minecraft.util.ChatComponentText
import net.minecraft.util.ChatStyle
import net.minecraft.util.EnumChatFormatting
class QueryCommand : CommandBase() {
override fun canCommandSenderUseCommand(sender: ICommandSender?): Boolean {
return true
}
override fun getCommandName(): String {
return "ledger"
}
override fun getCommandUsage(sender: ICommandSender?): String {
return ""
}
override fun getCommandAliases(): List<String> {
return listOf("lgq")
}
@Inject
lateinit var logger: LedgerLogger
override fun processCommand(sender: ICommandSender, args: Array<out String>) {
if (args.isEmpty()) {
logger.printOut("§eHere is how you can look up transactions:")
logger.printOut("")
logger.printOut("§f- §e/ledger withitem %POTATO%")
logger.printOut(" §aLook up transactions involving potatoes!")
logger.printOut("§f- §e/ledger withitem ENCHANTED_POTATO")
logger.printOut(" §aLook up transactions involving just enchanted potatoes!")
logger.printOut("§f- §e/ledger withitem %POTATO% withitem %CARROT%")
logger.printOut(" §aLook up transactions involving potatoes or carrots!")
logger.printOut("§f- §e/ledger withtype AUCTION_SOLD")
logger.printOut(" §aLook up transactions of sold auctions!")
logger.printOut("§f- §e/ledger withtype AUCTION_SOLD withitem CRIMSON%")
logger.printOut(" §aLook up sold auctions involving crimson armor pieces!")
logger.printOut("")
logger.printOut("§eFilters of the same type apply using §aOR§e and loggers of different types apply using §aAND§e.")
logger.printOut("§eYou can use % as a wildcard!")
return
}
val p = parseArgs(args)
when (p) {
is ParseResult.Success -> {
executeQuery(p)
}
is ParseResult.UnknownFilter -> {
logger.printOut("§cUnknown filter name ${p.start}. Available filter names are: ${mFilters.keys.joinToString()}")
}
is ParseResult.MissingArg -> {
logger.printOut("§cFilter ${p.filterM.name} is missing an argument.")
}
}
}
override fun addTabCompletionOptions(
sender: ICommandSender,
args: Array<out String>,
pos: BlockPos
): MutableList<String>? {
when (val p = parseArgs(args)) {
is ParseResult.MissingArg -> return null
is ParseResult.Success -> return p.lastFilterM.tabComplete(args.last())
is ParseResult.UnknownFilter -> return getListOfStringsMatchingLastWord(args, mFilters.keys)
}
}
@Inject
lateinit var database: Database
private fun executeQuery(parse: ParseResult.Success) {
val grouped = parse.filters
val query = DBLogEntry.from(database.connection)
.select(DBLogEntry.type, DBLogEntry.transactionId)
.join(DBItemEntry, on = Clause { column(DBLogEntry.transactionId) eq column(DBItemEntry.transactionId) })
for (value in grouped.values) {
query.where(ANDExpression(value))
}
query.limit(80u)
val dedup = mutableSetOf<UUIDUtil.ULIDWrapper>()
query.forEach {
val type = it[DBLogEntry.type]
val transactionId = it[DBLogEntry.transactionId]
if (!dedup.add(transactionId)) {
return@forEach
}
val timestamp = transactionId.getTimestamp()
val items = DBItemEntry.selectAll(database.connection)
.where(Clause { column(DBItemEntry.transactionId) eq string(transactionId.wrapped) })
.map { ItemChange.from(it) }
val text = ChatComponentText("")
.setChatStyle(ChatStyle().setColor(EnumChatFormatting.YELLOW))
.appendSibling(
ChatComponentText(type.name)
.setChatStyle(ChatStyle().setColor(EnumChatFormatting.GREEN))
)
.appendText(" on ")
.appendSibling(timestamp.formatChat())
.appendText("\n")
.appendSibling(
ChatComponentText(transactionId.wrapped).setChatStyle(ChatStyle().setColor(EnumChatFormatting.DARK_GRAY))
)
for (item in items) {
text.appendText("\n")
.appendSibling(item.formatChat())
}
text.appendText("\n")
logger.printOut(text)
}
}
sealed interface ParseResult {
data class UnknownFilter(val start: String) : ParseResult
data class MissingArg(val filterM: FilterM) : ParseResult
data class Success(val lastFilterM: FilterM, val filters: Map<FilterM, List<BooleanExpression>>) : ParseResult
}
fun parseArgs(args: Array<out String>): ParseResult {
require(args.isNotEmpty())
val arr = args.iterator()
val filters = mutableMapOf<FilterM, MutableList<BooleanExpression>>()
var lastFilterM: FilterM? = null
while (arr.hasNext()) {
val filterName = arr.next()
val filterM = mFilters[filterName]
if (filterM == null) {
return ParseResult.UnknownFilter(filterName)
}
if (!arr.hasNext()) {
return ParseResult.MissingArg(filterM)
}
filters.getOrPut(filterM, ::mutableListOf).add(filterM.getFilter(arr.next()))
lastFilterM = filterM
}
return ParseResult.Success(lastFilterM!!, filters)
}
val mFilters = listOf(TypeFilter, ItemFilter).associateBy { it.name }
object TypeFilter : FilterM {
override val name: String
get() = "withtype"
override fun getFilter(text: String): BooleanExpression {
val preparedText = "%" + text.trim('%') + "%"
return Clause { column(DBLogEntry.type) like preparedText }
}
override fun tabComplete(partialArg: String): MutableList<String> {
return TransactionType.entries.asSequence().map { it.name }.filter { partialArg in it }.toMutableList()
}
}
object ItemFilter : FilterM {
override val name: String
get() = "withitem"
private val itemIdProvider = Ledger.di.provide<ItemIdProvider>() // TODO: close this escape hatch
override fun getFilter(text: String): BooleanExpression {
return Clause { column(DBItemEntry.itemId) like text }
}
override fun tabComplete(partialArg: String): MutableList<String>? {
return itemIdProvider.getKnownItemIds()
.asSequence()
.map { it.string }
.filter { partialArg in it }
.take(100)
.toMutableList()
}
}
interface FilterM {
val name: String
fun getFilter(text: String): BooleanExpression
fun tabComplete(partialArg: String): MutableList<String>?
// fun tabCompleteFilter() TODO
}
}
|