<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Inhere</title>
    <description>The latest articles on DEV Community by Inhere (@inhere).</description>
    <link>https://dev.to/inhere</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F189337%2F89b81983-1573-41be-a1d8-c8db45ba5436.jpeg</url>
      <title>DEV Community: Inhere</title>
      <link>https://dev.to/inhere</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/inhere"/>
    <language>en</language>
    <item>
      <title>How my Go coding agent keeps a turn-1 constraint across 3 context resets</title>
      <dc:creator>Inhere</dc:creator>
      <pubDate>Mon, 26 Oct 2026 08:25:35 +0000</pubDate>
      <link>https://dev.to/inhere/how-my-go-coding-agent-keeps-a-turn-1-constraint-across-3-context-resets-426b</link>
      <guid>https://dev.to/inhere/how-my-go-coding-agent-keeps-a-turn-1-constraint-across-3-context-resets-426b</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://inhere.github.io/en/blog/2026/suga-long-session/" rel="noopener noreferrer"&gt;my blog&lt;/a&gt;. I'm the author of suga, an MIT-licensed coding agent written in Go.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Keep an agent in one session long enough and three things go wrong. The context fills up and gets compacted, and the part that gets dropped is the constraint you gave it in the first turn. Every request carries a few thousand tokens of fixed overhead, so long sessions get expensive. And when something breaks, there is no record of what the agent did.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;suga&lt;/code&gt; is a personal coding agent I write in Go. The goal is a session that can stay with me for a full working day. This post covers the trade-offs it makes for long sessions, which of them are backed by data and which experiments failed, and how it works with gofer, my job platform. suga itself is built on gofer, one batch of agent jobs at a time.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The current version is v0.49. It is &lt;code&gt;v0&lt;/code&gt;, so minor releases may break things. It is MIT licensed. What follows is an introduction to suga.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6ytee7zzxoki6oa57d6u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6ytee7zzxoki6oa57d6u.png" alt="suga: long agent sessions that keep the turn-1 constraint" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repository: &lt;a href="https://github.com/inhere/suga" rel="noopener noreferrer"&gt;github.com/inhere/suga&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Releases: &lt;a href="https://github.com/inhere/suga/releases/latest" rel="noopener noreferrer"&gt;releases/latest&lt;/a&gt; (Linux / macOS / Windows binaries)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A session that lasts all day: the hard part is the context reset
&lt;/h2&gt;

&lt;p&gt;The context window fills up eventually. The usual fix is to compress old messages into a summary and keep going. The summary is written by the model, though, and the model will reasonably drop whatever it thinks is unimportant, such as the "don't touch vendor/" you said in turn 1.&lt;/p&gt;

&lt;p&gt;suga uses &lt;strong&gt;segments&lt;/strong&gt; instead. When the context is nearly full, it starts a new segment. The old one is stored and stays searchable, and the new one opens with a &lt;strong&gt;handoff&lt;/strong&gt;. The handoff has a fixed structure: current task, coordinates (file / symbol / line), accepted and rejected decisions, how far verification got, progress and blockers, next step, and references to look things up. The "accepted and rejected" section holds hard constraints, rules and the user's own words. It is kept verbatim, has no length limit and may not be rewritten. Every other section has a byte budget (coordinates get at most 2 KB, for example), and old coordinates are deduplicated by file and symbol when they are folded in.&lt;/p&gt;

&lt;p&gt;Why a budget? The early handoff folded the whole previous handoff into each new one. In one 14-turn session it grew from 4.7 KB to 7.8 KB to 16 KB, and every later request carried it. With budgets, the same task stays at 7.2k to 8.7k tokens of context after each reset.&lt;/p&gt;

&lt;p&gt;This is tested by a set of "hard" long-session evals. Each task runs 13 to 14 turns with 2 to 3 forced resets, padded with large files and unrelated work, and the last turn checks whether the turn-1 constraint survived. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the user changed the constraint halfway through, and the check expects the new one, not the old one;&lt;/li&gt;
&lt;li&gt;the topic goes A, B, C and back to A, and the check looks for mixed-up topics;&lt;/li&gt;
&lt;li&gt;a tool output contains "ignore previous rules", and the check looks for whether the agent followed it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With a real model (deepseek-v4.1-flash), three runs per task, it currently passes 12/12.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;I got this wrong once.&lt;/strong&gt; I gave the model call that writes the handoff an output limit derived from the budget (about 3.3k tokens). Unit tests and fake-model evals all passed. On a real model, 3 of 4 tasks failed at the reset with "empty summary": a reasoning model spends output tokens on thinking, and the limit ran out before the answer started. That call now has no limit. The handoff size is enforced by the budgets and by trimming each section afterwards. The lesson: a fake model will not catch changes that depend on model behavior. They need a real model run.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Every token is accounted for: fixed prefix, tool descriptions, lazy loading
&lt;/h2&gt;

&lt;p&gt;Start with the shortest possible prompt (measured on v0.48). The prompt below asks the model to answer with just "ok":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;suga run &lt;span class="nt"&gt;--prompt&lt;/span&gt; &lt;span class="s2"&gt;"只回答 ok 两个字"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ok
opencode-go/deepseek-v4.1-flash · in 8853 / out 2 tokens · 2.8s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A 10-character prompt, 8,853 input tokens. The &lt;code&gt;context&lt;/code&gt; event in &lt;code&gt;suga inspect trace &amp;lt;session&amp;gt; --json&lt;/code&gt; shows where they went:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;Bytes&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tools&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;19,753&lt;/td&gt;
&lt;td&gt;schemas of the 16 tools in a read-only session, more than the next three combined&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rules&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;6,782&lt;/td&gt;
&lt;td&gt;workspace &lt;code&gt;AGENTS.md&lt;/code&gt; and local context files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;system&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3,559&lt;/td&gt;
&lt;td&gt;the stable system prompt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;skills&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1,333&lt;/td&gt;
&lt;td&gt;the skills list&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Providers cache by prefix, so the prefix has to stay stable: the tool table, the system prompt and the segment context do not change within a session, which lets later requests reuse what earlier ones cached. Put a timestamp or a random ID into the system prompt on every request and the cache stops working and the bill goes up. The thinking-signature bug later in this post has the same cause.&lt;/p&gt;

&lt;p&gt;Cost in a long session is roughly "number of calls × context per call". Breaking a 14-turn task down call by call showed that &lt;strong&gt;29% of the input was the fixed prefix resent on every call&lt;/strong&gt;: about 0.9k tokens of system prompt and about 7.0k tokens of schemas for the 20 built-in tools.&lt;/p&gt;

&lt;p&gt;So I did two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Shorter tool descriptions.&lt;/strong&gt; I removed parser details and conventions repeated across tools (five search tools each said "skips vendor/node_modules by default") and said them once in the system prompt. That saves about 920 tokens per call. A test now pins the character count of every tool schema so it cannot grow back unnoticed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lazy loading for rarely used tools.&lt;/strong&gt; In 128 eval runs, 11 of the 20 tools were called 44 times in total. The idea was to drop them from the tool table, keep a one-line list, and load them when needed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both went wrong at first, and the evals caught both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;After the trim, one task made 24% more calls. The session log showed why: the words "skips vendor/" had been cut from the &lt;code&gt;no_ignore&lt;/code&gt; parameter description, so the model could not see them and listed vendor/ through the shell instead. Putting those words back brought the call count back to normal.&lt;/li&gt;
&lt;li&gt;For lazy loading I first ran a probe: describe the tool schemas only in message text, not in the &lt;code&gt;tools&lt;/code&gt; declaration. Across three models and 9 runs, the model made 0 native calls. Models do not call tools that are not declared. So within a segment, lazy tools go through a resident proxy tool, &lt;code&gt;tool_call&lt;/code&gt;, and they join the real tool table at the next reset, which keeps the tool table and the cache stable within a segment. The on/off comparison: prompt per call down 16.9%, calls up 8.3%, total down only 6.8%, which failed the "calls up by no more than 5%" bar. So &lt;strong&gt;the feature is off by default&lt;/strong&gt;, and &lt;code&gt;tools.deferred_native: auto&lt;/code&gt; turns it on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These comparisons later became the eval &lt;code&gt;compare&lt;/code&gt; subcommand. It lines up two result sets by task, labels each as helped / hurt / no observed effect / confounded, and writes the verdict to a ledger, so I no longer do the math by hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  When something breaks, you can find out why: a thinking-signature bug
&lt;/h2&gt;

&lt;p&gt;suga writes every run to one SQLite file: sessions, turns, tokens and cache hits per model call, every tool call, resets, approvals and end-of-turn checks. Agents fail in predictable ways: going in circles, rerunning the same command, writing without verifying, claiming "done". With a timeline these show up. Here is the summary of one bug-fix run (measured on v0.48):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;suga inspect trace &amp;lt;session&amp;gt; &lt;span class="nt"&gt;--analyze&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;steps                  5 model calls, 6 tool calls, 2 parallel steps (max 2 calls in one step)
tokens                 in 51.2k (cache read 40.4k), out 427; reasoning 77 chars, text 478 chars
tools                  file_read x2, shell_exec x2, file_edit x1, file_list x1
re-reads               -
reads after own write  0
shell                  2 runs, 1 failed (exit != 0), 0 exited 0 with stderr output
repeated commands      command=go test ./... x2
tests                  2 runs, 0 after the first pass
phases                 explore 2, first write to green 2, after green 1 (model calls)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;re-reads&lt;/code&gt;, &lt;code&gt;reads after own write&lt;/code&gt;, &lt;code&gt;repeated commands&lt;/code&gt; and &lt;code&gt;tests&lt;/code&gt; rows exist to spot going in circles and skipped verification. &lt;code&gt;--transcript&lt;/code&gt; renders the same session as plain text you can paste into a review or hand to another agent, and &lt;code&gt;--web&lt;/code&gt; opens a read-only page you can leave open during a long run. A trace is a record after the fact. It does not stop an agent from looping, and it does not replace tests.&lt;/p&gt;

&lt;p&gt;Here is one problem the trace helped me find. Running multi-step tool calls on Claude, the second request was rejected with &lt;code&gt;Invalid signature in thinking block&lt;/code&gt;. Comparing the two requests turned up two causes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;after optional arguments the model left out were normalized away, &lt;code&gt;tool_use.input&lt;/code&gt; was sent back as &lt;code&gt;null&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Anthropic now binds a thinking block's signature to the request prefix it was generated under, and suga added a "about N minutes left before the deadline" reminder to every step. The text changed on every step, so the earlier thinking block no longer verified.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The fix: send back &lt;code&gt;{}&lt;/code&gt;, and keep that reminder's text and position fixed within a turn. After that I recorded real streaming responses from four providers (deepseek, glm, Claude, codex) as replay fixtures, with keys removed, so unit tests catch the next change in these protocol details first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Safe by default, and "tests pass" comes from a command, not the model
&lt;/h2&gt;

&lt;p&gt;suga is read-only by default. With writes enabled, chat asks before writing a file or running a command (&lt;code&gt;y&lt;/code&gt; / &lt;code&gt;n&lt;/code&gt; / &lt;code&gt;a&lt;/code&gt; to stop asking for this session), and rules by tool, path or command can live in a file. File tools snapshot before writing, so &lt;code&gt;:undo&lt;/code&gt; or &lt;code&gt;suga undo &amp;lt;session&amp;gt;&lt;/code&gt; can roll back. Changes made through &lt;code&gt;shell_exec&lt;/code&gt; are recorded but not rolled back, on purpose.&lt;/p&gt;

&lt;p&gt;A model saying "fixed, tests pass" does not count. &lt;code&gt;--verify&lt;/code&gt; runs the check command you give it after the turn ends, and a failure goes back to the model as a new turn (at most 2 rounds by default). A small repository with one bug (the prompt asks it to confirm the bug in &lt;code&gt;Add&lt;/code&gt; with the tests, then fix it; the model answered in Chinese):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;suga run &lt;span class="nt"&gt;--allow-write&lt;/span&gt; &lt;span class="nt"&gt;--verify&lt;/span&gt; &lt;span class="s2"&gt;"go test ./..."&lt;/span&gt; &lt;span class="nt"&gt;--prompt&lt;/span&gt; &lt;span class="s2"&gt;"calc.go 里的 Add 有 bug，先跑测试确认，再修好它"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;已确认并修复。
**确认 bug**：修复前 go test ./... 失败 —— calc_test.go:7: Add(2,3) = -1, want 5。
**原因**：calc.go:5 里 Add 写成了 a - b。
**修复**：改为 return a + b。

verify #1: go test ./... → exit 0 (488ms)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;verify #1&lt;/code&gt; line is the evidence. The same turn also runs end-of-turn checks (&lt;code&gt;nudge&lt;/code&gt;): files written but no check command run, plan steps still open, or an answer ending with "next I'll run the tests" each push the model back once.&lt;/p&gt;

&lt;p&gt;One more detail: approval is done by a person, and the model does not know that. Once, running as a resident session, suga replied to a message for me and wrote that "writing the file did not trigger approval". In fact I had approved it. Now a manually approved call ends its result with the line &lt;code&gt;[已经人工批准执行]&lt;/code&gt; ("executed after manual approval"), so the model does not have to guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working with gofer: how suga gets built
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/inhere/gofer" rel="noopener noreferrer"&gt;gofer&lt;/a&gt; is my other project. It runs agents such as codex, claude and suga as async jobs: submit a job, work in a separate git worktree, approve from the web UI or DingTalk, read logs and token usage, merge back to the main branch. suga runs in gofer in two ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;suga run&lt;/code&gt;: a one-off job that exits when done and streams events as stream-json;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;suga acp&lt;/code&gt;: a resident session. Each tool-call approval goes through ACP to gofer, where you click it on the web or in DingTalk.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is how suga itself is developed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Supervisor session (reads the plan, writes job briefs, reviews, releases)
   │  gofer job run -a suga --model &amp;lt;deepseek | glm | codex&amp;gt; --worktree ...
   ▼
Several suga jobs in parallel, each in its own worktree + branch: change code, run tests, commit
   │  supervisor reviews the diff, reruns race tests and pty smoke tests in a Linux container
   ▼
gofer job worktree merge → suga-verify workflow
   (Linux race / CI simulation / staticcheck / Windows tests + ConPTY smoke / 40+ pty scenarios)
   ▼
tag → GitHub Actions builds binaries for three platforms → upload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Jobs go to different models in rotation, and every review verdict is recorded. Changes that need a design get a design doc, a review and human approval, then a plan that also needs approval before implementation. By v0.49 there are 56 tags and more than 2,400 commits. For the last few dozen batches, the code was written almost entirely by suga jobs. I split the work, review it and release.&lt;/p&gt;

&lt;p&gt;The most useful step in this loop turned out to be review. Implementers often fail where they cannot verify their own work: a pty smoke test written on Windows that never ran, a fake model whose if/elif chain was cut in two by a single assignment, a test that took the echo of the input line as the end of the turn. All of these were caught by rerunning in a container before the merge.&lt;/p&gt;

&lt;p&gt;The two also connect directly. gofer sends mail to a resident suga session (&lt;code&gt;agent:suga&lt;/code&gt;), and suga replies with gofer's mailbox tool. suga sessions can message each other (&lt;code&gt;suga send&lt;/code&gt;) with message IDs, merged delivery, receipts and failure barriers. gofer's memory backend can serve as suga's long-term memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core embeds in other Go programs
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;pkg/agentcore&lt;/code&gt; is the core below the application layer: the model/tool loop, resumable run state, providers, tool registry and approval, MCP, skills and long-term memory. It does not depend on &lt;code&gt;internal/*&lt;/code&gt;. An allowlist in &lt;code&gt;pkg/agentcore/layering_test.go&lt;/code&gt; enforces that boundary: any dependency outside the list, or a new package that is not registered, fails the test. The smallest working setup needs one provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;runtime&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;agentcore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agentcore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Provider&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Final&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hello"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agentcore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunInput&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;AgentID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="s"&gt;"example-agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Instruction&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"say hello"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="c"&gt;// result.Output == "hello"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a real model, swap in &lt;code&gt;llm.NewOpenAIProvider(...)&lt;/code&gt;. Add tools with &lt;code&gt;reg.Register(...)&lt;/code&gt;. To control permissions, wrap it in &lt;code&gt;tools.NewApproval&lt;/code&gt;: with no approver configured, every call is denied, not allowed. The limits, stated here too: it is a single-agent loop with no multi-agent orchestration, and &lt;code&gt;pkg/sugasdk&lt;/code&gt; (in-process Open / Send / Event) is marked experimental.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compared with similar tools
&lt;/h2&gt;

&lt;p&gt;Claude Code and Codex CLI come from the model vendors, and their model integration and interaction are more polished. aider has a mature git workflow. opencode has a good TUI. suga makes different trade-offs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one Go binary with no runtime dependencies; Windows is a first-class platform (PowerShell, Job Object process trees, ConPTY smoke tests);&lt;/li&gt;
&lt;li&gt;session persistence, segments and handoffs, long-session evals and regression gates are where most of the effort went;&lt;/li&gt;
&lt;li&gt;it embeds in other Go programs (&lt;code&gt;pkg/sugasdk&lt;/code&gt;) and can run as an ACP server driven by an editor or by gofer;&lt;/li&gt;
&lt;li&gt;it switches between providers, and the config can list fallback models that it moves to when a connection fails.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When it is a poor fit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you want an IDE plugin or a GUI: it is terminal only for now;&lt;/li&gt;
&lt;li&gt;you want multi-agent orchestration: a sub-agent only does one read-only investigation and never hands out work of its own; job queues and the console live on the gofer side;&lt;/li&gt;
&lt;li&gt;you do not want to bring your own provider key: every request goes straight to the endpoint you configure, and there is no hosted service;&lt;/li&gt;
&lt;li&gt;you need English docs: the detailed docs are the Chinese built-in skill &lt;code&gt;suga-guide&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;you need a stable API: it is &lt;code&gt;v0&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I mostly use it on Windows and Linux, and CI runs Ubuntu and Windows. macOS builds are published, but I rarely use them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Up and running in 30 seconds
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eget &lt;span class="nb"&gt;install &lt;/span&gt;inhere/suga          &lt;span class="c"&gt;# or go install github.com/inhere/suga/cmd/suga@latest&lt;/span&gt;
suga init                         &lt;span class="c"&gt;# creates ~/.suga/config.yaml and .env&lt;/span&gt;
&lt;span class="c"&gt;# put one key in ~/.suga/.env, e.g. DEEPSEEK_API_KEY=... (built in: openai / anthropic / deepseek)&lt;/span&gt;
suga config &lt;span class="nb"&gt;set &lt;/span&gt;model deepseek/deepseek-chat
suga chat                         &lt;span class="c"&gt;# start in the current directory; :help lists commands, :context shows what is in the context&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a one-off task: &lt;code&gt;suga run --prompt "Summarize this project"&lt;/code&gt;. To have it change code and check its work: &lt;code&gt;suga run --allow-write --verify "go test ./..." --prompt "Fix the failing test"&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you read the code, start in these four places
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;internal/cli&lt;/code&gt;: one file per command; &lt;code&gt;root.go&lt;/code&gt; shows the whole command tree;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;internal/wiring&lt;/code&gt;: assembles providers, tools, skills, the system prompt and the runner, shared by the CLI and ACP;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;internal/personal&lt;/code&gt;: turns, segments, handoffs and events; reading it is reading suga's product logic;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pkg/agentcore&lt;/code&gt;: the core and capability layer; start with the table in &lt;code&gt;layering_test.go&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;docs/&lt;/code&gt; holds 45 design docs, 32 plans and 13 research reports. Most of the "why" is in there, and most numbers in this post come from &lt;code&gt;docs/research&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I'm inhere (&lt;a href="https://inhere.github.io" rel="noopener noreferrer"&gt;blog&lt;/a&gt; · &lt;a href="https://github.com/inhere" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;), the author of suga. The numbers in this post come from measured runs on v0.48 to v0.49 and from the reports under &lt;code&gt;docs/research&lt;/code&gt; in the repository. The commands are all in the text, so you can reproduce them.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>go</category>
      <category>ai</category>
      <category>cli</category>
      <category>opensource</category>
    </item>
    <item>
      <title>miglite v0.8.0: ship migrations inside the binary</title>
      <dc:creator>Inhere</dc:creator>
      <pubDate>Tue, 29 Sep 2026 12:22:19 +0000</pubDate>
      <link>https://dev.to/inhere/miglite-v080-ship-migrations-inside-the-binary-2l4n</link>
      <guid>https://dev.to/inhere/miglite-v080-ship-migrations-inside-the-binary-2l4n</guid>
      <description>&lt;p&gt;The last post about &lt;code&gt;miglite&lt;/code&gt; covered v0.4.0 in June. Since then it shipped four releases and 69 commits (&lt;code&gt;v0.4.0..v0.8.0&lt;/code&gt;), up to v0.8.0. The design did not change. Two things did: migration files can now travel inside the binary, and several flags that were accepted but ignored now take effect.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxggcc9v6nbg6xupd0ukf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxggcc9v6nbg6xupd0ukf.png" alt="miglite v0.8.0: embed migrations in the binary" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Project: &lt;a href="https://github.com/gookit/miglite" rel="noopener noreferrer"&gt;https://github.com/gookit/miglite&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;API docs: &lt;a href="https://pkg.go.dev/github.com/gookit/miglite" rel="noopener noreferrer"&gt;https://pkg.go.dev/github.com/gookit/miglite&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Previous post: &lt;a href="https://dev.to/en/blog/2026/gookit-miglite-intro/"&gt;miglite: raw SQL migrations for Go projects&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Latest release: &lt;a href="https://github.com/gookit/miglite/releases/tag/v0.8.0" rel="noopener noreferrer"&gt;v0.8.0&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What changed across these releases
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;v0.5.0&lt;/td&gt;
&lt;td&gt;2026-07-18&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;exec&lt;/code&gt; runs multiple statements in one transaction; &lt;code&gt;--db&lt;/code&gt; overrides the database name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v0.6.0&lt;/td&gt;
&lt;td&gt;2026-08-08&lt;/td&gt;
&lt;td&gt;Dependency updates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v0.7.0&lt;/td&gt;
&lt;td&gt;2026-09-21&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;internal/runtime&lt;/code&gt; extraction, four behaviour fixes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v0.8.0&lt;/td&gt;
&lt;td&gt;2026-09-28&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;fs.FS&lt;/code&gt; / &lt;code&gt;embed.FS&lt;/code&gt; migrations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The command set is unchanged: &lt;code&gt;create / init / up / down / skip / status / show / exec&lt;/code&gt;. Upgrade scripts do not need edits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ship migrations inside the binary
&lt;/h2&gt;

&lt;p&gt;Deploying a small service used to mean keeping a &lt;code&gt;migrations/&lt;/code&gt; directory next to the binary. In a container image that directory needs a &lt;code&gt;COPY&lt;/code&gt; line and the working directory has to line up with it. When the directory is missing or its files come from an older build, &lt;code&gt;miglite status&lt;/code&gt; reports no pending migrations, which is not an obvious failure.&lt;/p&gt;

&lt;p&gt;SQL files can now be embedded with &lt;code&gt;go:embed&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"embed"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/gookit/miglite"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/gookit/miglite/pkg/command"&lt;/span&gt;

    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="s"&gt;"modernc.org/sqlite"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;//go:embed migrations/*.sql&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;migrationFS&lt;/span&gt; &lt;span class="n"&gt;embed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FS&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;mig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;miglite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewAuto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;miglite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// a logical path inside migrationFS, not a disk path&lt;/span&gt;
        &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Migrations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"migrations"&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// miglite.NewWithConfigAndFS(cfg, migrationFS) does the same&lt;/span&gt;
    &lt;span class="n"&gt;mig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetFS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;migrationFS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetSqlDB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;mig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Up&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UpOption&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Yes&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;After &lt;code&gt;SetFS&lt;/code&gt;, &lt;code&gt;cfg.Migrations.Path&lt;/code&gt; is an io/fs logical path with forward slashes, not a directory on disk.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SetFS(nil)&lt;/code&gt; goes back to reading from the local filesystem, for projects that support both.&lt;/li&gt;
&lt;li&gt;An embedded filesystem is read-only, so &lt;code&gt;create&lt;/code&gt; still writes to the local disk and cannot add files to &lt;code&gt;embed.FS&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The new API surface is &lt;code&gt;Migrator.SetFS&lt;/code&gt;, &lt;code&gt;NewWithConfigAndFS&lt;/code&gt; and the package-level &lt;code&gt;miglite.BindFS&lt;/code&gt;, plus &lt;code&gt;ParseFS&lt;/code&gt;, &lt;code&gt;FindMigrationsFS&lt;/code&gt; and &lt;code&gt;MigrationsFromFS&lt;/code&gt; in &lt;code&gt;pkg/migration&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you reuse miglite's commands in your own CLI instead of calling &lt;code&gt;cmd/miglite&lt;/code&gt;, one &lt;code&gt;miglite.BindFS(migrationFS)&lt;/code&gt; call before &lt;code&gt;app.Run()&lt;/code&gt; makes the handlers read from that filesystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  --db points one command at another database
&lt;/h2&gt;

&lt;p&gt;Debugging and tests often need the same instance with a different database name, for example running migrations against a copy of production data. Editing the config or the environment variables for that is easy to forget to undo.&lt;/p&gt;

&lt;p&gt;v0.5.0 added a global &lt;code&gt;--db&lt;/code&gt; flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# status of one specific database&lt;/span&gt;
miglite &lt;span class="nt"&gt;--db&lt;/span&gt; new_db status

&lt;span class="c"&gt;# run SQL against that database&lt;/span&gt;
miglite &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;--db&lt;/span&gt; new_db &lt;span class="nt"&gt;--yes&lt;/span&gt; &lt;span class="s2"&gt;"SELECT current_database();"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It overrides the database name from the YAML file, &lt;code&gt;DATABASE_DSN&lt;/code&gt; or &lt;code&gt;DATABASE_URL&lt;/code&gt;; for SQLite it overrides the database file path. The flag applies to the current command only and is not written back to the config.&lt;/p&gt;

&lt;h2&gt;
  
  
  exec runs multi-statement SQL in one transaction
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;exec&lt;/code&gt; used to send statements to the database one at a time. If statement five failed, the first four had already been applied and the rest never ran.&lt;/p&gt;

&lt;p&gt;It now splits the input with &lt;code&gt;SplitSQL&lt;/code&gt; and runs the statements inside a single transaction, committing only when all of them succeed. Query statements go through &lt;code&gt;Query&lt;/code&gt;, and their result set is printed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;miglite &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;--yes&lt;/span&gt; ./scripts/import-seed.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The database still decides what can be rolled back. Several MySQL DDL statements commit implicitly and cannot be rolled back as part of a transaction. miglite guarantees how it executes the statements; whether a DDL statement is reversible depends on the engine. Migration files have the same constraint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four behaviours that only looked right
&lt;/h2&gt;

&lt;p&gt;v0.7.0 moved the logic out of package-level globals in &lt;code&gt;pkg/command&lt;/code&gt; into &lt;code&gt;internal/runtime&lt;/code&gt;: each call builds a &lt;code&gt;Runtime&lt;/code&gt; (config, connection, filesystem, connection ownership), and the CLI and the library share one output path. The refactor was not meant to be visible, and it fixed four real problems along the way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;up --skip-err&lt;/code&gt; accepted the flag and ignored it. A failed file is now skipped and the remaining migrations still run, while the command returns an error and the CLI exits non-zero.&lt;/li&gt;
&lt;li&gt;An injected &lt;code&gt;*sql.DB&lt;/code&gt; is no longer closed. A connection passed to &lt;code&gt;SetSqlDB(db)&lt;/code&gt; belongs to the caller, and only connections opened from the config are closed by miglite. In a long-running service the old behaviour could close a connection out from under you.&lt;/li&gt;
&lt;li&gt;A migration file without a &lt;code&gt;DOWN&lt;/code&gt; section is now reported as skipped with the &lt;code&gt;empty_down&lt;/code&gt; status, and only a real rollback reaches the after hook. Before, &lt;code&gt;down&lt;/code&gt; on such a file gave the impression that a rollback had happened.&lt;/li&gt;
&lt;li&gt;Failures no longer print the success banner. That sounds minor until a script scans the output by eye and the last line says everything completed.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;miglite up &lt;span class="nt"&gt;--yes&lt;/span&gt; &lt;span class="nt"&gt;--skip-err&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Using miglite as a library
&lt;/h2&gt;

&lt;p&gt;The main package still builds on &lt;code&gt;database/sql&lt;/code&gt; and does not bind a driver by default, the same as in v0.4.0. A typical call sequence now looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;mig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;miglite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewWithConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// or NewAuto / New(configFile)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetSqlDB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InitOption&lt;/span&gt;&lt;span class="p"&gt;{});&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Up&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UpOption&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Yes&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusOption&lt;/span&gt;&lt;span class="p"&gt;{});&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three differences from the CLI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Library calls never ask for confirmation. &lt;code&gt;Yes&lt;/code&gt; only affects the CLI prompt.&lt;/li&gt;
&lt;li&gt;Library calls print the same progress output as the CLI, through the shared output path introduced in v0.7.0.&lt;/li&gt;
&lt;li&gt;There is nothing to close. The &lt;code&gt;*sql.DB&lt;/code&gt; you pass in stays yours, and connections opened from the config are closed at the end of each call.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Should you upgrade
&lt;/h2&gt;

&lt;p&gt;There are no breaking changes between v0.4.0 and v0.8.0: no command was removed, no config key changed, and the migration file format is the same. Two things are worth checking after the upgrade:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If a script relied on &lt;code&gt;up --skip-err&lt;/code&gt; stopping at the first failure, it now continues past failed files. Check the exit-code handling around it.&lt;/li&gt;
&lt;li&gt;If you pass your own &lt;code&gt;*sql.DB&lt;/code&gt;, the connection is no longer closed for you, so workarounds added for that can be deleted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a single-binary deployment that should not carry a &lt;code&gt;migrations&lt;/code&gt; directory, the &lt;code&gt;embed.FS&lt;/code&gt; support in v0.8.0 removes that step. For a local SQLite database that just needs schema management, the v0.4.0 usage still works unchanged.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Project: &lt;a href="https://github.com/gookit/miglite" rel="noopener noreferrer"&gt;gookit/miglite&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Quick install: &lt;code&gt;go install github.com/gookit/miglite/cmd/miglite@latest&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Or with &lt;a href="https://github.com/inherelab/eget" rel="noopener noreferrer"&gt;eget&lt;/a&gt;: &lt;code&gt;eget install gookit/miglite&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>go</category>
    </item>
    <item>
      <title>eget: install GitHub releases without waiting for a central index</title>
      <dc:creator>Inhere</dc:creator>
      <pubDate>Wed, 01 Jul 2026 16:18:04 +0000</pubDate>
      <link>https://dev.to/inhere/eget-install-github-releases-without-waiting-for-a-central-index-751</link>
      <guid>https://dev.to/inhere/eget-install-github-releases-without-waiting-for-a-central-index-751</guid>
      <description>&lt;p&gt;The pace of command-line tooling has changed. New tools show up on GitHub every day, and active projects may publish several releases in a week, sometimes in a day.&lt;/p&gt;

&lt;p&gt;The slow part is often not the project itself. It is the central package index in the middle: a formula, bucket, manifest, or package definition that has not caught up yet. &lt;code&gt;eget&lt;/code&gt; is for that gap. If the release asset already exists, or the download URL can be described by a simple rule, &lt;code&gt;eget&lt;/code&gt; can fetch and install it directly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg148bup1zcv5qm3l71i7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg148bup1zcv5qm3l71i7.png" alt="eget poster" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Project: &lt;a href="https://github.com/inherelab/eget" rel="noopener noreferrer"&gt;https://github.com/inherelab/eget&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Latest release: &lt;a href="https://github.com/inherelab/eget/releases/latest" rel="noopener noreferrer"&gt;v1.8.x&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Documentation: &lt;a href="https://github.com/inherelab/eget/blob/main/README.md" rel="noopener noreferrer"&gt;README.md&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Start with a GitHub repository
&lt;/h2&gt;

&lt;p&gt;The shortest path is to pass a repository name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eget cfg init
&lt;span class="c"&gt;# install tools&lt;/span&gt;
eget i openai/codex
eget &lt;span class="nb"&gt;install &lt;/span&gt;junegunn/fzf
eget &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; rg BurntSushi/ripgrep
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;eget&lt;/code&gt; reads release assets, matches the current OS and architecture, downloads the selected file, extracts it when needed, and installs the executable into the target directory.&lt;/p&gt;

&lt;p&gt;If a project uses nightly builds, beta tags, or a specific release tag, choose it explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eget &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--tag&lt;/span&gt; nightly inhere/markview
eget &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--tag&lt;/span&gt; v0.28.0 openai/codex
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no extra registry step here. If the upstream release page has the asset, &lt;code&gt;eget&lt;/code&gt; can start from there.&lt;/p&gt;

&lt;h2&gt;
  
  
  The repository is the entry point
&lt;/h2&gt;

&lt;p&gt;Many package managers depend on a central index. That gives users a consistent experience, but it also creates delay. A project can publish a release before the index is updated.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;eget&lt;/code&gt; keeps the default path short:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;owner/repo -&amp;gt; releases -&amp;gt; assets -&amp;gt; install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When automatic asset matching is not enough, inspect the assets and add filters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eget query &lt;span class="nt"&gt;--action&lt;/span&gt; assets openai/codex

eget &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--asset&lt;/span&gt; &lt;span class="s2"&gt;"PRE:codex,SUF:.zip"&lt;/span&gt; openai/codex
eget &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--asset&lt;/span&gt; &lt;span class="s2"&gt;"REG:&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.deb$"&lt;/span&gt; owner/repo
eget &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--asset&lt;/span&gt; &lt;span class="s2"&gt;"windows,amd64,zip"&lt;/span&gt; owner/repo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some release archives have nested directories, or the executable name is not the name you want on disk. Handle that at install time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eget &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--extract-all&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--rename&lt;/span&gt; &lt;span class="s2"&gt;"codex-x86_64-pc-windows-msvc.exe=codex.exe"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  openai/codex

eget download &lt;span class="nt"&gt;--extract-all&lt;/span&gt; &lt;span class="nt"&gt;--strip-components&lt;/span&gt; 1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--asset&lt;/span&gt; &lt;span class="s2"&gt;"windows,zip"&lt;/span&gt; ventoy/Ventoy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The point is not to describe every package in advance. The point is to work with the files that upstream already published.&lt;/p&gt;

&lt;h2&gt;
  
  
  It is not limited to GitHub
&lt;/h2&gt;

&lt;p&gt;GitHub is common, but it is not the only source. &lt;code&gt;eget install&lt;/code&gt; and &lt;code&gt;eget download&lt;/code&gt; can target GitLab, Gitea/Forgejo, SourceForge, direct URLs, and local files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eget &lt;span class="nb"&gt;install &lt;/span&gt;gitlab:fdroid/fdroidserver
eget &lt;span class="nb"&gt;install &lt;/span&gt;gitlab:gitlab.gnome.org/GNOME/gtk
eget &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--asset&lt;/span&gt; linux,amd64 gitea:codeberg.org/forgejo/forgejo
eget &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--asset&lt;/span&gt; x64,PerUser,setup sourceforge:winmerge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A plain download URL also works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eget download &lt;span class="nt"&gt;--file&lt;/span&gt; go &lt;span class="nt"&gt;--to&lt;/span&gt; ~/go1.17.5 &lt;span class="se"&gt;\&lt;/span&gt;
  https://go.dev/dl/go1.17.5.linux-amd64.tar.gz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That matters for tools that do not publish GitHub releases. If there is a stable URL, or if the latest version can be read from a page, JSON file, or YAML file, the tool does not need to be wrapped in a central index first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Internal tools can use one reusable rule
&lt;/h2&gt;

&lt;p&gt;Internal tool distribution is usually boring in a very specific way. Every team has its own layout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://mydev.lan/tools/markview/latest.yaml
http://mydev.lan/tools/markview/markview-linux-amd64
http://mydev.lan/tools/chlog/latest.yaml
http://mydev.lan/tools/chlog/chlog-linux-amd64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the naming rule is stable, each tool should not need a full copy of the same configuration. &lt;code&gt;pkg_templates&lt;/code&gt; lets you describe the rule once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[pkg_templates.mydev]&lt;/span&gt;
&lt;span class="py"&gt;latest_url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"http://mydev.lan/tools/{name}/latest.yaml"&lt;/span&gt;
&lt;span class="py"&gt;latest_format&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"yaml"&lt;/span&gt;
&lt;span class="py"&gt;url_template&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"http://mydev.lan/tools/{name}/{name}-{os}-{arch}{ext}"&lt;/span&gt;
&lt;span class="py"&gt;ext_map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;windows&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;".exe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;linux&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;darwin&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nn"&gt;[packages.markview]&lt;/span&gt;
&lt;span class="py"&gt;repo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"pkg-template:mydev:markview"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then use the short alias:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eget add mydev:markview
eget &lt;span class="nb"&gt;install &lt;/span&gt;mydev:markview
eget &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--add&lt;/span&gt; mydev:chlog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not an internal app store. There is no server-side registry to maintain. It is a local rule for turning a package name into a latest-version request and a download URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep frequently used tools in config
&lt;/h2&gt;

&lt;p&gt;For a one-off test, &lt;code&gt;install owner/repo&lt;/code&gt; is enough. For tools you use regularly, add them to local config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eget &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--add&lt;/span&gt; junegunn/fzf
eget &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--add&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; rg BurntSushi/ripgrep
eget add &lt;span class="nt"&gt;--name&lt;/span&gt; winmerge &lt;span class="nt"&gt;--system&lt;/span&gt; windows/amd64 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--asset&lt;/span&gt; x64,PerUser,setup sourceforge:winmerge

&lt;span class="c"&gt;# restore all configured tools from an existing config file&lt;/span&gt;
eget &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;List what is installed or configured:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# installed packages by default; --all shows all configured packages too&lt;/span&gt;
eget list
eget &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;--all&lt;/span&gt;
&lt;span class="c"&gt;# configured packages that are not installed yet&lt;/span&gt;
eget &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;--ni&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The install record lives in &lt;code&gt;~/.config/eget/installed.toml&lt;/code&gt; by default. Config files live under the same directory, and you can edit them directly when that is faster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Update tools
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eget update &lt;span class="nt"&gt;--all&lt;/span&gt; &lt;span class="c"&gt;# check and update all updatable packages&lt;/span&gt;
&lt;span class="c"&gt;# or check first, then update selected tools&lt;/span&gt;
eget up &lt;span class="nt"&gt;--check&lt;/span&gt;
eget up fzf markview codex
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Self-update is a separate command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eget up &lt;span class="nt"&gt;--self&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  SDK downloads are built in
&lt;/h2&gt;

&lt;p&gt;SDK archives have a similar problem to CLI tools: multiple versions, multiple platforms, mirrors, archive extraction, and local install records.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;eget sdk&lt;/code&gt; handles that layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eget sdk config add &lt;span class="nt"&gt;--all&lt;/span&gt;
eget sdk config add &lt;span class="nt"&gt;--all&lt;/span&gt; &lt;span class="nt"&gt;--mirror&lt;/span&gt; mirror
eget sdk config add jdk &lt;span class="nt"&gt;--mirror&lt;/span&gt; zulu

eget sdk index refresh go
eget sdk index show go

eget sdk &lt;span class="nb"&gt;install &lt;/span&gt;go@1.22.0
eget sdk &lt;span class="nb"&gt;install &lt;/span&gt;go:1.22 node:20.11.1
eget sdk dl &lt;span class="nt"&gt;--os&lt;/span&gt; linux &lt;span class="nt"&gt;--arch&lt;/span&gt; arm64 &lt;span class="nt"&gt;-o&lt;/span&gt; ./downloads go:1.22
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The built-in templates cover Go, Node, and JDK. Sources can use official URLs or common mirrors. SDK index cache, archive download cache, and install records are kept separate, which works better for multi-version SDK installs.&lt;/p&gt;

&lt;p&gt;One boundary is important: &lt;code&gt;eget sdk&lt;/code&gt; downloads, extracts, and records SDKs. It does not modify &lt;code&gt;PATH&lt;/code&gt;, write shell hooks, or switch the active version for a project. If you need project-level activation, pair it with a tool such as &lt;a href="https://github.com/inhere/xenv" rel="noopener noreferrer"&gt;xenv&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cache mirrors help on team machines
&lt;/h2&gt;

&lt;p&gt;Repeated installs are faster when downloads are cached. On a local network, one machine can expose its existing package and SDK cache:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eget cache serve &lt;span class="nt"&gt;--host&lt;/span&gt; 0.0.0.0 &lt;span class="nt"&gt;--port&lt;/span&gt; 8686 &lt;span class="nt"&gt;--token&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$EGET_CACHE_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--json-log&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Other machines can try that cache before going upstream:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[cache_mirror]&lt;/span&gt;
&lt;span class="py"&gt;enable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"http://192.168.1.10:8686"&lt;/span&gt;
&lt;span class="py"&gt;timeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="py"&gt;fallback&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this enabled, &lt;code&gt;install&lt;/code&gt;, &lt;code&gt;download&lt;/code&gt;, and &lt;code&gt;sdk install&lt;/code&gt; try the LAN cache first and fall back to the original source on a miss. The mirror is only a download optimization, not a trust root. Existing checksum validation still runs later in the flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  More features are in the GitHub docs
&lt;/h2&gt;

&lt;p&gt;This article covers the main path. &lt;code&gt;eget&lt;/code&gt; also has a set of practical features that are better documented in the &lt;a href="https://github.com/inherelab/eget/blob/main/README.md" rel="noopener noreferrer"&gt;GitHub README&lt;/a&gt; and &lt;a href="https://github.com/inherelab/eget/blob/main/docs/config.md" rel="noopener noreferrer"&gt;configuration docs&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;concurrent downloads and batch install/update&lt;/li&gt;
&lt;li&gt;HTTP Range chunked downloads for large files&lt;/li&gt;
&lt;li&gt;resumable downloads with &lt;code&gt;.part&lt;/code&gt; files and metadata&lt;/li&gt;
&lt;li&gt;Windows GUI installer launch&lt;/li&gt;
&lt;li&gt;portable GUI package installation into &lt;code&gt;gui_target&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;separate API cache, download cache, and SDK index cache&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You may not need all of those every day. They become useful in CI, on shared development machines, on Windows tool distribution flows, and when large SDK archives are downloaded repeatedly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where eget fits
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;eget&lt;/code&gt; is a good fit when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you want to try a new GitHub release immediately&lt;/li&gt;
&lt;li&gt;a tool ships zip, tar.gz, exe, deb, or similar release assets&lt;/li&gt;
&lt;li&gt;internal tools follow a stable download URL rule&lt;/li&gt;
&lt;li&gt;CI machines, developer laptops, or LAN machines need the same tools and SDKs repeatedly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is not a replacement for a system package manager. Dependency solving, service registration, and complex post-install scripts are not its job.&lt;/p&gt;

&lt;p&gt;The shorter description is this: &lt;code&gt;eget&lt;/code&gt; is a release asset downloader and installer. If GitHub already has the release, a download site already has the file, or an internal directory already has the package, &lt;code&gt;eget&lt;/code&gt; helps get it onto the machine without waiting for the index in the middle.&lt;/p&gt;




&lt;p&gt;Originally published at &lt;a href="https://inhere.github.io/en/blog/2026/eget-intro/" rel="noopener noreferrer"&gt;https://inhere.github.io/en/blog/2026/eget-intro/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>github</category>
      <category>opensource</category>
      <category>tools</category>
      <category>go</category>
    </item>
    <item>
      <title>gookit/gcli v3.5.0 released - easy-to-use, feature-rich Go command line application and tool library</title>
      <dc:creator>Inhere</dc:creator>
      <pubDate>Tue, 23 Jun 2026 14:46:09 +0000</pubDate>
      <link>https://dev.to/inhere/gookitgcli-v350-released-easy-to-use-feature-rich-go-command-line-application-and-tool-library-4jkn</link>
      <guid>https://dev.to/inhere/gookitgcli-v350-released-easy-to-use-feature-rich-go-command-line-application-and-tool-library-4jkn</guid>
      <description>&lt;h2&gt;
  
  
  GCli v3.5 Updates: Changes Since v3.3.1
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/gookit/gcli" rel="noopener noreferrer"&gt;GCli&lt;/a&gt; is a command-line application and tool library for Go.&lt;br&gt;
This post covers the main changes from &lt;code&gt;v3.3.1&lt;/code&gt; to the recently released &lt;code&gt;v3.5&lt;/code&gt; (including the v3.4 cycle). These updates focus on developer experience and underlying stability.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you write CLI tools in Go, a few of these features might be useful. Here are the main changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Updates
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shell completion&lt;/strong&gt;: Supports zero-registration generation and a dynamic completion mode (bash / zsh / PowerShell).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Command middleware&lt;/strong&gt;: Handle auth, logging, and other cross-cutting concerns via &lt;code&gt;Command.Use()&lt;/code&gt; / &lt;code&gt;App.Use()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grouped help&lt;/strong&gt;: Categorize commands and options into titled sections using &lt;code&gt;Category&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Struct binding improvements&lt;/strong&gt;: Added a &lt;code&gt;field&lt;/code&gt; tag rule and support for expanding anonymous nested structs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interactive input&lt;/strong&gt;: Automatically prompt for missing values via &lt;code&gt;Question&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;POSIX short-option merging&lt;/strong&gt;: Support for &lt;code&gt;-aux&lt;/code&gt; splitting into &lt;code&gt;-a -u -x&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Robustness fixes&lt;/strong&gt;: Panic handling, &lt;code&gt;help&lt;/code&gt; command behavior, and more.&lt;/li&gt;
&lt;li&gt;A few breaking changes (migration guide at the end).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  1. Shell Completion Improvements
&lt;/h2&gt;

&lt;p&gt;Generating shell completion scripts previously required hardcoding command and option names, meaning adding a new command required regenerating the script. GCli v3.5 improves this workflow.&lt;/p&gt;

&lt;p&gt;You no longer need to manually register the &lt;code&gt;genac&lt;/code&gt; command. A built-in global option generates the static script directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# generate a completion script for your shell, then source it&lt;/span&gt;
myapp &lt;span class="nt"&gt;--gen-completion&lt;/span&gt; bash &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; myapp.bash
&lt;span class="nb"&gt;source &lt;/span&gt;myapp.bash

&lt;span class="c"&gt;# zsh / PowerShell also supported&lt;/span&gt;
myapp &lt;span class="nt"&gt;--gen-completion&lt;/span&gt; zsh  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; _myapp
myapp &lt;span class="nt"&gt;--gen-completion&lt;/span&gt; pwsh &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; myapp.ps1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Additionally, a &lt;strong&gt;dynamic completion&lt;/strong&gt; mode is now available. The generated script no longer hardcodes names; instead, it calls back into your binary via the built-in &lt;code&gt;--in-completion&lt;/code&gt; option to fetch candidates at completion time. New commands will immediately work with Tab-completion without regeneration.&lt;/p&gt;

&lt;p&gt;For option values, you can define a list of candidates using &lt;code&gt;Choices&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StrOpt2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"format"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"output format"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;gflag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithChoices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"yaml"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"table"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c"&gt;// typing `--format &amp;lt;Tab&amp;gt;` now suggests: json  yaml  table&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Command Middleware
&lt;/h2&gt;

&lt;p&gt;If you need to run auth checks or logging before a command's main logic without duplicating code across commands, you can use middleware.&lt;/p&gt;

&lt;p&gt;Handlers registered with &lt;code&gt;Use()&lt;/code&gt; run in order before the command's main &lt;code&gt;Func&lt;/code&gt;. If a handler returns an error, the chain stops, and the error propagates upward.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// command-level middleware&lt;/span&gt;
&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gcli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"TOKEN"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewErrf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"missing TOKEN env"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="c"&gt;// return nil to continue the chain&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c"&gt;// application-level middleware: applies before every command&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gcli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;gcli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Debugf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"running command: %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both &lt;code&gt;Command.Use()&lt;/code&gt; and &lt;code&gt;App.Use()&lt;/code&gt; return the receiver, supporting chaining. Apps without middleware behave exactly as before.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Grouped Help
&lt;/h2&gt;

&lt;p&gt;When an app has many commands and options, the help output can become cluttered. You can now group them into titled sections using the &lt;code&gt;Category&lt;/code&gt; field.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// group commands&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;gcli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"migrate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Desc&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"run db migrate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"database"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;gcli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"serve"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="n"&gt;Desc&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"start http server"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="c"&gt;// default group&lt;/span&gt;

&lt;span class="c"&gt;// group options&lt;/span&gt;
&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StrVar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;dsn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;gcli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CliOpt&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"db-dsn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Desc&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"database dsn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"database"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StrOpt2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"bind port"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gflag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithCategory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"network"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Groups appear in the order of their first definition, and items within a group are sorted by name. If no category is set, the output format remains the same as in older versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. More Flexible Struct Binding
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;FromStruct&lt;/code&gt; now supports a third tag rule (&lt;code&gt;TagRuleField&lt;/code&gt;) and automatically expands anonymous nested structs.&lt;/p&gt;

&lt;p&gt;The three available rules, selected via &lt;code&gt;c.FromStruct(ptr, ruleType)&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;gcli.TagRuleNamed&lt;/code&gt; (default): &lt;code&gt;flag:"name=int0;shorts=i;required=true;desc=message"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gcli.TagRuleSimple&lt;/code&gt;: &lt;code&gt;flag:"desc;required;default;shorts"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gcli.TagRuleField&lt;/code&gt; (new): Uses the field name (SnakeCase) as the option name and reads metadata from independent tag keys.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;commonOpts&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Verbose&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="s"&gt;`flag:"v" desc:"enable verbose output"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;demoOpts&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;commonOpts&lt;/span&gt;        &lt;span class="c"&gt;// anonymous: expands into a --verbose/-v option&lt;/span&gt;
    &lt;span class="n"&gt;UserName&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`flag:"u" desc:"the user name" required:"true"`&lt;/span&gt;
    &lt;span class="n"&gt;Age&lt;/span&gt;      &lt;span class="kt"&gt;int&lt;/span&gt;    &lt;span class="s"&gt;`desc:"the user age" default:"18"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustFromStruct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;demoOpts&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;gcli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TagRuleField&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;// =&amp;gt; options: --user-name/-u (required), --age (default 18), --verbose/-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;field&lt;/code&gt; rule keeps the option name tied to the struct field, while &lt;code&gt;desc&lt;/code&gt;, &lt;code&gt;default&lt;/code&gt;, and &lt;code&gt;required&lt;/code&gt; live in their own tags, making it easier to read and maintain.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Declarative Interactive Input
&lt;/h2&gt;

&lt;p&gt;If a required option is missing at runtime, you can now attach a &lt;code&gt;Question&lt;/code&gt;. GCli will detect the empty value and prompt the user for input interactively.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StrOpt2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"the access token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;gflag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithQuestion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Please input your access token: "&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ myapp deploy
Please input your access token: ▮
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a custom &lt;code&gt;Collector&lt;/code&gt; is also set, it takes priority over &lt;code&gt;Question&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. POSIX Short-Option Merging
&lt;/h2&gt;

&lt;p&gt;GCli now supports merging short flags (e.g., &lt;code&gt;-a -u -x&lt;/code&gt; becomes &lt;code&gt;-aux&lt;/code&gt;) in a POSIX style. This is disabled by default and can be enabled via &lt;code&gt;Config.EnhanceShort&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ParserCfg&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EnhanceShort&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gcli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EnhanceShortMerge&lt;/span&gt;  &lt;span class="c"&gt;// 1: -aux =&amp;gt; -a -u -x&lt;/span&gt;
&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ParserCfg&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EnhanceShort&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gcli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EnhanceShortAttach&lt;/span&gt; &lt;span class="c"&gt;// 2: also -Ostdout =&amp;gt; -O stdout&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It can also be enabled globally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;gcli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetEnhanceShort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gcli&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EnhanceShortMerge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;Constant&lt;/th&gt;
&lt;th&gt;Behavior&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;code&gt;EnhanceShortNone&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Off (default), fully compatible with old behavior&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;EnhanceShortMerge&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Split a group only when all members are bool shorts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;EnhanceShortAttach&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Also support value-attached form &lt;code&gt;-Ostdout&lt;/code&gt; = &lt;code&gt;-O stdout&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A safety check is in place: a group is only split if every character is a boolean short option. Mixed forms like &lt;code&gt;-aO&lt;/code&gt; (where &lt;code&gt;O&lt;/code&gt; takes a value) are left untouched to prevent misparsing.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Robustness Fixes
&lt;/h2&gt;

&lt;p&gt;Alongside new features, several long-standing issues were fixed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Panics are no longer swallowed&lt;/strong&gt;: &lt;code&gt;gflag.Parser.Parse&lt;/code&gt; previously ignored recovered panics. It now returns them as an error for easier upstream handling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;help &amp;lt;command&amp;gt;&lt;/code&gt; works on the first call&lt;/strong&gt;: Fixed an issue where it might print &lt;code&gt;unknown input command "help"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;findSimilarCmd&lt;/code&gt; fix&lt;/strong&gt;: No longer writes a phantom &lt;code&gt;help&lt;/code&gt; entry into the registry when an unknown command is run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Command.Copy()&lt;/code&gt; fix&lt;/strong&gt;: No longer clears the source command's hooks due to a shared pointer.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Breaking Changes &amp;amp; Migration
&lt;/h2&gt;

&lt;p&gt;A few internal cleanups require adjustments if you depended on them:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;import ".../gcli/v3/helper"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Now internal; inline your own helper&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;import ".../gcli/v3/gclicom"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Removed (unused after cliui migration)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Global &lt;code&gt;--verbose 4&lt;/code&gt; flag&lt;/td&gt;
&lt;td&gt;Env &lt;code&gt;GCLI_VERBOSE=debug&lt;/code&gt;, or &lt;code&gt;gcli.SetVerbose(gcli.VerbDebug)&lt;/code&gt; / &lt;code&gt;gcli.SetDebugMode()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;--verbose&lt;/code&gt; flag was removed because it bound to a per-app copy that the underlying logger never read, making it ineffective. Use the environment variable or code to control log levels.&lt;/p&gt;

&lt;p&gt;Additionally, multiple &lt;code&gt;App&lt;/code&gt; instances within the same process now share global options (verbose / help / version / strict / completion).&lt;/p&gt;

&lt;h2&gt;
  
  
  Upgrade &amp;amp; Examples
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go get &lt;span class="nt"&gt;-u&lt;/span&gt; github.com/gookit/gcli/v3@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;_examples/cmd&lt;/code&gt; directory in the repository includes runnable examples: &lt;code&gt;struct-flag&lt;/code&gt; (field tags + anonymous structs), &lt;code&gt;short-merge&lt;/code&gt; (short option merging), and &lt;code&gt;ask-demo&lt;/code&gt; (interactive input).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you run into issues or have suggestions, feel free to open an issue or PR on &lt;a href="https://github.com/gookit/gcli" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. For full API documentation, refer to &lt;a href="https://pkg.go.dev/github.com/gookit/gcli/v3" rel="noopener noreferrer"&gt;GoDoc&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>programming</category>
      <category>go</category>
      <category>opensource</category>
      <category>cli</category>
    </item>
    <item>
      <title>gookit/goutil v0.7.2 Released: Enhanced Features &amp; Fixes for an Improved Development Experience</title>
      <dc:creator>Inhere</dc:creator>
      <pubDate>Thu, 20 Nov 2025 14:36:14 +0000</pubDate>
      <link>https://dev.to/inhere/gookitgoutil-v072-released-enhanced-features-fixes-for-an-improved-development-experience-3d06</link>
      <guid>https://dev.to/inhere/gookitgoutil-v072-released-enhanced-features-fixes-for-an-improved-development-experience-3d06</guid>
      <description>&lt;p&gt;We're excited to announce the release of &lt;code&gt;gookit/goutil&lt;/code&gt; v0.7.2! This version brings numerous new features, optimizations, and bug fixes, further enhancing the stability and usability of this Go utility library. &lt;br&gt;
This update covers multiple modules, including math utilities, string processing, filesystem operations, and command-line flag handling, aiming to provide developers with a richer and more efficient toolkit.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Project Homepage: &lt;a href="https://github.com/gookit/goutil" rel="noopener noreferrer"&gt;https://github.com/gookit/goutil&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  Full Changelog: &lt;a href="https://github.com/gookit/goutil/releases/tag/v0.7.2" rel="noopener noreferrer"&gt;v0.7.2 Release Notes&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  Full documentation: &lt;a href="https://zread.ai/gookit/goutil" rel="noopener noreferrer"&gt;https://zread.ai/gookit/goutil&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  🛠️ Key Bug Fixes
&lt;/h2&gt;

&lt;p&gt;This version fixes several key issues to ensure the stable operation of the library:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;ccolor module&lt;/strong&gt;: Fixed the issue where the &lt;code&gt;NO_COLOR&lt;/code&gt; environment variable was not working correctly during printing, improving the compatibility of color output.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;cflag module&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;  Fixed a &lt;code&gt;capp&lt;/code&gt; runtime error that occurred when parsing global flags.&lt;/li&gt;
&lt;li&gt;  Fixed the issue where the &lt;code&gt;OnAppFlagParsed&lt;/code&gt; event was not triggered when no command was input, ensuring the integrity of the event handling logic.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  ✨ New Feature Highlights
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Math Utilities
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  Added the &lt;code&gt;IsInteger&lt;/code&gt; function for &lt;strong&gt;strictly checking if a value is of an integer type&lt;/strong&gt; (e.g., &lt;code&gt;int(x)&lt;/code&gt;, &lt;code&gt;uint(x)&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;  Added &lt;code&gt;StrictInt&lt;/code&gt; and &lt;code&gt;StrictUint&lt;/code&gt; conversion functions for stricter integer type conversions.&lt;/li&gt;
&lt;li&gt;  Added the &lt;code&gt;FormatBytes&lt;/code&gt; function to format byte sizes into &lt;strong&gt;human-readable strings&lt;/strong&gt; (e.g., "1.2 MB"), convenient for log output and UI display.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  String Utilities
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  Added the &lt;code&gt;NumVersion&lt;/code&gt; function to quickly extract the numeric part from a version string.&lt;/li&gt;
&lt;li&gt;  Added the &lt;code&gt;ReplaceVars&lt;/code&gt; function to support quick rendering of strings containing &lt;code&gt;{var}&lt;/code&gt; placeholders, simplifying template replacement operations.&lt;/li&gt;
&lt;li&gt;  Added &lt;code&gt;IsUpper&lt;/code&gt; and &lt;code&gt;IsLower&lt;/code&gt; functions to check if a string is entirely uppercase or lowercase.&lt;/li&gt;
&lt;li&gt;  Added &lt;code&gt;IsUint&lt;/code&gt; and &lt;code&gt;IsPositiveNum&lt;/code&gt; functions to enhance string numeric validation capabilities.&lt;/li&gt;
&lt;li&gt;  Added &lt;code&gt;BaseConvInt&lt;/code&gt; and &lt;code&gt;BaseConvIntByTpl&lt;/code&gt; functions to support integer conversion between different bases.&lt;/li&gt;
&lt;li&gt;  Added the &lt;code&gt;ContainsByteOne&lt;/code&gt; function to check if a string contains any of the specified bytes.&lt;/li&gt;
&lt;li&gt;  Optimized the &lt;code&gt;MTimeBaseID&lt;/code&gt; function, which now supports bases greater than 36.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Filesystem Utilities
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  Added the &lt;code&gt;EnsureDir&lt;/code&gt; function to ensure a directory exists (creates it if it doesn't).&lt;/li&gt;
&lt;li&gt;  Added &lt;code&gt;HomeDir&lt;/code&gt;/&lt;code&gt;UserHomeDir&lt;/code&gt; functions to get the user's home directory path.&lt;/li&gt;
&lt;li&gt;  Added &lt;code&gt;CreateSymlink&lt;/code&gt; and &lt;code&gt;IsSymlink&lt;/code&gt; functions to support the creation and detection of symbolic links.&lt;/li&gt;
&lt;li&gt;  Added &lt;code&gt;FindAllInParentDirs&lt;/code&gt;, &lt;code&gt;FindOneInParentDirs&lt;/code&gt;, and &lt;code&gt;FindNameInParentDirs&lt;/code&gt; functions to support &lt;strong&gt;finding files in parent directories&lt;/strong&gt;, simplifying the location of project configuration files.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Array/Slice Utilities
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  Added the &lt;code&gt;ToMap&lt;/code&gt; function to convert a list (slice) to a map.&lt;/li&gt;
&lt;li&gt;  Added the &lt;code&gt;Map1&lt;/code&gt; function to convert a list to a new list (similar to a map operation).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Map Utilities
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  Added the &lt;code&gt;AppendSMap&lt;/code&gt; function for merging string maps.&lt;/li&gt;
&lt;li&gt;  Added the &lt;code&gt;AliasesNames&lt;/code&gt; function for handling alias names.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Testing Utilities
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  Added the &lt;code&gt;assert.StrContainsAll&lt;/code&gt; assertion function to check if a string contains all specified substrings.&lt;/li&gt;
&lt;li&gt;  Added the &lt;code&gt;testutil.SafeBuffer&lt;/code&gt; type, a &lt;strong&gt;thread-safe buffer&lt;/strong&gt; designed for concurrent testing scenarios.&lt;/li&gt;
&lt;li&gt;  Enhanced &lt;code&gt;testutil.EchoServer&lt;/code&gt;, which now supports responding with 404, 405, 500, or custom status codes, facilitating the testing of HTTP client error handling logic.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Command-line Utilities
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  The &lt;code&gt;cflag&lt;/code&gt; module has a new &lt;code&gt;HelpOnEmptyArgs&lt;/code&gt; configuration option to automatically display help information when no arguments are provided, improving user experience.&lt;/li&gt;
&lt;li&gt;  Optimized the rendering of help information.&lt;/li&gt;
&lt;li&gt;  Added support for setting aliases for &lt;code&gt;cflag/capp&lt;/code&gt; commands.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Time Utilities
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  Added the &lt;code&gt;FormatDuration&lt;/code&gt; function to format time duration into a &lt;strong&gt;clock-like format&lt;/strong&gt; (e.g., "1h 23m 45s"), perfect for displaying performance statistics.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  ⚡ Performance Optimizations &amp;amp; Refactoring
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  Optimized the common logic of the &lt;code&gt;ToStringWith&lt;/code&gt; function to improve conversion performance.&lt;/li&gt;
&lt;li&gt;  Refactored &lt;code&gt;cflag.App&lt;/code&gt; into the subpackage &lt;code&gt;capp.App&lt;/code&gt;, improving code organization.&lt;/li&gt;
&lt;li&gt;  Optimized the handling options logic in the &lt;code&gt;ToInt64With&lt;/code&gt; function to enhance conversion efficiency.&lt;/li&gt;
&lt;li&gt;  Split integer conversion related code into &lt;code&gt;conv2int.go&lt;/code&gt; and optimized the performance of all integer conversion related methods in &lt;code&gt;mathutil&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  Updated the &lt;code&gt;VersionCompare&lt;/code&gt; function logic, fixing some comparison errors and improving the accuracy of version comparison.&lt;/li&gt;
&lt;li&gt;  The &lt;code&gt;VarReplacer&lt;/code&gt; in &lt;code&gt;textutil&lt;/code&gt; now supports parsing variable names as environment variables, enhancing dynamic configuration capabilities.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  🌐 Platform Compatibility &amp;amp; Development Workflow
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Added support for FreeBSD/Unix systems&lt;/strong&gt;, expanding the platform coverage of the &lt;code&gt;sysutil&lt;/code&gt; package.&lt;/li&gt;
&lt;li&gt;  Added comprehensive &lt;strong&gt;GitHub Copilot instructions&lt;/strong&gt; for gookit/goutil, optimizing the AI-assisted development workflow.&lt;/li&gt;
&lt;li&gt;  Upgraded the &lt;code&gt;github/codeql-action&lt;/code&gt; dependency from version 3 to 4, enhancing the security and capabilities of code analysis.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  📝 Other Updates
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  Added a &lt;code&gt;ShowLen&lt;/code&gt; option for &lt;code&gt;dump&lt;/code&gt; output, allowing control over whether to display length information.&lt;/li&gt;
&lt;li&gt;  Updated the README generation CLI and related documentation, improving the project documentation generation process and templates.&lt;/li&gt;
&lt;li&gt;  Fixed unit test errors and unified the code style.&lt;/li&gt;
&lt;li&gt;  Updated some comments and code styles.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  📦 How to Update
&lt;/h2&gt;

&lt;p&gt;You can use the following command to get the latest version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go get github.com/gookit/goutil@v0.7.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Migration &amp;amp; Compatibility Notes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;cflag -&amp;gt; cflag/capp&lt;/code&gt; Subpackage Refactoring: &lt;code&gt;cflag.App&lt;/code&gt; has been refactored to &lt;code&gt;capp.App&lt;/code&gt;. If your code directly references &lt;code&gt;cflag.App&lt;/code&gt;, please adjust:

&lt;ul&gt;
&lt;li&gt;  Old: &lt;code&gt;import "github.com/gookit/goutil/cflag"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  New: &lt;code&gt;import "github.com/gookit/goutil/capp"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  And switch related types/calls to the App in the &lt;code&gt;capp&lt;/code&gt; package.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;TIP: This is a potential breaking change in this release, please check and update your references before upgrading.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;VersionCompare&lt;/code&gt; and some string/version-related logic have been optimized. If you rely on the edge-case behavior of the old logic (such as equality comparison or sorting), please run relevant unit tests to verify after upgrading.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Example Snippets
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Using &lt;code&gt;mathutil.IsInteger / StrictInt&lt;/code&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"github.com/gookit/goutil/mathutil"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"github.com/gookit/goutil/testutil/assert"&lt;/span&gt;

&lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;123&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;mathutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsInteger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;i64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;mathutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StrictInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"42"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Does not allow string to integer conversion&lt;/span&gt;
&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;False&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Using &lt;code&gt;strutil.ReplaceVars&lt;/code&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"github.com/gookit/goutil/strutil"&lt;/span&gt;

&lt;span class="n"&gt;tpl&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"Hello, {name}! Today is {day}."&lt;/span&gt;
&lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;strutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReplaceVars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tpl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Gopher"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"day"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;"Wednesday"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="c"&gt;// Output: "Hello, Gopher! Today is Wednesday."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Using &lt;code&gt;fsutil.EnsureDir&lt;/code&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"github.com/gookit/goutil/fsutil"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;fsutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EnsureDir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/tmp/myapp/logs"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Handle error&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🤝 Contributing
&lt;/h2&gt;

&lt;p&gt;We thank all contributors for their efforts in this release! If you find any issues or have feature suggestions, please feel free to open an issue at &lt;a href="https://github.com/gookit/goutil/issues" rel="noopener noreferrer"&gt;GitHub Issues&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  📖 More Information
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  Project Homepage: &lt;a href="https://github.com/gookit/goutil" rel="noopener noreferrer"&gt;https://github.com/gookit/goutil&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  Full Changelog: &lt;a href="https://github.com/gookit/goutil/releases/tag/v0.7.2" rel="noopener noreferrer"&gt;v0.7.2 Release Notes&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  Full documentation: &lt;a href="https://zread.ai/gookit/goutil" rel="noopener noreferrer"&gt;https://zread.ai/gookit/goutil&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Enjoy the new features and improvements in v0.7.2&lt;/p&gt;

</description>
      <category>programming</category>
      <category>github</category>
      <category>development</category>
      <category>backend</category>
    </item>
    <item>
      <title>gookit/slog - release v0.5.8, Lightweight, configurable, extensible logging library</title>
      <dc:creator>Inhere</dc:creator>
      <pubDate>Thu, 01 May 2025 02:01:37 +0000</pubDate>
      <link>https://dev.to/inhere/gookitslog-release-v058-lightweight-configurable-extensible-logging-library-16ch</link>
      <guid>https://dev.to/inhere/gookitslog-release-v058-lightweight-configurable-extensible-logging-library-16ch</guid>
      <description>&lt;p&gt;We’re thrilled to announce the release of &lt;strong&gt;gookit/slog v0.5.8&lt;/strong&gt;, a significant update to the lightweight, configurable, and extensible logging library for Go. With this release, we’ve addressed critical bug fixes, introduced powerful new features, and refined the overall functionality to offer an even more reliable logging experience for developers.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Github: &lt;a href="https://github.com/gookit/slog" rel="noopener noreferrer"&gt;https://github.com/gookit/slog&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Highlights of v0.5.8
&lt;/h4&gt;

&lt;h5&gt;
  
  
  🛠️ Bug Fixes
&lt;/h5&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Windows File Path Parsing Issue Resolved&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Fixed a bug where log file paths could encounter parsing errors on Windows systems. (&lt;a href="https://github.com/gookit/slog/commit/9e6939b11743dfd62c660227f626ccdf07615047" rel="noopener noreferrer"&gt;Commit&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Backup File Handling Improvement&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Resolved a problem where setting &lt;code&gt;BackupNum=0&lt;/code&gt; caused all old files to be removed.  (&lt;a href="https://github.com/gookit/slog/commit/183fb6af53baaa777dd3fd7395f765037af68056" rel="noopener noreferrer"&gt;Commit&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;File Rotation Stability&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Fixed issues related to file rotation in short intervals, which previously caused conflicts and content overwrites.  (&lt;a href="https://github.com/gookit/slog/commit/e8e8d6f4bd548efee13c488e71b759563821fcc0" rel="noopener noreferrer"&gt;Commit&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Old File Matching Issue&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Resolved incorrect handling of file renaming (e.g., &lt;code&gt;error.log&lt;/code&gt; to &lt;code&gt;error.20250423_02.log&lt;/code&gt;). (&lt;a href="https://github.com/gookit/slog/commit/3d44bd2ef22c67e4d344865e75943e713a421c9e" rel="noopener noreferrer"&gt;Commit&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h5&gt;
  
  
  ✨ New Features
&lt;/h5&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Enhanced Logging Configuration&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Improved the configuration functions, making it easier to fine-tune logging behavior. (&lt;a href="https://github.com/gookit/slog/commit/ce6b6828b34d5ec9c607bf4615460a9a469bfa6a" rel="noopener noreferrer"&gt;Commit&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Top-Level &lt;code&gt;Log&lt;/code&gt; Function&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Introduced a &lt;code&gt;Log&lt;/code&gt; function at the package's top level for more streamlined usage.  (&lt;a href="https://github.com/gookit/slog/commit/581d3ba8955fe0f1eeb26bb97e14686c4be95981" rel="noopener noreferrer"&gt;Commit&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h5&gt;
  
  
  🔄 Updates and Improvements
&lt;/h5&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;WithRotateTimeString&lt;/code&gt; Function Added&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
A new function to handle rotation time strings, along with corresponding tests. (&lt;a href="https://github.com/gookit/slog/commit/07105c60483cc1e9b758247e6f214c8976b3f2f5" rel="noopener noreferrer"&gt;Commit&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Refactored Clean Method&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The clean method in the &lt;code&gt;rotatefile&lt;/code&gt; handler now includes additional tests for compressing and cleaning files.(&lt;a href="https://github.com/gookit/slog/commit/5caffda6c8dc33ed97f91e4274b96e878be8191c" rel="noopener noreferrer"&gt;Commit&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Improved Debugging Logs&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Added a debug mode environment variable and more detailed log outputs for development purposes. (&lt;a href="https://github.com/gookit/slog/commit/ee1d5848b585783ca9a14e0b6de8cd1bb2d152d6" rel="noopener noreferrer"&gt;Commit&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Optimized Rotate File Logic&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Refactored the logic to ensure file extensions are always at the end and reduced lock ranges during write and rotation processes. (&lt;a href="https://github.com/gookit/slog/commit/afd01cc28cbaa8ce5ce7ea1e4346cf21079c3f74" rel="noopener noreferrer"&gt;Commit&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h5&gt;
  
  
  📦 Dependency Updates
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;Updated &lt;code&gt;github.com/gookit/goutil&lt;/code&gt; to v0.6.18.
(&lt;a href="https://github.com/gookit/slog/pull/153" rel="noopener noreferrer"&gt;PR #153&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Updated &lt;code&gt;github.com/golang/glog&lt;/code&gt; from v1.1.1 to v1.2.4 for examples.
(&lt;a href="https://github.com/gookit/slog/pull/157" rel="noopener noreferrer"&gt;PR #157&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  🧪 Improved Test Coverage
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;Added more unit test cases and internal test functions to ensure the stability and reliability of new features.  (&lt;a href="https://github.com/gookit/slog/commit/5b64610339e53fe5dc59a8c9b0fa72bca59a523c" rel="noopener noreferrer"&gt;Commit&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h4&gt;
  
  
  Acknowledging New Contributors
&lt;/h4&gt;

&lt;p&gt;We’re excited to welcome two new contributors to the gookit/slog community:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;@ShyunnY&lt;/strong&gt; for their work on the &lt;code&gt;CallerFlagMode&lt;/code&gt; type alias. (&lt;a href="https://github.com/gookit/slog/pull/155" rel="noopener noreferrer"&gt;PR #155&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;@knbr13&lt;/strong&gt; for updating the code to use &lt;code&gt;os.ReadFile&lt;/code&gt; instead of the deprecated &lt;code&gt;ioutil.ReadFile&lt;/code&gt;. (&lt;a href="https://github.com/gookit/slog/pull/158" rel="noopener noreferrer"&gt;PR #158&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h4&gt;
  
  
  Full Changelog
&lt;/h4&gt;

&lt;p&gt;To explore all changes in detail, check out the &lt;a href="https://github.com/gookit/slog/compare/v0.5.7...v0.5.8" rel="noopener noreferrer"&gt;v0.5.8 changelog&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;This release represents a significant step forward in making &lt;code&gt;gookit/slog&lt;/code&gt; a more robust, flexible, and user-friendly logging solution for Go developers. We’re grateful for the contributions from the community and look forward to seeing how these changes will enhance your projects. &lt;/p&gt;

&lt;p&gt;Happy Logging!&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>programming</category>
      <category>go</category>
      <category>github</category>
    </item>
    <item>
      <title>gookit/goutil - released v0.6.10, an extension library of common Go features</title>
      <dc:creator>Inhere</dc:creator>
      <pubDate>Wed, 05 Jul 2023 03:44:25 +0000</pubDate>
      <link>https://dev.to/inhere/gookitgoutil-released-v0610-an-extension-library-of-common-go-features-16ho</link>
      <guid>https://dev.to/inhere/gookitgoutil-released-v0610-an-extension-library-of-common-go-features-16ho</guid>
      <description>&lt;p&gt;&lt;code&gt;gookit/goutil&lt;/code&gt; An extended library of Go's common functionality. Contains: number, string, slice/array, Map, struct, reflection, text, file, error, time and date, test, CLI, command run, system information, formatting, common information acquisition, etc.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Github&lt;/strong&gt;: &lt;a href="https://github.com/gookit/goutil"&gt;https://github.com/gookit/goutil&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  v0.6.10 change log
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Full changelog &lt;a href="https://github.com/gookit/goutil/compare/v0.6.9...v0.6.10"&gt;v0.6.9...v0.6.10&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  ✨ New features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;✨ feat: testutil - adds a new child package &lt;code&gt;fakeobj&lt;/code&gt; to create fake objects for testing&lt;/li&gt;
&lt;li&gt;✨ feat: testutil - adds a new function NewEchoServer() to start the echo server&lt;/li&gt;
&lt;li&gt;✨ feat: byteutil - Buffer adds more useful methods for ease of use&lt;/li&gt;
&lt;li&gt;✨ feat: dump - Special handling of custom int, uint type values, will print instructions in String format&lt;/li&gt;
&lt;li&gt;✨ feat: fsutil - adds a new function to Glob() quickly list matching files&lt;/li&gt;
&lt;li&gt;♻️ feat: httpreq - Refactoring the built-in HTTP client httpreq logic&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  👔 Update Adjustments
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;👔 up: structs - Update and support InitDefaults() for struct slice fields that are not null&lt;/li&gt;
&lt;li&gt;👔 up: maputil - &lt;code&gt;SimpleMerge()&lt;/code&gt; Supports deep merging of map[string]any data&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Other adjustments
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;✅ test: Update some documentation and fix some unit tests&lt;/li&gt;
&lt;li&gt;⬆️ dep: Update golang.org/x depends on the latest version&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Some feature usage examples
&lt;/h3&gt;

&lt;h4&gt;
  
  
  dump - prints custom types
&lt;/h4&gt;

&lt;p&gt;dump - special handles custom int, uint type values, and will print instructions in String format&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fEQkMjPf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/gookit/goutil/v0.6.10/dump/_examples/custom_type.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fEQkMjPf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/gookit/goutil/v0.6.10/dump/_examples/custom_type.png" alt="dump custom_type.png" width="800" height="296"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Initialize the struct
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Support for initializing the use of environment variables&lt;/li&gt;
&lt;li&gt;Support initializing slice fields and nesting structs
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ExtraDefault&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;City&lt;/span&gt;   &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`default:"some where"`&lt;/span&gt;
    &lt;span class="n"&gt;Github&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`default:"${ GITHUB_ADDR }"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Name&lt;/span&gt;  &lt;span class="kt"&gt;string&lt;/span&gt;        &lt;span class="s"&gt;`default:"inhere"`&lt;/span&gt;
    &lt;span class="n"&gt;Age&lt;/span&gt;   &lt;span class="kt"&gt;int&lt;/span&gt;           &lt;span class="s"&gt;`default:"300"`&lt;/span&gt;
    &lt;span class="n"&gt;Extra&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ExtraDefault&lt;/span&gt; &lt;span class="s"&gt;`default:""`&lt;/span&gt; &lt;span class="c"&gt;// mark: need init&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;optFn&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opt&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;structs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InitOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;opt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ParseEnv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;structs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InitDefaults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;optFn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;goutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PanicErr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;dump&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Initialization result:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;structs_test&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"inhere"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;
  &lt;span class="n"&gt;Age&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;300&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;Extra&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;structs_test&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExtraDefault&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;City&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"some where"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;
    &lt;span class="n"&gt;Github&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://some .... url"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;21&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Test with Echo Server
&lt;/h4&gt;

&lt;p&gt;Use &lt;code&gt;testutil.NewEchoServer()&lt;/code&gt; to quickly create an HTTP echo server. Convenient for testing HTTP requests, responses, etc.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Usage examples:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;testSrvAddr&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestMain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;testutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewEchoServer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;testSrvAddr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"http://"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Listener&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Addr&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"server addr:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;testSrvAddr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestNewEchoServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// can direct request the echo server&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;testSrvAddr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"text/plain"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hello!"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NoErr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// binding response to the testutil.EchoReply&lt;/span&gt;
    &lt;span class="n"&gt;rr&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;testutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ParseRespToReply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;dump&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"text/plain"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContentType&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"hello!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  v0.6.9 change log
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Full changelog &lt;a href="https://github.com/gookit/goutil/compare/v0.6.8...v0.6.9"&gt;v0.6.8...v0.6.9&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  ✨ New features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;strutil - Added &lt;code&gt;ToByteSize(),SafeByteSize()&lt;/code&gt; for converting size string (eg: 5MB) to byte size&lt;/li&gt;
&lt;li&gt;strutil has added &lt;code&gt;ParseSizeRange()&lt;/code&gt; to facilitate and quickly parse string size range expressions eg: &lt;code&gt;200kb~50mb&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;strutil - Added new utility functions: &lt;code&gt;DatetimeNo(), RandWithTpl(), SimpleMatch()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;byteutil - Add new utility functions: &lt;code&gt;Random()&lt;/code&gt;, &lt;code&gt;AppendAny()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;fsutil - Add new utility functions: &lt;code&gt;ReadOrErr()&lt;/code&gt;, &lt;code&gt;ReadStringOrErr()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;cliutil - Add new utility functions: &lt;code&gt;Confirm()&lt;/code&gt;, &lt;code&gt;ReadAsBool()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;errorx - Add new utility functions &lt;code&gt;Err(), Errf()&lt;/code&gt; for easy creation of error&lt;/li&gt;
&lt;li&gt;structs - &lt;code&gt;InitDefaults()&lt;/code&gt; enhanced to support nested structure pointer initialization&lt;/li&gt;
&lt;li&gt;structs - &lt;code&gt;SetValues()&lt;/code&gt; enhanced to support setting and processing structure pointer values&lt;/li&gt;
&lt;li&gt;structs - Add &lt;code&gt;ToSMap(), TryToSMap(), TryToSMap()&lt;/code&gt; to convert structure to string map&lt;/li&gt;
&lt;li&gt;testutil/assert new assertion methods &lt;code&gt;NotContainsKey()&lt;/code&gt;, &lt;code&gt;NotContainsKeys()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Reflects new utility methods &lt;code&gt;UnexportedValue()&lt;/code&gt;, &lt;code&gt;SetUnexportedValue()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;maputil new tool functions &lt;code&gt;HasOneKey(), CombineToMap(), TryAnyMap()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Added utility functions &lt;code&gt;AnyToSlice(), CombineToMap()&lt;/code&gt; in arrutil&lt;/li&gt;
&lt;li&gt;jsonutil has added tool methods &lt;code&gt;IsJSON(), IsJSONFast()&lt;/code&gt; to check whether it is JSON&lt;/li&gt;
&lt;li&gt;♻️ fsutil/finder file and directory search reconstruction, enhanced functions, more convenient to use&lt;/li&gt;
&lt;li&gt;timex new utility functions &lt;code&gt;NowAddSec(), IsDuration(), InRange()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;timex &lt;code&gt;ToDuration()&lt;/code&gt; enhanced, support unit d, w and long unit hour, min, sec&lt;/li&gt;
&lt;li&gt;mathutil new tool methods &lt;code&gt;InRange()&lt;/code&gt;, &lt;code&gt;OutRange()&lt;/code&gt;, &lt;code&gt;InUintRange()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;fsutil new tool method &lt;code&gt;RemoveSub()&lt;/code&gt; to quickly delete subdirectories and files&lt;/li&gt;
&lt;li&gt;maputil &lt;code&gt;GetByPath()&lt;/code&gt; is enhanced to support matching paths similar to &lt;code&gt;top.*.field&lt;/code&gt; to get values&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  👔 Update adjustments
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;strutil optimization &lt;code&gt;ToByteSize()&lt;/code&gt; and &lt;code&gt;GlobMatch()&lt;/code&gt; logic&lt;/li&gt;
&lt;li&gt;struts - &lt;code&gt;InitDefaults()&lt;/code&gt; supports initializing non-zero ptr substructure fields&lt;/li&gt;
&lt;li&gt;💥 struts - &lt;code&gt;InitDefaults()&lt;/code&gt; To initialize a substructure, the field must be marked with &lt;code&gt;default:""&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Other adjustments
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;📝 doc: README documentation updated, some subpackages added README&lt;/li&gt;
&lt;li&gt;✅ test: Added and fixed some unit tests&lt;/li&gt;
&lt;li&gt;⬆️ dep: update golang.org/x dependencies to latest version&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Some feature usage examples
&lt;/h3&gt;

&lt;h4&gt;
  
  
  finder - find files or dir
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;fsutil/finder&lt;/code&gt; Provides a simple and fast way to find matching files and directories.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Built-in support Name, extension, suffix, prefix, glob, path, etc. include or exclude&lt;/li&gt;
&lt;li&gt;Support matching file size, modification time (ModTime) and other extended matching methods
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;ff&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;finder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewFinder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/path/to/dir/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="c"&gt;// OnlyFindDir(). // default find files&lt;/span&gt;
    &lt;span class="n"&gt;UseAbsPath&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;WithoutDotDir&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;WithDirName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"testdata"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;finder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HumanModTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;lt;10m"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="c"&gt;// latest 10 minutes, to Now&lt;/span&gt;
    &lt;span class="n"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;finder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HumanSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"500kb~10mb"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c"&gt;// limit file size&lt;/span&gt;

&lt;span class="c"&gt;// Find() returns chan, can be for handling&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;el&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Find&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;el&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  strutil.ParseSizeRange
&lt;/h4&gt;

&lt;p&gt;It is easy to parse the string size range to byte size.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;opt&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;strutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ParseSizeOpt&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="n"&gt;mix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;strutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ParseSizeRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1kb~1mb"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;goutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PanicErr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// OUTPUT: 1024, 1048576&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Examples of supported expression formats:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"1KB~2MB"       =&amp;gt; 1KB to 2MB
"-1KB"          =&amp;gt; &amp;lt;1KB
"~1MB"          =&amp;gt; &amp;lt;1MB
"&amp;lt; 1KB"         =&amp;gt; &amp;lt;1KB
"1KB"           =&amp;gt; &amp;gt;1KB
"1KB~"          =&amp;gt; &amp;gt;1KB
"&amp;gt;1KB"          =&amp;gt; &amp;gt;1KB
"+1KB"          =&amp;gt; &amp;gt;1KB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  timex.ParseRange()
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;timex.ParseRange()&lt;/code&gt; It is easy to quickly parse the relative time size range or keyword into &lt;code&gt;time.Time&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ParseRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"-1h~1h"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;goutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PanicErr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Examples of supported expression formats:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"-5h~-1h"           =&amp;gt; 5 hours ago to 1 hour ago
"1h~5h"             =&amp;gt; 1 hour after to 5 hours after
"-1h~1h"            =&amp;gt; 1 hour ago to 1 hour after
"-1h"               =&amp;gt; 1 hour ago to feature. eq "-1h~"
"-1h~0"             =&amp;gt; 1 hour ago to now.
"&amp;lt; -1h" OR "~-1h"   =&amp;gt; 1 hour ago.
"&amp;gt; 1h" OR "1h"      =&amp;gt; 1 hour after to feature

// keyword: now, today, yesterday, tomorrow
"today"          =&amp;gt; today start to today end
"yesterday"      =&amp;gt; yesterday start to yesterday end
"tomorrow"       =&amp;gt; tomorrow start to tomorrow end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  More usage
&lt;/h2&gt;

&lt;p&gt;For more instructions, see Unit tests for &lt;a href="https://github.com/gookit/slog/blob/master/README.zh-CN.md"&gt;README&lt;/a&gt; and related methods&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Github&lt;/strong&gt;: &lt;a href="https://github.com/gookit/goutil"&gt;https://github.com/gookit/goutil&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>programming</category>
      <category>opensource</category>
      <category>go</category>
      <category>util</category>
    </item>
    <item>
      <title>gookit/slog - release v0.5.2, Lightweight, configurable, extensible logging library</title>
      <dc:creator>Inhere</dc:creator>
      <pubDate>Tue, 20 Jun 2023 08:10:20 +0000</pubDate>
      <link>https://dev.to/inhere/gookitslog-release-v052-lightweight-configurable-extensible-logging-library-5d9k</link>
      <guid>https://dev.to/inhere/gookitslog-release-v052-lightweight-configurable-extensible-logging-library-5d9k</guid>
      <description>&lt;p&gt;&lt;code&gt;gookit/slog&lt;/code&gt; 📑 Lightweight, configurable, extensible logging library written in Go.&lt;br&gt;
Support multi level, multi outputs and built-in multi file logger, buffers, clean, rotate-file handling.&lt;/p&gt;



&lt;blockquote&gt;
&lt;p&gt;Github &lt;a href="https://github.com/gookit/slog"&gt;https://github.com/gookit/slog&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  v0.5.2 changelog
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Full changelog&lt;/strong&gt;: &lt;a href="https://github.com/gookit/slog/compare/v0.5.1...v0.5.2"&gt;https://github.com/gookit/slog/compare/v0.5.1...v0.5.2&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Feature
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;✨ feat: Add new option FilePerm to customize the permission on log file creation. issues #102 &lt;a href="https://github.com/gookit/slog/commit/699ecd8bc2dea0fb35607ba89d4299e86f96e40b"&gt;https://github.com/gookit/slog/commit/699ecd8bc2dea0fb35607ba89d4299e86f96e40b&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;✨ feat: rotatefile - Added new option &lt;code&gt;ModeCreate&lt;/code&gt; to support creating log files at rotation time. &lt;a href="https://github.com/gookit/slog/commit/8c4a9a08a9b3432a699f6770b73935ecfd42e5b8"&gt;https://github.com/gookit/slog/commit/8c4a9a08a9b3432a699f6770b73935ecfd42e5b8&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Update
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;👔 up: Update and add more unit tests, upgrade goutil to v0.6.10 &lt;a href="https://github.com/gookit/slog/commit/693cc04eb0e375183357be18893cbecaa171ffa1"&gt;https://github.com/gookit/slog/commit/693cc04eb0e375183357be18893cbecaa171ffa1&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📝 doc: Update README document description &lt;a href="https://github.com/gookit/slog/commit/cbf77c38db5615a9a7ec8ab07853b2f9e3517bfa"&gt;https://github.com/gookit/slog/commit/cbf77c38db5615a9a7ec8ab07853b2f9e3517bfa&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;✅ test: Add more unit test cases &lt;a href="https://github.com/gookit/slog/commit/8468ea470645bb981fb34002176671bac1a9559e"&gt;https://github.com/gookit/slog/commit/8468ea470645bb981fb34002176671bac1a9559e&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  New feature usage
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Custom log file permissions
&lt;/h3&gt;

&lt;p&gt;The log file permission flag created by configuring the &lt;code&gt;FilePerm&lt;/code&gt; setting of &lt;code&gt;hander.Config&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;h1&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustFileHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/tmp/error.log"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithLogLevels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DangerLevels&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithFilePerm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0644&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c"&gt;// &amp;lt;- sets log file permissions&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;slog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PushHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Split files using ModeCreate mode
&lt;/h3&gt;

&lt;p&gt;about &lt;code&gt;RotateMode&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ModeRename By default, every time rename handles rotation.&lt;/li&gt;
&lt;li&gt;ModeCreate Create files only by split time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Setting &lt;code&gt;RotateMode=ModeCreate&lt;/code&gt; allows log files to be created only by split time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;h1&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustRotateFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"/tmp/error.log"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;rotatefile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EveryHour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;// split by hour&lt;/span&gt;
    &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithLogLevels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DangerLevels&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithRotateMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rotatefile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModeCreate&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c"&gt;// set RotateMode=ModeCreate&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;slog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PushHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configure the logfile as &lt;code&gt;/tmp/error.log&lt;/code&gt; in the above example. When set to &lt;code&gt;ModeCreate&lt;/code&gt; mode, the file will not actually be created,&lt;br&gt;
Instead, it will be created according to the actual split time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/tmp/error.log.20230618_1500
/tmp/error.log.20230618_1600
/tmp/error.log.20230618_1700
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tips&lt;/strong&gt;: The &lt;code&gt;ModeCreate&lt;/code&gt; mode can be applied to command-line tool applications. Because they exit after each execution, &lt;code&gt;ModeRename&lt;/code&gt; may not be able to split files on time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And using &lt;code&gt;ModeRename&lt;/code&gt; mode will have the following effect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/tmp/error.log &lt;span class="c"&gt;# &amp;lt;- Logs are always written to this file&lt;/span&gt;
/tmp/error.log.20230618_1500
/tmp/error.log.20230618_1600
/tmp/error.log.20230618_1700
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  More usage
&lt;/h2&gt;

&lt;p&gt;More usage please see &lt;a href="https://github.com/gookit/slog/blob/master/README.md"&gt;README&lt;/a&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>go</category>
      <category>github</category>
      <category>tooling</category>
    </item>
    <item>
      <title>GO tool library goutil released v0.6.4</title>
      <dc:creator>Inhere</dc:creator>
      <pubDate>Sun, 12 Feb 2023 03:52:39 +0000</pubDate>
      <link>https://dev.to/inhere/go-tool-library-goutil-released-v064-56nj</link>
      <guid>https://dev.to/inhere/go-tool-library-goutil-released-v064-56nj</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/gookit/goutil" rel="noopener noreferrer"&gt;gookit/goutil&lt;/a&gt; - 💪 Helper Utils For Go: int, byte, string, array/slice, map, struct, dump, convert/format, error, web/http, cli/flag, OS/ENV, filesystem, system, test/assert, time and more. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;GitHub: &lt;a href="https://github.com/gookit/goutil" rel="noopener noreferrer"&gt;https://github.com/gookit/goutil&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Changlog from v0.6.2 to v0.6.4
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Fixed
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;🐛 fix(env, str): fix the envutil test error and update some str util &lt;a href="https://github.com/gookit/goutil/commit/2e7aa6329973423a76ea6b9b20a8b9150281dfb1" rel="noopener noreferrer"&gt;2e7aa63&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🐛 fix(dump): makeslice: cap out of range. fix: #76 &lt;a href="https://github.com/gookit/goutil/commit/4ba9b10e15a448eee1b8d2974835fc69407618ff" rel="noopener noreferrer"&gt;4ba9b10&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Feature
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;👔 feat: str - add some check util func &lt;a href="https://github.com/gookit/goutil/commit/0e4792acf21e1bf742f1af5882b74ff40c49b935" rel="noopener noreferrer"&gt;0e4792a&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;👔 feat: str/textutil - support quick replace vars for give text &lt;a href="https://github.com/gookit/goutil/commit/ecc23cb0cec4c518623b08c37f2e1fd948d6bea2" rel="noopener noreferrer"&gt;ecc23cb&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;👔 feat: sys,proc - add and update some util func &lt;a href="https://github.com/gookit/goutil/commit/aa78114b0da1017f6456957ba86cd24eeebe5fc7" rel="noopener noreferrer"&gt;aa78114&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;✨ feat: fs - add more new fs util functions &lt;a href="https://github.com/gookit/goutil/commit/3352512f6a133ae15a57944bc54362b604c7ff95" rel="noopener noreferrer"&gt;3352512&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;✨ feat: map, struct - add new util functions &lt;a href="https://github.com/gookit/goutil/commit/90af09e118b4a521c19341e6b8a49a0890e22e7f" rel="noopener noreferrer"&gt;90af09e&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;✨ feat: stdio - add new util functions &lt;a href="https://github.com/gookit/goutil/commit/ea243d62c89dc9ec7be9fd6a9b0bcdb39bfd506b" rel="noopener noreferrer"&gt;ea243d6&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;✨ feat: arr - add new util functions for in/notIn check &lt;a href="https://github.com/gookit/goutil/commit/2a18ca2915c93e53aea74e86779943f04bd1910a" rel="noopener noreferrer"&gt;2a18ca2&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;✨ feat: testutil - add new util func IsMatchAll() &lt;a href="https://github.com/gookit/goutil/commit/d0dade8703da172f80424ff8c85ec759ec8273ae" rel="noopener noreferrer"&gt;d0dade8&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;✨ feat: math - add new util func for compare value &lt;a href="https://github.com/gookit/goutil/commit/176923bb953622b995ed80017056cbe04db251cd" rel="noopener noreferrer"&gt;176923b&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;feat: error, test - add some new helper util fuctions &lt;a href="https://github.com/gookit/goutil/commit/718821631d8a7cc0567a2f39eece80f1b7c54157" rel="noopener noreferrer"&gt;7188216&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;✨ feat: fs - add more make dir util func &lt;a href="https://github.com/gookit/goutil/commit/c4447cb0d32cec53221c3bb99a14ecefc4ec382e" rel="noopener noreferrer"&gt;c4447cb&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;✨ feat: env,sys - add new func for get all env info map &lt;a href="https://github.com/gookit/goutil/commit/b3d7669d1a1c851d7f75e2a9ad449d74ca6c969c" rel="noopener noreferrer"&gt;b3d7669&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;✨ feat: str - add new func for check ignore case string &lt;a href="https://github.com/gookit/goutil/commit/746eb35d45a9c0d40fb9ae7945c9fe89bbc79765" rel="noopener noreferrer"&gt;746eb35&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;✨ feat: goutil - add new func for quick run go func &lt;a href="https://github.com/gookit/goutil/commit/97a2863089d70a2d39f5fa720f74890767293d0b" rel="noopener noreferrer"&gt;97a2863&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;✨ feat: arr - add new func for quick convert slice type &lt;a href="https://github.com/gookit/goutil/commit/75d8d89f4cb7e9c59587caeccfeb73202beb42b1" rel="noopener noreferrer"&gt;75d8d89&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;✨ feat: reflects - add new func for convert slice value and more util &lt;a href="https://github.com/gookit/goutil/commit/f87fc86119afd8418b47e93ad26b3925f78bef15" rel="noopener noreferrer"&gt;f87fc86&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;✨ feat: structs - func InitDefaults() support set slice field &lt;a href="https://github.com/gookit/goutil/commit/948fa703e7107edb4dc736aa041c260fc8df7d9b" rel="noopener noreferrer"&gt;948fa70&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;✨ feat: byte - add StrOrErr(), SafeString() for convert to string &lt;a href="https://github.com/gookit/goutil/commit/767ed80a5e98d20b9e832a08d065de301ba0b4ec" rel="noopener noreferrer"&gt;767ed80&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;✨ feat: cmdr - add new option for output result to OS &lt;a href="https://github.com/gookit/goutil/commit/6aaea78dee886b7aa5483d71826a34813d8cd77b" rel="noopener noreferrer"&gt;6aaea78&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Update
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;👔 up: str, math - add new util func OrElse() &lt;a href="https://github.com/gookit/goutil/commit/cb21fbc4624c800f9609137e053dfe2fef027190" rel="noopener noreferrer"&gt;cb21fbc&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;👔 up: str - update some util func and add more tests &lt;a href="https://github.com/gookit/goutil/commit/a3e07f4b5fbbb6131d42116a4e1c61c7614d7d69" rel="noopener noreferrer"&gt;a3e07f4&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🐛 up: textscan - fix multi inline comments parse error &lt;a href="https://github.com/gookit/goutil/commit/bf68c945ee79d7e214087ab117b55fa02b0f1c54" rel="noopener noreferrer"&gt;bf68c94&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;👔 up: str,math - update some util func &lt;a href="https://github.com/gookit/goutil/commit/eaf09908ead83f19d6a10882547e5cda1e42a79e" rel="noopener noreferrer"&gt;eaf0990&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;👔 up: str,byte - add and update some util func &lt;a href="https://github.com/gookit/goutil/commit/cf5d34e6b20bf45830fed19a3d3b6ac4682cdade" rel="noopener noreferrer"&gt;cf5d34e&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;👔 up: byte,cflag - update and add some new tests &lt;a href="https://github.com/gookit/goutil/commit/6e47f6911ed99225a1b5f6fdf6cfafe3b034e333" rel="noopener noreferrer"&gt;6e47f69&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;👔 up(env, str): update the env var parse and strutil.Valid() &lt;a href="https://github.com/gookit/goutil/commit/5193849498317900885f524fae7cf5777559f246" rel="noopener noreferrer"&gt;5193849&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;👔 up(str): update some str value get func &lt;a href="https://github.com/gookit/goutil/commit/676fe4e08e7313c593278d9e4f734225381e6be3" rel="noopener noreferrer"&gt;676fe4e&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;✨ up(env,sys): update some func and add new util func &lt;a href="https://github.com/gookit/goutil/commit/9eaddb5669280dc91c989e4930ecd1efbdef18cf" rel="noopener noreferrer"&gt;9eaddb5&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;👔 up(str): update the SplitInlineComment() logic &lt;a href="https://github.com/gookit/goutil/commit/31445cd999e5577b9e7e7f6d3e9efe3289fd7131" rel="noopener noreferrer"&gt;31445cd&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;👔 up(sys): update the SearchPath(), support limit result &lt;a href="https://github.com/gookit/goutil/commit/67cbae1d5db5c5ec09240917fae73c78e7d9e9d6" rel="noopener noreferrer"&gt;67cbae1&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;✅ up(sys,env,map): add more tests case for util func &lt;a href="https://github.com/gookit/goutil/commit/392536c6b91555d25cb5cbca8dc6ab81a56b92b9" rel="noopener noreferrer"&gt;392536c&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;👔 up(fs,sys): update some fs and sys util func run logic &lt;a href="https://github.com/gookit/goutil/commit/772762873ad9fa766afce0841d0343078770545e" rel="noopener noreferrer"&gt;7727628&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;👔 up(struct): update some structs comments and update readme &lt;a href="https://github.com/gookit/goutil/commit/f461852b5dff186ce1467af42ec31a89f3ae3c22" rel="noopener noreferrer"&gt;f461852&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;👔 up(str): update some string util comments and func &lt;a href="https://github.com/gookit/goutil/commit/796d1fe6674337176b82ef71bd1e47b26b80b072" rel="noopener noreferrer"&gt;796d1fe&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Other
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;build(deps): bump golang.org/x/text from 0.5.0 to 0.6.0 &lt;a href="https://github.com/gookit/goutil/commit/79d77815079d167871c65f27186911014fdb53fd" rel="noopener noreferrer"&gt;79d7781&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;doc: re-generate the README docs &lt;a href="https://github.com/gookit/goutil/commit/42e30848e174ad8b442132b89d0c910b4c8be673" rel="noopener noreferrer"&gt;42e3084&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;doc: update the doc parse logic, support generic types &lt;a href="https://github.com/gookit/goutil/commit/030676da0275c93e1e67af344c0fd88cccad3b3e" rel="noopener noreferrer"&gt;030676d&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;doc: generate and update README docs &lt;a href="https://github.com/gookit/goutil/commit/f342e92004e670fc2aee7f72d0caa21f4172a347" rel="noopener noreferrer"&gt;f342e92&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;build(deps): bump golang.org/x/text from 0.6.0 to 0.7.0 &lt;a href="https://github.com/gookit/goutil/commit/1e3ea77d248bcecbeb26fcf8d3ef520d2bdd8829" rel="noopener noreferrer"&gt;1e3ea77&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;GitHub: &lt;a href="https://github.com/gookit/goutil" rel="noopener noreferrer"&gt;https://github.com/gookit/goutil&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>discuss</category>
    </item>
    <item>
      <title>Lightweight event management, dispatch tool library implemented by Go</title>
      <dc:creator>Inhere</dc:creator>
      <pubDate>Thu, 07 Jul 2022 14:37:14 +0000</pubDate>
      <link>https://dev.to/inhere/lightweight-event-management-dispatch-tool-library-implemented-by-go-583p</link>
      <guid>https://dev.to/inhere/lightweight-event-management-dispatch-tool-library-implemented-by-go-583p</guid>
      <description>&lt;p&gt;&lt;code&gt;event&lt;/code&gt; - Lightweight event management, dispatch tool library implemented by Go&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Support for custom definition event objects&lt;/li&gt;
&lt;li&gt;Support for adding multiple listeners to an event&lt;/li&gt;
&lt;li&gt;Supports setting the priority of the event listener. The higher the priority, the higher the trigger.&lt;/li&gt;
&lt;li&gt;Support for a set of event listeners based on the event name prefix &lt;code&gt;PREFIX.*&lt;/code&gt;.

&lt;ul&gt;
&lt;li&gt;add &lt;code&gt;app.*&lt;/code&gt; event listen, trigger &lt;code&gt;app.run&lt;/code&gt; &lt;code&gt;app.end&lt;/code&gt;, Both will trigger the &lt;code&gt;app.*&lt;/code&gt; event at the same time&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Support for using the wildcard &lt;code&gt;*&lt;/code&gt; to listen for triggers for all events&lt;/li&gt;
&lt;li&gt;Complete unit testing, unit coverage &lt;code&gt;&amp;gt; 95%&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Repo and doc
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Github: &lt;a href="https://github.com/gookit/event"&gt;https://github.com/gookit/event&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pkg.go.dev/github.com/gookit/event"&gt;Godoc for github&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Install
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;install &lt;/span&gt;github.com/gookit/event
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Quick start
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/gookit/event"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Register event listener&lt;/span&gt;
    &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;On&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"evt1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenerFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"handle event: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Normal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Register multiple listeners&lt;/span&gt;
    &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;On&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"evt1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenerFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"handle event: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;High&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// ... ...&lt;/span&gt;

    &lt;span class="c"&gt;// Trigger event&lt;/span&gt;
    &lt;span class="c"&gt;// Note: The second listener has a higher priority, so it will be executed first.&lt;/span&gt;
    &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustFire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"evt1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"arg0"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"val0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"arg1"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"val1"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: The second listener has a higher priority, so it will be executed first.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Using the wildcard
&lt;/h3&gt;

&lt;p&gt;Register event listener and name end with wildcard &lt;code&gt;*&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;dbListener1&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenerFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"handle event: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;On&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"app.db.*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dbListener1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Normal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Trigger events on other logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;doCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// do something ...&lt;/span&gt;
    &lt;span class="c"&gt;// Trigger event&lt;/span&gt;
    &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustFire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"app.db.create"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"arg0"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"val0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"arg1"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"val1"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;doUpdate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// do something ...&lt;/span&gt;
    &lt;span class="c"&gt;// Trigger event&lt;/span&gt;
    &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustFire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"app.db.update"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"arg0"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"val0"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Like the above, triggering the &lt;code&gt;app.db.create&lt;/code&gt; &lt;code&gt;app.db.update&lt;/code&gt; event&lt;br&gt;
will trigger the execution of the &lt;code&gt;dbListener1&lt;/code&gt; listener.&lt;/p&gt;
&lt;h2&gt;
  
  
  Write event listeners
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Using anonymous functions
&lt;/h3&gt;

&lt;p&gt;You can use anonymous function for quick write an event lister.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;mypgk&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/gookit/event"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;fnHandler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"handle event: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// register&lt;/span&gt;
    &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;On&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"evt1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenerFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fnHandler&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;High&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using the structure method
&lt;/h3&gt;

&lt;p&gt;You can use struct write an event lister, and it should implementation interface &lt;code&gt;event.Listener&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;interface:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Listener interface&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Listener&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;example:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Implementation interface &lt;code&gt;event.Listener&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;mypgk&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"github.com/gookit/event"&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;MyListener&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// userData string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;MyListener&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"OK"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Register multiple event listeners
&lt;/h2&gt;

&lt;p&gt;Can implementation interface &lt;code&gt;event.Subscriber&lt;/code&gt; for register&lt;br&gt;
multiple event listeners at once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;interface:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Subscriber event subscriber interface.&lt;/span&gt;
&lt;span class="c"&gt;// you can register multi event listeners in a struct func.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Subscriber&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// SubscribedEvents register event listeners&lt;/span&gt;
    &lt;span class="c"&gt;// key: is event name&lt;/span&gt;
    &lt;span class="c"&gt;// value: can be Listener or ListenerItem interface&lt;/span&gt;
    &lt;span class="n"&gt;SubscribedEvents&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;example&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Implementation interface &lt;code&gt;event.Subscriber&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;mypgk&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/gookit/event"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;MySubscriber&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// ooo&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;MySubscriber&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;SubscribedEvents&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}{&lt;/span&gt;
        &lt;span class="s"&gt;"e1"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenerFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;e1Handler&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s"&gt;"e2"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenerItem&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Priority&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AboveNormal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Listener&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenerFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"an error"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="s"&gt;"e3"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;MyListener&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;MySubscriber&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;e1Handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"e1-key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"val1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Write custom events
&lt;/h2&gt;

&lt;p&gt;If you want to customize the event object or define some fixed event information in advance,&lt;br&gt;
you can implement the &lt;code&gt;event.Event&lt;/code&gt; interface.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;interface:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Event interface&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Event&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="c"&gt;// Target() interface{}&lt;/span&gt;
    &lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;SetData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Event&lt;/span&gt;
    &lt;span class="n"&gt;Abort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;IsAborted&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;examples:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;mypgk&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"github.com/gookit/event"&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;MyEvent&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BasicEvent&lt;/span&gt;
    &lt;span class="n"&gt;customData&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;MyEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;CustomData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customData&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Usage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;MyEvent&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;customData&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"hello"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"e1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// add listener&lt;/span&gt;
&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;On&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"e1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenerFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"custom Data: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;MyEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomData&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}))&lt;/span&gt;

&lt;span class="c"&gt;// trigger&lt;/span&gt;
&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"e1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;// OR&lt;/span&gt;
&lt;span class="c"&gt;// event.FireEvent(e)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Main method
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;On/Listen(name string, listener Listener, priority ...int)&lt;/code&gt; Register event listener&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Subscribe/AddSubscriber(sbr Subscriber)&lt;/code&gt;  Subscribe to support registration of multiple event listeners&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Trigger/Fire(name string, params M) (error, Event)&lt;/code&gt; Trigger event by name and params&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MustTrigger/MustFire(name string, params M) Event&lt;/code&gt;   Trigger event, there will be panic if there is an error&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FireEvent(e Event) (err error)&lt;/code&gt;    Trigger an event based on a given event instance&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FireBatch(es ...interface{}) (ers []error)&lt;/code&gt; Trigger multiple events at once&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AsyncFire(e Event)&lt;/code&gt;   Async fire event by 'go' keywords&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>programming</category>
      <category>opensource</category>
      <category>github</category>
      <category>go</category>
    </item>
    <item>
      <title>slog - Lightweight, configurable, extensible Go logging library</title>
      <dc:creator>Inhere</dc:creator>
      <pubDate>Thu, 16 Jun 2022 02:33:47 +0000</pubDate>
      <link>https://dev.to/inhere/slog-lightweight-configurable-extensible-go-logging-library-52h2</link>
      <guid>https://dev.to/inhere/slog-lightweight-configurable-extensible-go-logging-library-52h2</guid>
      <description>&lt;p&gt;&lt;code&gt;slog&lt;/code&gt; is a lightweight, configurable, extensible Go logging library.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fgookit%2Fslog%2Fmaster%2F_example%2Fimages%2Fconsole-log-all-level.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fgookit%2Fslog%2Fmaster%2F_example%2Fimages%2Fconsole-log-all-level.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Simple, directly available without configuration&lt;/li&gt;
&lt;li&gt;Support common log level processing.

&lt;ul&gt;
&lt;li&gt;eg: &lt;code&gt;trace&lt;/code&gt; &lt;code&gt;debug&lt;/code&gt; &lt;code&gt;info&lt;/code&gt; &lt;code&gt;notice&lt;/code&gt; &lt;code&gt;warn&lt;/code&gt; &lt;code&gt;error&lt;/code&gt; &lt;code&gt;fatal&lt;/code&gt; &lt;code&gt;panic&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Support any extension of &lt;code&gt;Handler&lt;/code&gt; &lt;code&gt;Formatter&lt;/code&gt; as needed&lt;/li&gt;

&lt;li&gt;Supports adding multiple &lt;code&gt;Handler&lt;/code&gt; log processing at the same time, outputting logs to different places&lt;/li&gt;

&lt;li&gt;Support to custom log message &lt;code&gt;Formatter&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Built-in &lt;code&gt;json&lt;/code&gt; &lt;code&gt;text&lt;/code&gt; two log record formatting &lt;code&gt;Formatter&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Support to custom build log messages &lt;code&gt;Handler&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;The built-in &lt;code&gt;handler.Config&lt;/code&gt; &lt;code&gt;handler.Builder&lt;/code&gt; can easily and quickly build the desired log handler&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Has built-in common log write handler program

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;console&lt;/code&gt; output logs to the console, supports color output&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;writer&lt;/code&gt; output logs to the specified &lt;code&gt;io.Writer&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;file&lt;/code&gt; output log to the specified file, optionally enable &lt;code&gt;buffer&lt;/code&gt; to buffer writes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;simple&lt;/code&gt; output log to the specified file, write directly to the file without buffering&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rotate_file&lt;/code&gt; outputs logs to the specified file, and supports splitting files by time and size, and &lt;code&gt;buffer&lt;/code&gt; buffered writing is enabled by default&lt;/li&gt;
&lt;li&gt;See ./handler folder for more built-in implementations&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Output logs to file
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Support enabling &lt;code&gt;buffer&lt;/code&gt; for log writing&lt;/li&gt;
&lt;li&gt;Support splitting log files by &lt;code&gt;time&lt;/code&gt; and &lt;code&gt;size&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Support configuration to compress log files via &lt;code&gt;gzip&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Support clean old log files by &lt;code&gt;BackupNum&lt;/code&gt; &lt;code&gt;BackupTime&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Git repository
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Github: &lt;a href="https://github.com/gookit/slog" rel="noopener noreferrer"&gt;https://github.com/gookit/slog&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go get github.com/gookit/slog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Quick start
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/gookit/slog"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;slog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Trace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"this is a log message"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;slog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"this is a log message"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;slog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"this is a log message"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;slog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Notice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"this is a log message"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;slog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"this is a log message"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;slog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"this is a log message"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;slog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"this is a log message"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;output preview:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fgookit%2Fslog%2Fmaster%2F_example%2Fimages%2Fconsole-log-all-level.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fgookit%2Fslog%2Fmaster%2F_example%2Fimages%2Fconsole-log-all-level.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Logs to file
&lt;/h2&gt;

&lt;p&gt;Using &lt;code&gt;slog&lt;/code&gt; to output logs to files is very convenient, and supports multiple files, splitting by time, etc.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/gookit/slog"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/gookit/slog/handler"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/gookit/slog/rotatefile"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;slog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustFlush&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// DangerLevels contains: slog.PanicLevel, slog.ErrorLevel, slog.WarnLevel&lt;/span&gt;
    &lt;span class="n"&gt;h1&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustRotateFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/tmp/logs/app_error.log"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rotatefile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EveryHour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithLogLevels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DangerLevels&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// NormalLevels contains: slog.InfoLevel, slog.NoticeLevel, slog.DebugLevel, slog.TraceLevel&lt;/span&gt;
    &lt;span class="n"&gt;h2&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustRotateFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/tmp/logs/app_info.log"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rotatefile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EveryHour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithLogLevels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NormalLevels&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;slog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PushHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;slog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PushHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// add logs&lt;/span&gt;
    &lt;span class="n"&gt;slog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"info message text"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;slog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"error message text"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See logs dir:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; /tmp/logs
app_error.log
app_info.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  More usage
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;More usage please see &lt;a href="https://github.com/gookit/slog/blob/master/README.zh-CN.md" rel="noopener noreferrer"&gt;README&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>programming</category>
      <category>go</category>
      <category>log</category>
      <category>opensource</category>
    </item>
    <item>
      <title>gookit/goutil - Useful utils package for the Go</title>
      <dc:creator>Inhere</dc:creator>
      <pubDate>Mon, 13 Jun 2022 07:40:15 +0000</pubDate>
      <link>https://dev.to/inhere/gookitgoutil-useful-utils-package-for-the-go-15bh</link>
      <guid>https://dev.to/inhere/gookitgoutil-useful-utils-package-for-the-go-15bh</guid>
      <description>&lt;p&gt;&lt;code&gt;gookit/goutil&lt;/code&gt; 💪 Useful utils package for the Go: int, string, array/slice, map, error, time, format, CLI, ENV, filesystem, system, testing and more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sub-packages
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;arrutil&lt;/code&gt;: Array/Slice util functions. eg: check, convert, formatting&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dump&lt;/code&gt;: Simple variable printing tool, printing slice, map will automatically wrap each element and display the call location&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cliutil&lt;/code&gt; Command-line util functions. eg: read input, exec command, cmdline parse/build&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;errorx&lt;/code&gt; Provide an enhanced error implements for go, allow with stacktraces and wrap another error.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;envutil&lt;/code&gt; ENV util for current runtime env information. eg: get one, get info, parse var&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fmtutil&lt;/code&gt; Format data util functions. eg: data, size&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fsutil&lt;/code&gt; Filesystem util functions, quick create, read and write file. eg: file and dir check, operate&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;jsonutil&lt;/code&gt; some util functions for quick read, write, encode, decode JSON data.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;maputil&lt;/code&gt; Map data util functions. eg: convert, sub-value get, simple merge&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mathutil&lt;/code&gt;Math(int, number) util functions. eg: convert, math calc, random&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;netutil&lt;/code&gt; Network util functions

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;netutil/httpreq&lt;/code&gt; An easier-to-use HTTP client that wraps http.Client&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;strutil&lt;/code&gt; String util functions. eg: bytes, check, convert, encode, format and more&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;sysutil&lt;/code&gt; System util functions. eg: sysenv, exec, user, process&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;testutil&lt;/code&gt; Test help util functions. eg: http test, mock ENV value&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;timex&lt;/code&gt; Provides an enhanced time.Time implementation. Add more commonly used functional methods

&lt;ul&gt;
&lt;li&gt;such as: DayStart(), DayAfter(), DayAgo(), DateFormat() and more.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Git repository
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Github: &lt;a href="https://github.com/gookit/goutil" rel="noopener noreferrer"&gt;https://github.com/gookit/goutil&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Gitee: &lt;a href="https://gitee.com/gookit/goutil" rel="noopener noreferrer"&gt;https://gitee.com/gookit/goutil&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go get github.com/gookit/goutil
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;h3&gt;
  
  
  goutil/dump
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;goutil/dump&lt;/code&gt;  is a golang data printing toolkit that prints beautiful and easy to read go slice, map, struct data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Examples&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;//  go run ./dump/_examples/struct.go&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;s1&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;cannotExport&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="p"&gt;}{&lt;/span&gt;
        &lt;span class="n"&gt;cannotExport&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}{&lt;/span&gt;
            &lt;span class="s"&gt;"key1"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"key2"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"abcd123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;s2&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ab&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
        &lt;span class="n"&gt;Cd&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
    &lt;span class="p"&gt;}{&lt;/span&gt;
        &lt;span class="s"&gt;"ab"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;23&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Infoln&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"- Use fmt.Println:"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Infoln&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;- Use dump.Println:"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;dump&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;s2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fgookit%2Fgoutil%2Fmaster%2Fdump%2F_examples%2Fstruct.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fgookit%2Fgoutil%2Fmaster%2Fdump%2F_examples%2Fstruct.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  goutil/timex
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;goutil/timex&lt;/code&gt; Provides an enhanced time.Time implementation, and add more commonly used functional methods. &lt;/p&gt;

&lt;p&gt;such as: DayStart(), DayAfter(), DayAgo(), DateFormat() and more&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create timex instance:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;timex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c"&gt;// from time.Time&lt;/span&gt;
&lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;timex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;timex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FromTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="c"&gt;// from time unix&lt;/span&gt;
&lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;timex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FromUnix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1647411580&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Create time for date string&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// auto match layout by datetime&lt;/span&gt;
&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;  &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;timex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FromString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"2022-04-20 19:40:34"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;// custom set the datetime layout&lt;/span&gt;
&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;  &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;timex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FromString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"2022-04-20 19:40:34"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"2006-01-02 15:04:05"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;// use date template as layout&lt;/span&gt;
&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;  &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;timex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FromDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"2022-04-20 19:40:34"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Y-m-d H:I:S"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Quickly fetch time&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;timex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Yesterday&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tomorrow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DayStart&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// get time at Y-m-d 00:00:00&lt;/span&gt;
&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DayEnd&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// get time at Y-m-d 23:59:59&lt;/span&gt;
&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HourStart&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// get time at Y-m-d H:00:00&lt;/span&gt;
&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HourEnd&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// get time at Y-m-d H:59:59&lt;/span&gt;

&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddDay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHour&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;120&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Quickly format date&lt;/strong&gt;：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"y/m/d H:I"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Y-m-d H:I:S"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Output: 2022-04-20 19:40:34&lt;/span&gt;
&lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"y-m-d H:I:S"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Output: 22-04-20 19:40:34&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  More usage
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;More utils and usage please see README &lt;a href="https://github.com/gookit/goutil/blob/master/README.md" rel="noopener noreferrer"&gt;https://github.com/gookit/goutil/blob/master/README.md&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>programming</category>
      <category>go</category>
      <category>github</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
