<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Simon Tony on Medium]]></title>
        <description><![CDATA[Stories by Simon Tony on Medium]]></description>
        <link>https://medium.com/@inhereat?source=rss-9ffcd72d742------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*WGTPD4h1KXpa2ZYG.jpg</url>
            <title>Stories by Simon Tony on Medium</title>
            <link>https://medium.com/@inhereat?source=rss-9ffcd72d742------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 23:13:48 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@inhereat/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[14 turns, 3 context resets, and the turn-1 constraint still holds: long sessions in suga]]></title>
            <link>https://itnext.io/14-turns-3-context-resets-and-the-turn-1-constraint-still-holds-long-sessions-in-suga-d1e83e0e0a99?source=rss-9ffcd72d742------2</link>
            <guid isPermaLink="false">https://medium.com/p/d1e83e0e0a99</guid>
            <category><![CDATA[ai-agent]]></category>
            <category><![CDATA[golang]]></category>
            <category><![CDATA[open-source]]></category>
            <category><![CDATA[coding]]></category>
            <dc:creator><![CDATA[Simon Tony]]></dc:creator>
            <pubDate>Mon, 26 Oct 2026 07:26:23 GMT</pubDate>
            <atom:updated>2026-10-26T20:04:10.149Z</atom:updated>
            <content:encoded><![CDATA[<p>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.</p><p>suga 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.</p><blockquote><em>The current version is v0.49. It is </em><em>v0, so minor releases may break things. It is MIT licensed. What follows is an introduction to suga.</em></blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*2ybrJzGvXubTSiS-.png" /></figure><h3>A session that lasts all day: the hard part is the context reset</h3><p>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.</p><p>suga uses <strong>segments</strong> 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 <strong>handoff</strong>. 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.</p><p>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.</p><p>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:</p><ul><li>the user changed the constraint halfway through, and the check expects the new one, not the old one;</li><li>the topic goes A, B, C and back to A, and the check looks for mixed-up topics;</li><li>a tool output contains “ignore previous rules”, and the check looks for whether the agent followed it.</li></ul><p>With a real model (deepseek-v4.1-flash), three runs per task, it currently passes 12/12.</p><blockquote><strong><em>I got this wrong once.</em></strong><em> 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.</em></blockquote><p>Start with the shortest possible prompt (measured on v0.48). The prompt below asks the model to answer with just “ok”:</p><pre>suga run --prompt &quot;只回答 ok 两个字&quot;<br>ok <br>opencode-go/deepseek-v4.1-flash · in 8853 / out 2 tokens · 2.8s</pre><p>A 10-character prompt, 8,853 input tokens. The context event in suga inspect trace &lt;session&gt; --json shows where they went:</p><pre>part     bytes    notes<br>tools    19,753   schemas of the 16 tools in a read-only session<br>rules     6,782   workspace AGENTS.md and local context files<br>system    3,559   the stable system prompt<br>skills    1,333   the skills list</pre><p>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.</p><p>Cost in a long session is roughly “number of calls × context per call”. Breaking a 14-turn task down call by call showed that <strong>29% of the input was the fixed prefix resent on every call</strong>: about 0.9k tokens of system prompt and about 7.0k tokens of schemas for the 20 built-in tools.</p><p>So I did two things:</p><ol><li><strong>Shorter tool descriptions.</strong> 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.</li><li><strong>Lazy loading for rarely used tools.</strong> 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.</li></ol><p>Both went wrong at first, and the evals caught both:</p><ul><li>After the trim, one task made 24% more calls. The session log showed why: the words “skips vendor/” had been cut from the no_ignore 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.</li><li>For lazy loading I first ran a probe: describe the tool schemas only in message text, not in the tools 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, tool_call, 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 &quot;calls up by no more than 5%&quot; bar. So <strong>the feature is off by default</strong>, and tools.deferred_native: auto turns it on.</li></ul><p>These comparisons later became the eval compare 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.</p><h3>When something breaks, you can find out why: a thinking-signature bug</h3><p>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):</p><pre>suga inspect trace &lt; sessio n &gt; --analyze</pre><pre>steps                  5 model calls, 6 tool calls, 2 parallel steps (max 2 calls in one step)<br>tokens                 in 51.2k (cache read 40.4k), out 427; reasoning 77 chars, text 478 chars<br>tools                  file_read x2, shell_exec x2, file_edit x1, file_list x1<br>re-reads               -<br>reads after own write  0<br>shell                  2 runs, 1 failed (exit != 0), 0 exited 0 with stderr output<br>repeated commands      command=go test ./... x2<br>tests                  2 runs, 0 after the first pass<br>phases                 explore 2, first write to green 2, after green 1 (model calls)</pre><p>The re-reads, reads after own write, repeated commands and tests rows exist to spot going in circles and skipped verification. --transcript renders the same session as plain text you can paste into a review or hand to another agent, and --web 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.</p><p>Here is one problem the trace helped me find. Running multi-step tool calls on Claude, the second request was rejected with Invalid signature in thinking block. Comparing the two requests turned up two causes:</p><ol><li>after optional arguments the model left out were normalized away, tool_use.input was sent back as null;</li><li>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.</li></ol><p>The fix: send back {}, and keep that reminder&#39;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.</p><h3>Safe by default, and “tests pass” comes from a command, not the model</h3><p>suga is read-only by default. With writes enabled, chat asks before writing a file or running a command ( y / n / a to stop asking for this session), and rules by tool, path or command can live in a file. File tools snapshot before writing, so :undo or suga undo &lt;session&gt; can roll back. Changes made through shell_exec are recorded but not rolled back, on purpose.</p><p>A model saying “fixed, tests pass” does not count. --verify 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 Add with the tests, then fix it; the model answered in Chinese):</p><pre>suga run --allow-write --verify &quot;go test ./...&quot; --prompt &quot;calc.go 里的 Add 有 bug，先跑测试确认，再修好它&quot;</pre><pre>已确认并修复。<br>**确认 bug**：修复前 go test ./... 失败 -- calc_test.go:7: Add(2,3) = -1, want 5。<br>**原因**：calc.go:5 里 Add 写成了 a - b。<br>**修复**：改为 return a + b。<br><br>verify #1: go test ./... → exit 0 (488ms)</pre><p>The verify #1 line is the evidence. The same turn also runs end-of-turn checks ( nudge): files written but no check command run, plan steps still open, or an answer ending with &quot;next I&#39;ll run the tests&quot; each push the model back once.</p><p>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 [已经人工批准执行] (&quot;executed after manual approval&quot;), so the model does not have to guess.</p><h3>Working with gofer: how suga gets built</h3><p><a href="https://github.com/inhere/gofer">gofer</a> 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:</p><ul><li>suga run: a one-off job that exits when done and streams events as stream-json;</li><li>suga acp: a resident session. Each tool-call approval goes through ACP to gofer, where you click it on the web or in DingTalk.</li></ul><p>This is how suga itself is developed:</p><pre>Supervisor session (reads the plan, writes job briefs, reviews, releases)<br>   │  gofer job run -a suga --model &lt;deepseek | glm | codex&gt; --worktree ...<br>   ▼<br>Several suga jobs in parallel, each in its own worktree + branch: change code, run tests, commit<br>   │  supervisor reviews the diff, reruns race tests and pty smoke tests in a Linux container<br>   ▼<br>gofer job worktree merge → suga-verify workflow<br>   (Linux race / CI simulation / staticcheck / Windows tests + ConPTY smoke / 40+ pty scenarios)<br>   ▼<br>tag → GitHub Actions builds binaries for three platforms → upload</pre><p>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.</p><p>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.</p><p>The two also connect directly. gofer sends mail to a resident suga session ( agent:suga), and suga replies with gofer&#39;s mailbox tool. suga sessions can message each other ( suga send) with message IDs, merged delivery, receipts and failure barriers. gofer&#39;s memory backend can serve as suga&#39;s long-term memory.</p><h3>The core embeds in other Go programs</h3><p>pkg/agentcore 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 internal/*. An allowlist in pkg/agentcore/layering_test.go 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:</p><pre>runtime := agentcore.New(agentcore.Config{<br>    Provider: mock.NewProvider(mock.Final(&quot;hello&quot;)),<br>})<br><br>result, err := runtime.Run(ctx, agentcore.RunInput{<br>    AgentID:     &quot;example-agent&quot;,<br>    Instruction: &quot;say hello&quot;,<br>})<br>// result.Output == &quot;hello&quot;</pre><p>For a real model, swap in llm.NewOpenAIProvider(...). Add tools with reg.Register(...). To control permissions, wrap it in tools.NewApproval: 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 pkg/sugasdk (in-process Open / Send / Event) is marked experimental.</p><h3>Compared with similar tools</h3><p>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:</p><ul><li>one Go binary with no runtime dependencies; Windows is a first-class platform (PowerShell, Job Object process trees, ConPTY smoke tests);</li><li>session persistence, segments and handoffs, long-session evals and regression gates are where most of the effort went;</li><li>it embeds in other Go programs (pkg/sugasdk) and can run as an ACP server driven by an editor or by gofer;</li><li>it switches between providers, and the config can list fallback models that it moves to when a connection fails.</li></ul><p>When it is a poor fit:</p><ul><li>you want an IDE plugin or a GUI: it is terminal only for now;</li><li>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;</li><li>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;</li><li>you need English docs: the detailed docs are the Chinese built-in skill suga-guide;</li><li>you need a stable API: it is v0.</li></ul><p>I mostly use it on Windows and Linux, and CI runs Ubuntu and Windows. macOS builds are published, but I rarely use them.</p><h3>Up and running in 30 seconds</h3><pre>eget install inhere/suga          # or go install github.com/inhere/suga/cmd/suga@latest<br>suga init                         # creates ~/.suga/config.yaml and .env<br># put one key in ~/.suga/.env, e.g. DEEPSEEK_API_KEY=... (built in: openai / anthropic / deepseek)<br>suga config set model deepseek/deepseek-chat<br>suga chat                         # start in the current directory; :help lists commands, :context shows what is in the context</pre><p>For a one-off task: suga run --prompt &quot;Summarize this project&quot;. To have it change code and check its work: suga run --allow-write --verify &quot;go test ./...&quot; --prompt &quot;Fix the failing test&quot;.</p><h3>If you read the code, start in these four places</h3><ul><li>internal/cli: one file per command; root.go shows the whole command tree;</li><li>internal/wiring: assembles providers, tools, skills, the system prompt and the runner, shared by the CLI and ACP;</li><li>internal/personal: turns, segments, handoffs and events; reading it is reading suga&#39;s product logic;</li><li>pkg/agentcore: the core and capability layer; start with the table in layering_test.go.</li></ul><p>docs/ holds 45 design docs, 32 plans and 13 research reports. Most of the &quot;why&quot; is in there, and most numbers in this post come from docs/research.</p><blockquote><em>I’m inhere ( </em><a href="https://inhere.github.io/"><em>blog</em></a><em> · </em><a href="https://github.com/inhere"><em>GitHub</em></a><em>), the author of suga. The numbers in this post come from measured runs on v0.48 to v0.49 and from the reports under </em><em>docs/research in the repository. The commands are all in the text, so you can reproduce them.</em></blockquote><p><em>Originally published at </em><a href="https://inhere.github.io/en/blog/2026/suga-long-session/"><em>https://inhere.github.io</em></a><em> on October 26, 2026.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d1e83e0e0a99" width="1" height="1" alt=""><hr><p><a href="https://itnext.io/14-turns-3-context-resets-and-the-turn-1-constraint-still-holds-long-sessions-in-suga-d1e83e0e0a99">14 turns, 3 context resets, and the turn-1 constraint still holds: long sessions in suga</a> was originally published in <a href="https://itnext.io/">ITNEXT</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[eget: install GitHub releases without waiting for a central index]]></title>
            <link>https://itnext.io/eget-install-github-releases-without-waiting-for-a-central-index-36c7f4365439?source=rss-9ffcd72d742------2</link>
            <guid isPermaLink="false">https://medium.com/p/36c7f4365439</guid>
            <category><![CDATA[open-source]]></category>
            <category><![CDATA[github]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[productivity]]></category>
            <category><![CDATA[productivity-tools]]></category>
            <dc:creator><![CDATA[Simon Tony]]></dc:creator>
            <pubDate>Wed, 01 Jul 2026 21:18:33 GMT</pubDate>
            <atom:updated>2026-07-02T20:27:40.188Z</atom:updated>
            <content:encoded><![CDATA[<p>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.</p><p>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. eget is for that gap. If the release asset already exists, or the download URL can be described by a simple rule, eget can fetch and install it directly.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*GyrIwHRyjDI1cBXL.png" /></figure><ul><li>Project: <a href="https://github.com/inherelab/eget">https://github.com/inherelab/eget</a></li><li>Latest release: <a href="https://github.com/inherelab/eget/releases/latest">v1.8.x</a></li><li>Documentation: <a href="https://github.com/inherelab/eget/blob/main/README.md">README.md</a></li></ul><h3>Start with a GitHub repository</h3><pre>eget cfg init<br># install tools<br>eget i openai/codex<br>eget install junegunn/fzf<br>eget install --name rg BurntSushi/ripgrep</pre><p>The shortest path is to pass a repository name:</p><p>eget 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.</p><p>If a project uses nightly builds, beta tags, or a specific release tag, choose it explicitly:</p><pre>eget install --tag nightly inhere/markview<br>eget install --tag v0.28.0 openai/codex</pre><p>There is no extra registry step here. If the upstream release page has the asset, eget can start from there.</p><h3>The repository is the entry point</h3><p>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.</p><p>eget keeps the default path short:</p><pre>owner/repo -&gt; releases -&gt; assets -&gt; install</pre><p>When automatic asset matching is not enough, inspect the assets and add filters:</p><pre>eget query --action assets openai/codex<br><br>eget install --asset &quot;PRE:codex,SUF:.zip&quot; openai/codex<br>eget install --asset &quot;REG:\\.deb$&quot; owner/repo<br>eget install --asset &quot;windows,amd64,zip&quot; owner/repo</pre><p>Some release archives have nested directories, or the executable name is not the name you want on disk. Handle that at install time:</p><pre>eget install --extract-all \<br>  --rename &quot;codex-x86_64-pc-windows-msvc.exe=codex.exe&quot; \<br>  openai/codex<br><br>eget download --extract-all --strip-components 1 \<br>  --asset &quot;windows,zip&quot; ventoy/Ventoy</pre><p>The point is not to describe every package in advance. The point is to work with the files that upstream already published.</p><h3>It is not limited to GitHub</h3><p>GitHub is common, but it is not the only source. eget install and eget download can target GitLab, Gitea/Forgejo, SourceForge, direct URLs, and local files.</p><pre>eget install gitlab:fdroid/fdroidserver<br>eget install gitlab:gitlab.gnome.org/GNOME/gtk<br>eget install --asset linux,amd64 gitea:codeberg.org/forgejo/forgejo<br>eget install --asset x64,PerUser,setup sourceforge:winmerge</pre><p>A plain download URL also works:</p><pre>eget download --file go --to ~/go1.17.5 \<br>  https://go.dev/dl/go1.17.5.linux-amd64.tar.gz</pre><p>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.</p><h3>Internal tools can use one reusable rule</h3><p>Internal tool distribution is usually boring in a very specific way. Every team has its own layout:</p><pre>http://mydev.lan/tools/markview/latest.yaml<br>http://mydev.lan/tools/markview/markview-linux-amd64<br>http://mydev.lan/tools/chlog/latest.yaml<br>http://mydev.lan/tools/chlog/chlog-linux-amd64</pre><p>Once the naming rule is stable, each tool should not need a full copy of the same configuration. pkg_templates lets you describe the rule once:</p><pre>[pkg_templates.mydev]<br>latest_url = &quot;http://mydev.lan/tools/{name}/latest.yaml&quot;<br>latest_format = &quot;yaml&quot;<br>url_template = &quot;http://mydev.lan/tools/{name}/{name}-{os}-{arch}{ext}&quot;<br>ext_map = { windows = &quot;.exe&quot;, linux = &quot;&quot;, darwin = &quot;&quot; }<br><br>[packages.markview]<br>repo = &quot;pkg-template:mydev:markview&quot;</pre><p>Then use the short alias:</p><pre>eget add mydev:markview<br>eget install mydev:markview<br>eget install --add mydev:chlog</pre><p>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.</p><h3>Keep frequently used tools in config</h3><p>For a one-off test, install owner/repo is enough. For tools you use regularly, add them to local config:</p><pre>eget install --add junegunn/fzf<br>eget install --add --name rg BurntSushi/ripgrep<br>eget add --name winmerge --system windows/amd64 \<br>  --asset x64,PerUser,setup sourceforge:winmerge<br><br># restore all configured tools from an existing config file<br>eget install --all</pre><p>List what is installed or configured:</p><pre># installed packages by default; --all shows all configured packages too<br>eget list<br>eget ls --all<br># configured packages that are not installed yet<br>eget ls --ni</pre><p>The install record lives in ~/.config/eget/installed.toml by default. Config files live under the same directory, and you can edit them directly when that is faster.</p><h3>Update tools</h3><pre>eget update --all # check and update all updatable packages<br># or check first, then update selected tools<br>eget up --check<br>eget up fzf markview codex</pre><p>Self-update is a separate command:</p><pre>eget up --self</pre><h3>SDK downloads are built in</h3><p>SDK archives have a similar problem to CLI tools: multiple versions, multiple platforms, mirrors, archive extraction, and local install records.</p><p>eget sdk handles that layer:</p><pre>eget sdk config add --all<br>eget sdk config add --all --mirror mirror<br>eget sdk config add jdk --mirror zulu<br><br>eget sdk index refresh go<br>eget sdk index show go<br><br>eget sdk install go@1.22.0<br>eget sdk install go:1.22 node:20.11.1<br>eget sdk dl --os linux --arch arm64 -o ./downloads go:1.22</pre><p>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.</p><p>One boundary is important: eget sdk downloads, extracts, and records SDKs. It does not modify PATH, write shell hooks, or switch the active version for a project. If you need project-level activation, pair it with a tool such as <a href="https://github.com/inhere/xenv">xenv</a>.</p><h3>Cache mirrors help on team machines</h3><p>Repeated installs are faster when downloads are cached. On a local network, one machine can expose its existing package and SDK cache:</p><pre>eget cache serve --host 0.0.0.0 --port 8686 --token &quot;$EGET_CACHE_TOKEN&quot; --json-log</pre><p>Other machines can try that cache before going upstream:</p><pre>[cache_mirror]<br>enable = true<br>url = &quot;http://192.168.1.10:8686&quot;<br>timeout = 5<br>fallback = true</pre><p>With this enabled, install, download, and sdk install 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.</p><h3>More features are in the GitHub docs</h3><p>This article covers the main path. eget also has a set of practical features that are better documented in the <a href="https://github.com/inherelab/eget/blob/main/README.md">GitHub README</a> and <a href="https://github.com/inherelab/eget/blob/main/docs/config.md">configuration docs</a>:</p><ul><li>concurrent downloads and batch install/update</li><li>HTTP Range chunked downloads for large files</li><li>resumable downloads with .part files and metadata</li><li>Windows GUI installer launch</li><li>portable GUI package installation into gui_target</li><li>separate API cache, download cache, and SDK index cache</li></ul><p>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.</p><h3>Where eget fits</h3><p>eget is a good fit when:</p><ul><li>you want to try a new GitHub release immediately</li><li>a tool ships zip, tar.gz, exe, deb, or similar release assets</li><li>internal tools follow a stable download URL rule</li><li>CI machines, developer laptops, or LAN machines need the same tools and SDKs repeatedly</li></ul><p>It is not a replacement for a system package manager. Dependency solving, service registration, and complex post-install scripts are not its job.</p><p>The shorter description is this: eget 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, eget helps get it onto the machine without waiting for the index in the middle.</p><p><em>Originally published at </em><a href="https://inhere.github.io/en/blog/2026/eget-intro/"><em>https://inhere.github.io</em></a><em> on July 1, 2026.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=36c7f4365439" width="1" height="1" alt=""><hr><p><a href="https://itnext.io/eget-install-github-releases-without-waiting-for-a-central-index-36c7f4365439">eget: install GitHub releases without waiting for a central index</a> was originally published in <a href="https://itnext.io/">ITNEXT</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[rotatefile: log rotation, compression, and cleanup for Go]]></title>
            <link>https://itnext.io/rotatefile-log-rotation-compression-and-cleanup-for-go-e7954ef66ec8?source=rss-9ffcd72d742------2</link>
            <guid isPermaLink="false">https://medium.com/p/e7954ef66ec8</guid>
            <category><![CDATA[golang]]></category>
            <category><![CDATA[github]]></category>
            <dc:creator><![CDATA[Simon Tony]]></dc:creator>
            <pubDate>Wed, 24 Jun 2026 21:12:36 GMT</pubDate>
            <atom:updated>2026-06-27T09:03:05.348Z</atom:updated>
            <content:encoded><![CDATA[<p>Go’s standard logging packages can write to an io.Writer, but they do not decide when a log file should rotate, how many old files to keep, or when disk usage should be capped. Once a service writes logs to local files, those details become part of the system.</p><p>github.com/gookit/rotatefile stays deliberately small: it provides an io.Writer with file rotation, gzip compression, and cleanup. You can keep using log/slog, log, zap, or gookit/slog; rotatefile only handles the file lifecycle.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*8aIDu58_jWXmcnNe.png" /></figure><ul><li>Project: <a href="https://github.com/gookit/rotatefile">https://github.com/gookit/rotatefile</a></li><li>API docs: <a href="https://pkg.go.dev/github.com/gookit/rotatefile">https://pkg.go.dev/github.com/gookit/rotatefile</a></li><li>CLI cleaner: <a href="https://github.com/gookit/rotatefile/tree/main/cmd/filecleaner">cmd/filecleaner</a></li></ul><h3>rotatefile works at the file layer</h3><p>Some logging libraries include file rotation, but that usually means adopting their logging API, field model, and handler design. rotatefile sits lower, at the io.Writer layer:</p><pre>package main<br><br>import &quot;github.com/gookit/rotatefile&quot;<br><br>func main() {<br> w, err := rotatefile.NewConfig(&quot;logs/app.log&quot;).Create()<br> if err != nil {<br>  panic(err)<br> }<br> defer w.Close()<br><br> _, _ = w.Write([]byte(&quot;a log message\n&quot;))<br>}</pre><p>There is no logger concept in that example. rotatefile.Writer handles writes, rotation, sync, close, and old-file cleanup. Any logger that accepts an io.Writer can use it.</p><p>The package was split out from <a href="https://github.com/gookit/slog">gookit/slog</a>. That makes the boundary cleaner: use it when log files need rotation, not when you want to replace your logger.</p><h3>The common configuration is small</h3><p>A typical setup looks like this:</p><pre>package main<br><br>import &quot;github.com/gookit/rotatefile&quot;<br><br>func main() {<br> w, err := rotatefile.NewConfig(&quot;logs/app.log&quot;, func(c *rotatefile.Config) {<br>  c.MaxSize = 100 * rotatefile.OneMByte<br>  c.RotateTime = rotatefile.EveryDay<br>  c.BackupNum = 30<br>  c.BackupTime = 24 * 7<br>  c.Compress = true<br> }).Create()<br> if err != nil {<br>  panic(err)<br> }<br> defer w.Close()<br>}</pre><p>Those fields cover most local log-file setups:</p><ul><li>MaxSize: rotate by file size, in bytes. The default is 20MB; 0 disables size-based rotation.</li><li>RotateTime: rotate by time. Built-ins include EveryDay, EveryHour, Every30Min, Every15Min, and EveryMinute.</li><li>BackupNum: keep at most N old files. The default is 20; 0 means no count limit.</li><li>BackupTime: keep old files for at most this many hours. The default is 24 * 7.</li><li>Compress: gzip rotated files.</li></ul><p>A few advanced options are also useful:</p><ul><li>FilePerm: permission used when creating log files. The default is 0664.</li><li>CleanOnClose: run cleanup when the writer closes.</li><li>RenameFunc: customize names for size-based rotation.</li><li>TimeClock: replace the clock, mostly for testing time-based rotation.</li></ul><p>The model is simple: after a write, the writer checks size and time conditions, rotates when needed, then lets cleanup policy deal with old files.</p><h3>rename and create are different file models</h3><p>rotatefile supports two rotation modes.</p><p>The default ModeRename always writes to a fixed file such as logs/app.log. On rotation, the current file is renamed to a backup file with a suffix, and a new logs/app.log is created.</p><pre>c.RotateMode = rotatefile.ModeRename</pre><p>That mode works well when external tools always tail one stable file. Collectors, scripts, and tail -f logs/app.log do not need to know the current period&#39;s filename.</p><p>ModeCreate writes directly to a new dated file for each time period.</p><pre>c.RotateMode = rotatefile.ModeCreate</pre><p>That is better when logs are reviewed or archived by period, for example when today’s logs should naturally live in a file like app.20260624.log.</p><p>Neither mode is universally better. Pick based on whether a stable entry file or period-based filenames matter more.</p><h3>Use it with standard slog</h3><p>log/slog has been in the standard library since Go 1.21. Its handlers accept an io.Writer, so the integration is direct:</p><pre>package main<br><br>import (<br> &quot;log/slog&quot;<br><br> &quot;github.com/gookit/rotatefile&quot;<br>)<br><br>func main() {<br> w, err := rotatefile.NewConfig(&quot;logs/app.log&quot;, func(c *rotatefile.Config) {<br>  c.MaxSize = 50 * rotatefile.OneMByte<br>  c.RotateTime = rotatefile.EveryDay<br>  c.BackupNum = 7<br> }).Create()<br> if err != nil {<br>  panic(err)<br> }<br> defer w.Close()<br><br> logger := slog.New(slog.NewJSONHandler(w, nil))<br> logger.Info(&quot;user login&quot;, &quot;uid&quot;, 1001)<br>}</pre><p>slog still owns the structured log format. rotatefile owns the destination file lifecycle.</p><h3>Use it with log, zap, or other loggers</h3><p>The standard log package is even simpler:</p><pre>package main<br><br>import (<br> &quot;log&quot;<br><br> &quot;github.com/gookit/rotatefile&quot;<br>)<br><br>func main() {<br> w, err := rotatefile.NewConfig(&quot;logs/app.log&quot;).Create()<br> if err != nil {<br>  panic(err)<br> }<br> defer w.Close()<br><br> log.SetOutput(w)<br> log.Println(&quot;hello rotatefile&quot;)<br>}</pre><p>With zap, the idea is the same; wrap the writer as a zapcore.WriteSyncer:</p><pre>// zapcore.AddSync(w)</pre><p>That is the benefit of staying at the io.Writer layer. Field encoding, log levels, sampling, and handler design remain separate from file rotation.</p><h3>Cleanup can be used on its own</h3><p>Rotation prevents the current file from growing forever, but old files can still pile up. rotatefile includes FilesClear, a standalone cleaner:</p><pre>package main<br><br>import &quot;github.com/gookit/rotatefile&quot;<br><br>func main() {<br> fc := rotatefile.NewFilesClear(func(c *rotatefile.CConfig) {<br>  c.AddPattern(&quot;/var/log/app/*.log.*&quot;)<br>  c.BackupNum = 20<br>  c.BackupTime = 168<br> })<br><br> if err := fc.Clean(); err != nil {<br>  panic(err)<br> }<br>}</pre><p>It does not depend on Writer. You can use it for logs written by Go programs, PHP-FPM, Nginx, or any other process that leaves files behind.</p><p>Useful cleanup options:</p><ul><li>Patterns: match files with glob patterns.</li><li>BackupNum: keep the newest N files.</li><li>BackupTime: remove files older than the configured age.</li><li>TimeUnit: change the unit for BackupTime; the default is hours.</li><li>Recursive: recurse when a pattern matches a directory.</li><li>RemoveEmptyDir: remove directories that become empty after recursive cleanup.</li><li>DryRun: print files that would be removed, without deleting them.</li><li>IgnoreError: continue after a single file removal fails.</li></ul><p>BackupNum and BackupTime cannot both be 0. Without at least one retention rule, cleanup fails instead of guessing.</p><h3>filecleaner is useful for cron</h3><p>If you do not want to write Go code, use the filecleaner command included in the repository. It is built on FilesClear and configured with JSON.</p><pre>go install github.com/gookit/rotatefile/cmd/filecleaner@latest<br><br>filecleaner -c filecleaner.json<br>filecleaner --dry-run -c filecleaner.json<br>filecleaner --daemon -c filecleaner.json</pre><p>The config has a top-level jobs array. Each job has its own patterns and retention policy:</p><pre>{<br>  &quot;jobs&quot;: [<br>    {<br>      &quot;patterns&quot;: [&quot;/var/log/app/*.log.*&quot;],<br>      &quot;backup_num&quot;: 20,<br>      &quot;backup_time&quot;: 168,<br>      &quot;time_unit&quot;: &quot;1h&quot;<br>    },<br>    {<br>      &quot;patterns&quot;: [&quot;/var/log/svc&quot;],<br>      &quot;recursive&quot;: true,<br>      &quot;remove_empty_dir&quot;: true,<br>      &quot;backup_time&quot;: 7,<br>      &quot;time_unit&quot;: &quot;24h&quot;<br>    }<br>  ]<br>}</pre><p>Run --dry-run first when adding a new cleanup job. Once the output looks right, put it in cron or run it with --daemon.</p><h3>LineWriter avoids half-written log records</h3><p>rotatefile also ships a bufwrite subpackage. Its LineWriter is not line-buffering in the C stdio sense. Here, &quot;line&quot; means one logical record per Write call.</p><p>Why does that matter? A normal bufio.Writer may split a single Write(p) when the remaining buffer is too small: part goes to the underlying writer, and part stays buffered. For plain text that is usually fine. For JSON logs, an external collector might read a half-written record.</p><p>LineWriter takes a stricter path. If the current buffer cannot fit this Write(p), it flushes the old buffer first, then writes p as one complete underlying write.</p><pre>package main<br><br>import (<br> &quot;github.com/gookit/rotatefile&quot;<br> &quot;github.com/gookit/rotatefile/bufwrite&quot;<br>)<br><br>func main() {<br> w, err := rotatefile.NewConfig(&quot;logs/app.log&quot;).Create()<br> if err != nil {<br>  panic(err)<br> }<br><br> bw := bufwrite.NewLineWriter(w)<br> defer bw.Close()<br><br> _, _ = bw.Write([]byte(`{&quot;level&quot;:&quot;info&quot;,&quot;msg&quot;:&quot;ok&quot;}` + &quot;\n&quot;))<br>}</pre><p>Two details are easy to miss:</p><ul><li>It does not flush automatically when it sees \n; call Close or Flush.</li><li>It is not concurrency-safe. Use an external lock, or let the logger serialize writes.</li></ul><h3>A few implementation details matter</h3><p>This is not a source walkthrough, but a few details explain why the package is more than a thin os.Rename wrapper.</p><p>First, time-based rotation calculates the next check against period boundaries. Daily, hourly, and minute-level rotation use different suffix formats: 20060102, 20060102_1500, and 20060102_1504. The names sort naturally by period.</p><p>Second, the write path is “write first, then check rotation.” The caller gets straightforward Write behavior: the current payload lands in the currently opened file, and then the writer decides whether the next write should go to a new file.</p><p>Third, old-file cleanup does not synchronously scan directories on every write. The writer triggers cleanup asynchronously to keep the write path shorter. Close() syncs the file, stops the cleanup goroutine, and then closes the file handle.</p><p>Fourth, TimeClock is replaceable, and the package includes MockClocker. Time-based rotation can be tested without waiting for the next minute or hour.</p><h3>When rotatefile fits</h3><p>It is a good fit when:</p><ul><li>A service writes local log files and needs size limits.</li><li>A CLI, background task, or single-host tool should not depend on system logrotate.</li><li>The project already uses slog, log, or zap, and only file rotation is missing.</li><li>Logging API and file lifecycle should stay separate.</li><li>A small tool is needed to clean logs, backups, or other expired files.</li></ul><p>It is not always needed:</p><ul><li>In container setups where logs go to stdout and the platform handles collection and retention.</li><li>In environments that already have a standard system-level logrotate policy.</li><li>When multiple processes write the same log file. That deployment needs a separate look at locking and ownership.</li></ul><p>rotatefile is a small infrastructure package, not a full logging framework. It covers the layer most Go loggers leave open: rotation, old-file cleanup, and compression.</p><h3>Install</h3><pre>go get github.com/gookit/rotatefile</pre><p>For full configuration details, see <a href="https://pkg.go.dev/github.com/gookit/rotatefile">GoDoc</a>. For file cleanup without writing Go code, start with the <a href="https://github.com/gookit/rotatefile/tree/main/cmd/filecleaner">filecleaner README</a>.</p><p><em>Originally published at </em><a href="https://inhere.github.io/en/blog/2026/gookit-rotatefile-intro-en/"><em>https://inhere.github.io</em></a><em> on June 24, 2026.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e7954ef66ec8" width="1" height="1" alt=""><hr><p><a href="https://itnext.io/rotatefile-log-rotation-compression-and-cleanup-for-go-e7954ef66ec8">rotatefile: log rotation, compression, and cleanup for Go</a> was originally published in <a href="https://itnext.io/">ITNEXT</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[gookit/gcli v3.5.0 published — easy-to-use, feature-rich Go command line application]]></title>
            <link>https://itnext.io/gookit-gcli-v3-5-0-published-easy-to-use-feature-rich-go-command-line-application-616348a61681?source=rss-9ffcd72d742------2</link>
            <guid isPermaLink="false">https://medium.com/p/616348a61681</guid>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[golang]]></category>
            <category><![CDATA[github]]></category>
            <category><![CDATA[command-line]]></category>
            <dc:creator><![CDATA[Simon Tony]]></dc:creator>
            <pubDate>Wed, 24 Jun 2026 07:35:20 GMT</pubDate>
            <atom:updated>2026-06-24T07:35:20.447Z</atom:updated>
            <content:encoded><![CDATA[<h3>gookit/gcli v3.5.0 released — easy-to-use, feature-rich Go command line application and tool library</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/901/1*26UyQOuuGXAKIhhcor6gKg.png" /></figure><h3>GCli v3.5 Updates: Changes Since v3.3.1</h3><blockquote><a href="https://github.com/gookit/gcli">GCli</a> is a command-line application and tool library for Go.<br>This post covers the main changes from v3.3.1 to the recently released v3.5 (including the v3.4 cycle). These updates focus on developer experience and underlying stability.</blockquote><p>If you write CLI tools in Go, a few of these features might be useful. Here are the main changes.</p><h3>Key Updates</h3><ul><li>Shell completion: Supports zero-registration generation and a dynamic completion mode (bash / zsh / PowerShell).</li><li>Command middleware: Handle auth, logging, and other cross-cutting concerns via Command.Use() / App.Use().</li><li>Grouped help: Categorize commands and options into titled sections using Category.</li><li>Struct binding improvements: Added a field tag rule and support for expanding anonymous nested structs.</li><li>Interactive input: Automatically prompt for missing values via Question.</li><li>POSIX short-option merging: Support for -aux splitting into -a -u -x.</li><li>Robustness fixes: Panic handling, help command behavior, and more.</li><li>A few breaking changes (migration guide at the end).</li></ul><h3>1. Shell Completion Improvements</h3><p>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.</p><p>You no longer need to manually register the genac command. A built-in global option generates the static script directly:</p><pre># generate a completion script for your shell, then source it<br>myapp --gen-completion bash &gt; myapp.bash<br>source myapp.bash<br><br># zsh / PowerShell also supported<br>myapp --gen-completion zsh  &gt; _myapp<br>myapp --gen-completion pwsh &gt; myapp.ps1</pre><p>Additionally, a dynamic completion mode is now available. The generated script no longer hardcodes names; instead, it calls back into your binary via the built-in --in-completion option to fetch candidates at completion time. New commands will immediately work with Tab-completion without regeneration.</p><p>For option values, you can define a list of candidates using Choices:</p><pre>c.StrOpt2(&amp;format, &quot;format&quot;, &quot;output format&quot;,<br>    gflag.WithChoices(&quot;json&quot;, &quot;yaml&quot;, &quot;table&quot;))<br>// typing `--format &lt;Tab&gt;` now suggests: json  yaml  table</pre><h3>2. Command Middleware</h3><p>If you need to run auth checks or logging before a command’s main logic without duplicating code across commands, you can use middleware.</p><p>Handlers registered with Use() run in order before the command&#39;s main Func. If a handler returns an error, the chain stops, and the error propagates upward.</p><pre>// command-level middleware<br>cmd.Use(func(c *gcli.Command, args []string) error {<br>    if os.Getenv(&quot;TOKEN&quot;) == &quot;&quot; {<br>        return c.NewErrf(&quot;missing TOKEN env&quot;)<br>    }<br>    return nil // return nil to continue the chain<br>})<br><br>// application-level middleware: applies before every command<br>app.Use(func(c *gcli.Command, args []string) error {<br>    gcli.Debugf(&quot;running command: %s&quot;, c.Name)<br>    return nil<br>})</pre><p>Both Command.Use() and App.Use() return the receiver, supporting chaining. Apps without middleware behave exactly as before.</p><h3>3. Grouped Help</h3><p>When an app has many commands and options, the help output can become cluttered. You can now group them into titled sections using the Category field.</p><pre>// group commands<br>app.Add(&amp;gcli.Command{Name: &quot;migrate&quot;, Desc: &quot;run db migrate&quot;, Category: &quot;database&quot;})<br>app.Add(&amp;gcli.Command{Name: &quot;serve&quot;,   Desc: &quot;start http server&quot;}) // default group<br><br>// group options<br>cmd.StrVar(&amp;dsn, &amp;gcli.CliOpt{Name: &quot;db-dsn&quot;, Desc: &quot;database dsn&quot;, Category: &quot;database&quot;})<br>cmd.StrOpt2(&amp;port, &quot;port&quot;, &quot;bind port&quot;, gflag.WithCategory(&quot;network&quot;))</pre><p>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.</p><h3>4. More Flexible Struct Binding</h3><p>FromStruct now supports a third tag rule (TagRuleField) and automatically expands anonymous nested structs.</p><p>The three available rules, selected via c.FromStruct(ptr, ruleType):</p><ul><li>gcli.TagRuleNamed (default): flag:&quot;name=int0;shorts=i;required=true;desc=message&quot;</li><li>gcli.TagRuleSimple: flag:&quot;desc;required;default;shorts&quot;</li><li>gcli.TagRuleField (new): Uses the field name (SnakeCase) as the option name and reads metadata from independent tag keys.</li></ul><pre>type commonOpts struct {<br>    Verbose bool `flag:&quot;v&quot; desc:&quot;enable verbose output&quot;`<br>}<br><br>type demoOpts struct {<br>    commonOpts        // anonymous: expands into a --verbose/-v option<br>    UserName string `flag:&quot;u&quot; desc:&quot;the user name&quot; required:&quot;true&quot;`<br>    Age      int    `desc:&quot;the user age&quot; default:&quot;18&quot;`<br>}<br>c.MustFromStruct(&amp;demoOpts{}, gcli.TagRuleField)<br>// =&gt; options: --user-name/-u (required), --age (default 18), --verbose/-v</pre><p>The field rule keeps the option name tied to the struct field, while desc, default, and required live in their own tags, making it easier to read and maintain.</p><h3>5. Declarative Interactive Input</h3><p>If a required option is missing at runtime, you can now attach a Question. GCli will detect the empty value and prompt the user for input interactively.</p><pre>c.StrOpt2(&amp;token, &quot;token&quot;, &quot;the access token&quot;,<br>    gflag.WithQuestion(&quot;Please input your access token: &quot;))</pre><pre>$ myapp deploy<br>Please input your access token: ▮</pre><p>If a custom Collector is also set, it takes priority over Question.</p><h3>6. POSIX Short-Option Merging</h3><p>GCli now supports merging short flags (e.g., -a -u -x becomes -aux) in a POSIX style. This is disabled by default and can be enabled via Config.EnhanceShort.</p><pre>c.ParserCfg().EnhanceShort = gcli.EnhanceShortMerge  // 1: -aux =&gt; -a -u -x<br>c.ParserCfg().EnhanceShort = gcli.EnhanceShortAttach // 2: also -Ostdout =&gt; -O stdout</pre><p>It can also be enabled globally:</p><pre>gcli.SetEnhanceShort(gcli.EnhanceShortMerge)</pre><p>LevelConstantBehavior0EnhanceShortNoneOff (default), fully compatible with old behavior1EnhanceShortMergeSplit a group only when all members are bool shorts2EnhanceShortAttachAlso support value-attached form -Ostdout = -O stdout</p><p>A safety check is in place: a group is only split if every character is a boolean short option. Mixed forms like -aO (where O takes a value) are left untouched to prevent misparsing.</p><h3>7. Robustness Fixes</h3><p>Alongside new features, several long-standing issues were fixed:</p><ul><li>Panics are no longer swallowed: gflag.Parser.Parse previously ignored recovered panics. It now returns them as an error for easier upstream handling.</li><li>help &lt;command&gt; works on the first call: Fixed an issue where it might print unknown input command &quot;help&quot;.</li><li>findSimilarCmd fix: No longer writes a phantom help entry into the registry when an unknown command is run.</li><li>Command.Copy() fix: No longer clears the source command&#39;s hooks due to a shared pointer.</li></ul><h3>Breaking Changes &amp; Migration</h3><p>A few internal cleanups require adjustments if you depended on them:</p><p>BeforeAfterimport &quot;.../gcli/v3/helper&quot;Now internal; inline your own helperimport &quot;.../gcli/v3/gclicom&quot;Removed (unused after cliui migration)Global --verbose 4 flagEnv GCLI_VERBOSE=debug, or gcli.SetVerbose(gcli.VerbDebug) / gcli.SetDebugMode()</p><p>The --verbose 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.</p><p>Additionally, multiple App instances within the same process now share global options (verbose / help / version / strict / completion).</p><h3>Upgrade &amp; Examples</h3><pre>go get -u github.com/gookit/gcli/v3@latest</pre><p>The _examples/cmd directory in the repository includes runnable examples: struct-flag (field tags + anonymous structs), short-merge (short option merging), and ask-demo (interactive input).</p><blockquote>If you run into issues or have suggestions, feel free to open an issue or PR on <a href="https://github.com/gookit/gcli">GitHub</a>. For full API documentation, refer to <a href="https://pkg.go.dev/github.com/gookit/gcli/v3">GoDoc</a>.</blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=616348a61681" width="1" height="1" alt=""><hr><p><a href="https://itnext.io/gookit-gcli-v3-5-0-published-easy-to-use-feature-rich-go-command-line-application-616348a61681">gookit/gcli v3.5.0 published — easy-to-use, feature-rich Go command line application</a> was originally published in <a href="https://itnext.io/">ITNEXT</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[gookit/goutil v0.7.2 Released: Enhanced Features & Fixes for an Improved Development Experience]]></title>
            <link>https://itnext.io/gookit-goutil-v0-7-2-released-enhanced-features-fixes-for-an-improved-development-experience-a0f81826e55e?source=rss-9ffcd72d742------2</link>
            <guid isPermaLink="false">https://medium.com/p/a0f81826e55e</guid>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[github]]></category>
            <category><![CDATA[golang]]></category>
            <category><![CDATA[software-development]]></category>
            <dc:creator><![CDATA[Simon Tony]]></dc:creator>
            <pubDate>Thu, 20 Nov 2025 20:25:21 GMT</pubDate>
            <atom:updated>2025-11-20T20:25:21.198Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/592/1*2za99QZReWXH0Kw-ClgIpw.jpeg" /></figure><p>We’re excited to announce the release of <strong>gookit/goutil</strong> v0.7.2! This version brings numerous new features, optimizations, and bug fixes, further enhancing the stability and usability of this Go utility library.<br>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.</p><ul><li>Project Homepage: <a href="https://github.com/gookit/goutil">https://github.com/gookit/goutil</a></li><li>Full Changelog: <a href="https://github.com/gookit/goutil/releases/tag/v0.7.2">v0.7.2 Release Notes</a></li><li>Full documentation: <a href="https://zread.ai/gookit/goutil">https://zread.ai/gookit/goutil</a></li></ul><h3>🛠️ Key Bug Fixes</h3><p>This version fixes several key issues to ensure the stable operation of the library:</p><ul><li><strong>ccolor module</strong>: Fixed the issue where the <strong>NO_COLOR</strong> environment variable was not working correctly during printing, improving the compatibility of color output.</li><li><strong>cflag module</strong>:</li><li>Fixed a <strong>capp</strong> runtime error that occurred when parsing global flags.</li><li>Fixed the issue where the <strong>OnAppFlagParsed</strong> event was not triggered when no command was input, ensuring the integrity of the event handling logic.</li></ul><h3>✨ New Feature Highlights</h3><h3>Math Utilities</h3><ul><li>Added the <strong>IsInteger</strong> function for <strong>strictly checking if a value is of an integer type</strong> (e.g., <strong>int(x)</strong>, <strong>uint(x)</strong>).</li><li>Added <strong>StrictInt</strong> and <strong>StrictUint</strong> conversion functions for stricter integer type conversions.</li><li>Added the <strong>FormatBytes</strong> function to format byte sizes into <strong>human-readable strings</strong> (e.g., &quot;1.2 MB&quot;), convenient for log output and UI display.</li></ul><h3>String Utilities</h3><ul><li>Added the <strong>NumVersion</strong> function to quickly extract the numeric part from a version string.</li><li>Added the <strong>ReplaceVars</strong> function to support quick rendering of strings containing <strong>{var}</strong> placeholders, simplifying template replacement operations.</li><li>Added <strong>IsUpper</strong> and <strong>IsLower</strong> functions to check if a string is entirely uppercase or lowercase.</li><li>Added <strong>IsUint</strong> and <strong>IsPositiveNum</strong> functions to enhance string numeric validation capabilities.</li><li>Added <strong>BaseConvInt</strong> and <strong>BaseConvIntByTpl</strong> functions to support integer conversion between different bases.</li><li>Added the <strong>ContainsByteOne</strong> function to check if a string contains any of the specified bytes.</li><li>Optimized the <strong>MTimeBaseID</strong> function, which now supports bases greater than 36.</li></ul><h3>Filesystem Utilities</h3><ul><li>Added the <strong>EnsureDir</strong> function to ensure a directory exists (creates it if it doesn&#39;t).</li><li>Added <strong>HomeDir</strong>/<strong>UserHomeDir</strong> functions to get the user&#39;s home directory path.</li><li>Added <strong>CreateSymlink</strong> and <strong>IsSymlink</strong> functions to support the creation and detection of symbolic links.</li><li>Added <strong>FindAllInParentDirs</strong>, <strong>FindOneInParentDirs</strong>, and <strong>FindNameInParentDirs</strong> functions to support <strong>finding files in parent directories</strong>, simplifying the location of project configuration files.</li></ul><h3>Array/Slice Utilities</h3><ul><li>Added the <strong>ToMap</strong> function to convert a list (slice) to a map.</li><li>Added the <strong>Map1</strong> function to convert a list to a new list (similar to a map operation).</li></ul><h3>Map Utilities</h3><ul><li>Added the <strong>AppendSMap</strong> function for merging string maps.</li><li>Added the <strong>AliasesNames</strong> function for handling alias names.</li></ul><h3>Testing Utilities</h3><ul><li>Added the <strong>assert.StrContainsAll</strong> assertion function to check if a string contains all specified substrings.</li><li>Added the <strong>testutil.SafeBuffer</strong> type, a <strong>thread-safe buffer</strong> designed for concurrent testing scenarios.</li><li>Enhanced <strong>testutil.EchoServer</strong>, which now supports responding with 404, 405, 500, or custom status codes, facilitating the testing of HTTP client error handling logic.</li></ul><h3>Command-line Utilities</h3><ul><li>The <strong>cflag</strong> module has a new <strong>HelpOnEmptyArgs</strong> configuration option to automatically display help information when no arguments are provided, improving user experience.</li><li>Optimized the rendering of help information.</li><li>Added support for setting aliases for <strong>cflag/capp</strong> commands.</li></ul><h3>Time Utilities</h3><ul><li>Added the <strong>FormatDuration</strong> function to format time duration into a <strong>clock-like format</strong> (e.g., &quot;1h 23m 45s&quot;), perfect for displaying performance statistics.</li></ul><h3>⚡ Performance Optimizations &amp; Refactoring</h3><ul><li>Optimized the common logic of the <strong>ToStringWith</strong> function to improve conversion performance.</li><li>Refactored <strong>cflag.App</strong> into the subpackage <strong>capp.App</strong>, improving code organization.</li><li>Optimized the handling options logic in the <strong>ToInt64With</strong> function to enhance conversion efficiency.</li><li>Split integer conversion related code into <strong>conv2int.go</strong> and optimized the performance of all integer conversion related methods in <strong>mathutil</strong>.</li><li>Updated the <strong>VersionCompare</strong> function logic, fixing some comparison errors and improving the accuracy of version comparison.</li><li>The <strong>VarReplacer</strong> in <strong>textutil</strong> now supports parsing variable names as environment variables, enhancing dynamic configuration capabilities.</li></ul><h3>🌐 Platform Compatibility &amp; Development Workflow</h3><ul><li><strong>Added support for FreeBSD/Unix systems</strong>, expanding the platform coverage of the <strong>sysutil</strong> package.</li><li>Added comprehensive <strong>GitHub Copilot instructions</strong> for gookit/goutil, optimizing the AI-assisted development workflow.</li><li>Upgraded the <strong>github/codeql-action</strong> dependency from version 3 to 4, enhancing the security and capabilities of code analysis.</li></ul><h3>📝 Other Updates</h3><ul><li>Added a <strong>ShowLen</strong> option for <strong>dump</strong> output, allowing control over whether to display length information.</li><li>Updated the README generation CLI and related documentation, improving the project documentation generation process and templates.</li><li>Fixed unit test errors and unified the code style.</li><li>Updated some comments and code styles.</li></ul><h3>📦 How to Update</h3><p>You can use the following command to get the latest version:</p><pre>go get github.com/gookit/goutil@v0.7.2</pre><h3>Migration &amp; Compatibility Notes</h3><ul><li><strong>cflag -&gt; cflag/capp</strong> Subpackage Refactoring: <strong>cflag.App</strong> has been refactored to <strong>capp.App</strong>. If your code directly references <strong>cflag.App</strong>, please adjust:</li><li>Old: <strong>import &quot;github.com/gookit/goutil/cflag&quot;</strong></li><li>New: <strong>import &quot;github.com/gookit/goutil/capp&quot;</strong></li><li>And switch related types/calls to the App in the <strong>capp</strong> package.</li></ul><blockquote><em>TIP: This is a potential breaking change in this release, please check and update your references before upgrading.</em></blockquote><ul><li><strong>VersionCompare</strong> 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.</li></ul><h3>Example Snippets</h3><h4>Using mathutil.IsInteger / StrictInt</h4><pre>import &quot;github.com/gookit/goutil/mathutil&quot;<br>import &quot;github.com/gookit/goutil/testutil/assert&quot;<br><br>val := 123<br>if mathutil.IsInteger(val) {<br>    // true<br>}<br>i64, ok := mathutil.StrictInt(&quot;42&quot;) // Does not allow string to integer conversion<br>assert.False(t, ok)</pre><h4>Using strutil.ReplaceVars</h4><pre>import &quot;github.com/gookit/goutil/strutil&quot;<br>tpl := &quot;Hello, {name}! Today is {day}.&quot;<br>out := strutil.ReplaceVars(tpl, map[string]string{<br>    &quot;name&quot;: &quot;Gopher&quot;,<br>    &quot;day&quot;:  &quot;Wednesday&quot;,<br>})<br>// Output: &quot;Hello, Gopher! Today is Wednesday.&quot;</pre><h4>Using fsutil.EnsureDir</h4><pre>import &quot;github.com/gookit/goutil/fsutil&quot;<br><br>if err := fsutil.EnsureDir(&quot;/tmp/myapp/logs&quot;); err != nil {<br>    // Handle error<br>}</pre><blockquote>📖 more usage please see <a href="https://zread.ai/gookit/goutil">https://zread.ai/gookit/goutil</a></blockquote><h3>🤝 Contributing</h3><p>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 <a href="https://github.com/gookit/goutil/issues">GitHub Issues</a>.</p><h3>📖 More Information</h3><ul><li>Project Homepage: <a href="https://github.com/gookit/goutil">https://github.com/gookit/goutil</a></li><li>Full Changelog: <a href="https://github.com/gookit/goutil/releases/tag/v0.7.2">v0.7.2 Release Notes</a></li><li>Full documentation: <a href="https://zread.ai/gookit/goutil">https://zread.ai/gookit/goutil</a></li></ul><p>Enjoy the new features and improvements in v0.7.2</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a0f81826e55e" width="1" height="1" alt=""><hr><p><a href="https://itnext.io/gookit-goutil-v0-7-2-released-enhanced-features-fixes-for-an-improved-development-experience-a0f81826e55e">gookit/goutil v0.7.2 Released: Enhanced Features &amp; Fixes for an Improved Development Experience</a> was originally published in <a href="https://itnext.io/">ITNEXT</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[gookit/goutil v0.7.1 Released: Enhanced Toolset and Usage Optimization]]></title>
            <link>https://itnext.io/gookit-goutil-v0-7-1-released-enhanced-toolset-and-usage-optimization-0ff4322b8e03?source=rss-9ffcd72d742------2</link>
            <guid isPermaLink="false">https://medium.com/p/0ff4322b8e03</guid>
            <category><![CDATA[github]]></category>
            <category><![CDATA[golang]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[utilities]]></category>
            <dc:creator><![CDATA[Simon Tony]]></dc:creator>
            <pubDate>Wed, 13 Aug 2025 11:58:11 GMT</pubDate>
            <atom:updated>2025-08-13T11:58:11.353Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/807/1*HQ5ss0SOP8OSkHJosA8BLg.png" /></figure><p>We are pleased to announce the official release of ​<strong>​gookit/goutil v0.7.1​</strong>​! As the first patch version in the v0.7.0 series, this update brings ​<strong>​20+ new features, critical bug fixes, and multiple performance optimizations​</strong>​, further enhancing developer experience and toolset practicality.</p><p><a href="https://github.com/gookit/goutil">GitHub - gookit/goutil: 💪 Helper Utils(800+): int, byte, string, array/slice, map, struct, dump, convert/format, error, web/http, cli/flag, OS/ENV, filesystem, system, test/assert, time and more. Go 常用的工具函数：数字，字符串，数组，Map，结构体，反射，文本，文件，错误，时间日期，特殊处理，格式化，常用信息获取等等</a></p><h3>🚀 Release Highlights</h3><h3>1. ​​Enhanced Time Handling​​</h3><ul><li>timex.ToDuration() now supports negative values and composite time formats</li><li>Accepts time representations like -1.5h, 2d3h, and 2hour33min</li><li>Simplified time calculation logic for improved code readability</li></ul><h3>2. ​​Map Operation Enhancements​​</h3><ul><li>New AnyToStrMap() function for converting arbitrary types to string maps</li><li>Introduced L2StrMap type for two-level string map operations</li><li>Added utility methods to Data type: One(), StrOne(), BoolOne(), etc.</li><li>New slice-to-map conversion functions: SliceToSMap(), SliceToMap(), SliceToTypeMap()</li></ul><h3>3. ​​String Processing Improvements​​</h3><ul><li>Case-insensitive string check functions: IContainsOne(), IContainsAll()</li><li>Fast comma-joining function JoinComma()</li><li>IsVarName() function for variable name format validation</li></ul><h3>4. ​​Advanced Text Processing​​</h3><ul><li>New StrVarRenderer implementing shell-style variable interpolation</li><li>Simplified variable substitution and template rendering in tests</li></ul><h3>🛠️ Key Features Explained</h3><h3>⏱️ Enhanced Time Handling</h3><pre>// Supports negative values and composite formats<br>duration, err := timex.ToDuration(&quot;-1.5h&quot;)   // -1h30m<br>duration, err := timex.ToDuration(&quot;2d3h&quot;)   // 2d3h<br>duration, err := timex.ToDuration(&quot;2hour33min&quot;) // 2h33m</pre><h3>🗺️ Upgraded Map Operations</h3><pre>// Convert any type to string map<br>strMap := maputil.AnyToStrMap(someData)</pre><pre>// Multi-key retrieval<br>value := data.One(&quot;key1&quot;, &quot;key2&quot;, &quot;key3&quot;) // Returns first existing value</pre><pre>// Slice to map conversion<br>slice := []string{&quot;a:1&quot;, &quot;b:2&quot;, &quot;c:3&quot;}<br>sMap := maputil.SliceToSMap(slice, &quot;:&quot;) // {&quot;a&quot;: &quot;1&quot;, &quot;b&quot;: &quot;2&quot;, &quot;c&quot;: &quot;3&quot;}</pre><h3>📝 New String Utilities</h3><pre>// Case-insensitive containment check<br>strutil.IContainsOne(&quot;Hello World&quot;, []string{&quot;hello&quot;, &quot;golang&quot;}) // true</pre><pre>// Fast comma joining<br>result := strutil.JoinComma([]string{&quot;a&quot;, &quot;b&quot;, &quot;c&quot;}) // &quot;a,b,c&quot;</pre><pre>// Variable name validation<br>isValid := strutil.IsVarName(&quot;valid_var123&quot;) // true</pre><h3>🧪 Advanced Text Templating</h3><p>Implements simple shell-style variable interpolation with expressions:</p><ul><li>Variable format: $var_name, ${some_var}, ${top.sub_var}</li><li>Function calls: ${func_name($var_name, &#39;const string&#39;)}</li></ul><pre>// Using StrVarRenderer<br>renderer := textutil.NewStrVarRenderer()<br>result := renderer.Render(&quot;Hello ${name}&quot;, map[string]string{&quot;name&quot;: &quot;World&quot;}) // &quot;Hello World&quot;</pre><h3>🐛 Bug Fixes</h3><ul><li>Fixed conversion errors in maputil.TryAnyMap for non-string key maps</li><li>Resolved parsing issues in internal ParseEnvLines</li><li>Improved test stability across multiple cases</li></ul><h3>🔧 Additional Improvements</h3><h3>Core Enhancements</h3><ul><li>Renamed SMap to StrMap for naming consistency</li><li>Extracted common ENV parsing logic to internal package</li><li>Added DecodeFile() to jsonutil for simplified file operations</li></ul><h3>Refactoring</h3><ul><li>Optimized map operations for large data structures</li><li>Improved memory efficiency in string processing functions</li><li>Enhanced test suite stability</li></ul><h3>📚 Documentation &amp; Testing</h3><ul><li>Updated documentation for structs, testutil, and x/finder</li><li>Added 500+ new test cases</li><li>Modernized GitHub Actions workflows</li></ul><h3>🔄 Upgrade Guide</h3><h3>Installation</h3><pre>go get github.com/gookit/goutil/v0.7.1</pre><h3>Key Changes</h3><ol><li>​<strong>​API Change​</strong>​: SMap renamed to StrMap</li><li>​<strong>​New Features​</strong>​: Review added utilities for workflow simplification</li><li>​<strong>​Bug Fixes​</strong>​: Resolved previous TryAnyMap conversion issues</li></ol><h3>📈 Performance Metrics</h3><ul><li>15–20% faster map operations</li><li>30% reduced memory usage in string processing</li><li>98% test suite stability improvement</li></ul><h3>🤝 Community Contributions</h3><p>Special thanks to all contributors! We welcome feedback and contributions via <a href="https://github.com/gookit/goutil/issues">GitHub Issues</a>.</p><h3>📖 Resources</h3><ul><li>📄 <a href="https://github.com/gookit/goutil/compare/v0.7.0...v0.7.1">Full Changelog</a></li><li>📦 <a href="https://pkg.go.dev/github.com/gookit/goutil">Package Documentation</a></li><li>🐛 <a href="https://github.com/gookit/goutil/issues">Report Issues</a></li></ul><p>Upgrade now to leverage these powerful enhancements in your Go projects!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0ff4322b8e03" width="1" height="1" alt=""><hr><p><a href="https://itnext.io/gookit-goutil-v0-7-1-released-enhanced-toolset-and-usage-optimization-0ff4322b8e03">gookit/goutil v0.7.1 Released: Enhanced Toolset and Usage Optimization</a> was originally published in <a href="https://itnext.io/">ITNEXT</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Best distributed task scheduling framework — Openjob 1.0.7 released]]></title>
            <link>https://itnext.io/best-distributed-task-scheduling-framework-openjob-1-0-7-released-fa777111182c?source=rss-9ffcd72d742------2</link>
            <guid isPermaLink="false">https://medium.com/p/fa777111182c</guid>
            <category><![CDATA[java]]></category>
            <category><![CDATA[task-management]]></category>
            <category><![CDATA[scheduling]]></category>
            <category><![CDATA[big-data]]></category>
            <category><![CDATA[distributed-systems]]></category>
            <dc:creator><![CDATA[Simon Tony]]></dc:creator>
            <pubDate>Mon, 11 Sep 2023 08:57:44 GMT</pubDate>
            <atom:updated>2023-09-11T08:57:44.115Z</atom:updated>
            <content:encoded><![CDATA[<h3>Best distributed task scheduling framework — Openjob 1.0.7 released</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*AJHds_cWbYhIUi-x.png" /></figure><h3>Introduction</h3><p><a href="https://github.com/open-job/openjob">Openjob</a> is a new distributed task scheduling framework based on Akka architecture. Supports multiple cronjob, delay task, workflow, lightweight distributed computing, unlimited horizontal scaling, with high scalability and fault tolerance. Also has complete management, powerful alarm monitoring, and support multiple languages</p><ul><li>High reliability</li><li>Distributed with stateless design, using the Master/Worker architecture, supports multiple databases (MySQL/PostgreSQL/Oracle)</li><li>High performance</li><li>System uses a consistency sharding algorithm, lock-free design, task scheduling is accurate down to the second, supporting lightweight distributed computing and unlimited horizontal scaling</li><li>Cronjob</li><li>Supports distributed cronjob, fixed rate tasks, high-performance second tasks, and onetime tasks</li><li>Distributed computing</li><li>Supports multiple distributed programming models such as standalone, broadcast, Map, MapReduce, and sharding, easy to complete distributed computing for big data</li><li>Delay task</li><li>High performance delay task based on Redis , support multi-level storage, and provides rich statistics and reports</li><li>Workflow</li><li>Supports workflow scheduling engine, visual DAG design, and easy to complete complex task scheduling</li><li>Permission management</li><li>User management, supports menu, button, and data permission settings, flexible management of user permissions</li><li>Alarm monitoring</li><li>Overall monitoring metrics, rich and alarm in time, easy to locate and resolve online problem</li><li>Multiple languages</li><li>Support multiple languages such as Java, Go, PHP, and Python, as well as build with frameworks such as Spring Boot, Gin, and Swoft</li></ul><p>If you are looking for a high-performance distributed task scheduling framework that supports cronjob, delay task, lightweight computing, workflow, and supports multiple programming languages, then <a href="https://github.com/open-job/openjob">Openjob</a> is definitely the way to go.</p><h3>Released</h3><p><a href="https://github.com/open-job/openjob">Openjob</a> 1.0.7 newly supports H2/TiDB database and adds second delay task, fixed rate task, broadcast task, sharding task, and Map Reduce lightweight computing.</p><h4>Second Delay</h4><p>Second delay support scheduling at interval of 1 to 60 seconds. After each task execution is completed, scheduling will be triggered again at second intervals.</p><ul><li>High reliability: Second delay have high reliability characteristics. If a machine goes down, it can be re-run on another machine.</li><li>Rich task types: Second delay belong to the cron type and can be applied to all task types and execution types.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*xZclC65NPCeZha1D.png" /></figure><h4>Fixed Rate</h4><p>Crontab must be divisible by 60, if task executed every 50 minutes, Crontab cannot support it.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Oxxjwv6PYtp8hgdV.png" /></figure><h4>Map Reduce</h4><p>MapReduce is a lightweight distributed batch task. Implemented by the MapProcessor or MapReduceProcessor. Compared with big data batch task (such as Hadoop, Spark, etc.), MapReduce does not need to import data into the big data platform, and has no additional storage and computing costs. It can realize second data processing and the advantages of low cost, fast speed and programming.</p><h3>Example</h3><p>Define executor by annotation as follows:</p><pre>/**<br> * @author stelin swoft@qq.com<br> * @since 1.0.7<br> */<br>@Component(&quot;mapReduceTestProcessor&quot;)<br>public class MapReduceTestProcessor implements MapReduceProcessor {<br>    private static final Logger logger = LoggerFactory.getLogger(&quot;openjob&quot;);<br><br>    private static final String TWO_NAME = &quot;TASK_TWO&quot;;<br>    private static final String THREE_NAME = &quot;TASK_THREE&quot;;<br><br>    @Override<br>    public ProcessResult process(JobContext context) {<br>        if (context.isRoot()) {<br>            List&lt;MapChildTaskTest&gt; tasks = new ArrayList&lt;&gt;();<br>            for (int i = 1; i &lt; 5; i++) {<br>                tasks.add(new MapChildTaskTest(i));<br>            }<br>            logger.info(&quot;Map Reduce root task mapList={}&quot;, tasks);<br>            return this.map(tasks, TWO_NAME);<br>        }<br>        if (context.isTask(TWO_NAME)) {<br>            MapChildTaskTest task = (MapChildTaskTest) context.getTask();<br>            List&lt;MapChildTaskTest&gt; tasks = new ArrayList&lt;&gt;();<br>            for (int i = 1; i &lt; task.getId()*2; i++) {<br>                tasks.add(new MapChildTaskTest(i));<br>            }<br>            logger.info(&quot;Map Reduce task two mapList={}&quot;, tasks);<br>            return this.map(tasks, THREE_NAME);<br>        }<br>        if (context.isTask(THREE_NAME)) {<br>            MapChildTaskTest task = (MapChildTaskTest) context.getTask();<br>            logger.info(&quot;Map Reduce task three mapTask={}&quot;, task);<br>            return new ProcessResult(true, String.valueOf(task.getId() * 2));<br>        }<br>        return ProcessResult.success();<br>    }<br>    @Override<br>    public ProcessResult reduce(JobContext jobContext) {<br>        List&lt;String&gt; resultList = jobContext.getTaskResultList().stream().map(TaskResult::getResult)<br>                .collect(Collectors.toList());<br>        logger.info(&quot;Map Reduce resultList={}&quot;, resultList);<br>        return ProcessResult.success();<br>    }<br>    @Data<br>    @AllArgsConstructor<br>    @NoArgsConstructor<br>    public static class MapChildTaskTest {<br>        private Integer id;<br>    }<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*mtFgYxkb9BhlLID6.png" /></figure><h4>Sharding</h4><p><strong>Sharding model includes static sharding and dynamic sharding:</strong></p><ul><li><strong>Static sharding: The main scenario is to process a fixed number of sharding. For example, there are 256 databases in sub-databases, which requires many worker to be processed in a distributed.</strong></li><li><strong>Dynamic sharding: The main scenario is distributed processing of unknown data. For example, a large table is constantly changing and requires distributed batch running.</strong></li></ul><h3>Feature</h3><ul><li><strong>Compatible with elastic-job’s static sharding.</strong></li><li><strong>Supports languages: Java, PHP, Python, Shell, and Go.</strong></li><li><strong>High availability: When the worker that executes a sharding task becomes abnormal, it will be dynamically assigned to other normal worker to execute the task.</strong></li><li><strong>Flow control: You can set the number of concurrent subtasks for a single worker. For example, if there are 100 sharding and a total of 3 worker, up to 5 sharding can be controlled to execute concurrently, and the others are waiting in the queue.</strong></li><li><strong>Retry on failure: Automatically retry when subtask execution fails.</strong></li></ul><h3>Example</h3><p><strong>Define executor by annotation as follows:</strong></p><pre>/**<br> * @author stelin swoft@qq.com<br> * @since 1.0.7<br> */<br>@Component<br>public class ShardingAnnotationProcessor {<br>    private static final Logger logger = LoggerFactory.getLogger(&quot;openjob&quot;);<br><br>    @Openjob(&quot;annotationShardingProcessor&quot;)<br>    public ProcessResult shardingProcessor(JobContext jobContext) {<br>        logger.info(&quot;Sharding annotation processor execute success! shardingId={} shardingNum={} shardingParams={}&quot;,<br>                jobContext.getShardingId(), jobContext.getShardingNum(), jobContext.getShardingParam());<br>        logger.info(&quot;jobContext={}&quot;, jobContext);<br>        return ProcessResult.success();<br>    }<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*yt3G4S5hrFk4PM92.png" /></figure><h4>Broadcast</h4><p><em>Broadcast task instance will be broadcast to all workers to the application for execution. Task will be completed when all workers have completed execution. If any worker execute fail, the task will fail.</em></p><h3>Application scenarios​</h3><p><strong><em>Batch operation</em></strong><a href="http://localhost:3000/docs/developer-guide/distributed-model/broadcast#batch-operation%20%22Direct%20link%20to%20Batch%20operation%22"><em>​</em></a></p><ul><li><em>Broadcast to all workers to run a script.</em></li><li><em>Broadcast to all workers to cleaning data regularly.</em></li><li><em>Dynamically start a service on each worker</em></li></ul><p><strong><em>Data aggregation</em></strong><a href="http://localhost:3000/docs/developer-guide/distributed-model/broadcast#data-aggregation%20%22Direct%20link%20to%20Data%20aggregation%22"><em>​</em></a></p><ul><li><em>Initialize using JavaProcessor-&gt;preProcess</em></li><li><em>When each worker executes the process, it returns the result according to the business.</em></li><li><em>Execute postProcess to obtain the execution results of all workers.</em></li></ul><h3>Feature​</h3><p><em>There are many types of broadcast tasks, such as script or Java Processor. If Java processor, it can supports preProcess and postProcess advanced features.</em></p><ul><li><em>preProcess will be executed before each worker execute process, and will only be executed once.</em></li><li><em>process is the actual task execution.</em></li><li><em>postProcess will be executed once after the process execution on each worker is completed and successfully executed, and the results can be returned as data transmission.</em></li></ul><h4>Example</h4><p><em>Define executor by annotation as follows:</em></p><pre>/**<br> * @author stelin swoft@qq.com<br> * @since 1.0.7<br> */<br>@Component(&quot;broadcastPostProcessor&quot;)<br>public class BroadcastProcessor implements JavaProcessor {<br>    private static final Logger logger = LoggerFactory.getLogger(&quot;openjob&quot;);<br><br>    @Override<br>    public void preProcess(JobContext context) {<br>        logger.info(&quot;Broadcast pre process!&quot;);<br>    }<br>    @Override<br>    public ProcessResult process(JobContext context) throws Exception {<br>        logger.info(&quot;Broadcast process!&quot;);<br>        return new ProcessResult(true, &quot;{\&quot;data\&quot;:\&quot;result data\&quot;}&quot;);<br>    }<br>    @Override<br>    public ProcessResult postProcess(JobContext context) {<br>        logger.info(&quot;Broadcast post process taskList={}&quot;, context.getTaskResultList());<br>        System.out.println(context.getTaskResultList());<br>        return ProcessResult.success();<br>    }<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*y2oBUpDSt3zf0a_v.png" /></figure><h3>More</h3><ul><li>Official website: <a href="https://openjob.io/">https://openjob.io</a></li><li>Github: <a href="https://github.com/open-job/openjob">https://github.com/open-job/openjob</a></li><li>Live demo: <a href="https://demo.openjob.io/">https://demo.openjob.io</a></li><li>username: openjob</li><li>password: <a href="http://openjob.io/">openjob.io</a></li></ul><p><a href="https://github.com/open-job/openjob">GitHub - open-job/openjob: Distributed high performance task scheduling framework</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fa777111182c" width="1" height="1" alt=""><hr><p><a href="https://itnext.io/best-distributed-task-scheduling-framework-openjob-1-0-7-released-fa777111182c">Best distributed task scheduling framework — Openjob 1.0.7 released</a> was originally published in <a href="https://itnext.io/">ITNEXT</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[More powerful and intelligent task scheduling framework — Openjob 1.0.6 published]]></title>
            <link>https://itnext.io/more-powerful-and-intelligent-task-scheduling-framework-openjob-1-0-6-published-7ef5ac75274f?source=rss-9ffcd72d742------2</link>
            <guid isPermaLink="false">https://medium.com/p/7ef5ac75274f</guid>
            <category><![CDATA[distributed-systems]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[task-management]]></category>
            <category><![CDATA[scheduling]]></category>
            <category><![CDATA[big-data]]></category>
            <dc:creator><![CDATA[Simon Tony]]></dc:creator>
            <pubDate>Mon, 07 Aug 2023 10:08:33 GMT</pubDate>
            <atom:updated>2023-08-07T10:08:33.603Z</atom:updated>
            <content:encoded><![CDATA[<h3>More powerful and intelligent task scheduling framework — Openjob 1.0.6 published</h3><h3>More powerful and intelligent task scheduling framework.</h3><h3>Introduction</h3><p>Openjob is a new distributed task scheduling framework based on Akka architecture. Supports multiple cronjob, delay task, workflow, lightweight distributed computing, unlimited horizontal scaling, with high scalability and fault tolerance. It also has complete management, powerful alarm monitoring, and support multiple languages</p><ul><li>Complete task log, and suppport storage (H2/Mysql/Elasticsearch).</li><li>detailed recording of task execution stack information</li><li>Provides task event monitoring alarms, detailed alarm histories, and support notifications with Webhook,Wecom, ,Dingding and Feishu triggers.</li><li>Designed with namespace, support button-level access and easy to manage complex project.</li><li>Supports multiple programming languages, such as Java, Go, PHP and Python and various languages is very friendly.</li></ul><p>If you are looking for a high-performance distributed task scheduling framework that supports cronjob, delay task, lightweight computing, workflow, and supports multiple programming languages, then Openjob is definitely the way to go.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*_3aJVgb85yvT48qS.png" /></figure><h3>Feature</h3><p>Openjob not only supports basic cronjob, but also provides delayed jobs, distributed computing, and workflow</p><h4>Cronjob</h4><ul><li><strong>Cronjob</strong>, support Unix Crontab expression</li><li><strong>Second</strong>, execution cycle less than 60 seconds</li><li><strong>Fixed rate</strong>, execute tasks at a fixed frequency with minute unit</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*hoISh8WCDJQkpnfX.png" /></figure><h4>Delay Task</h4><ul><li>Distributed, high-performance delay task based on Redis, and providing rich reports and statistics</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*IWccwug_9uXNS77c.png" /></figure><h4>Distributed Computing</h4><ul><li><strong>Standalone</strong>, execute on a worker client</li><li><strong>Broadcast</strong>, execute on all worker clients</li><li><strong>Map</strong>, a map function can distribute big data to multiple machines for execution, like Hadoop map</li><li><strong>MapReduce</strong>, MapReduce is an extension of the Map.After all map sub-tasks are completed, the Reduce method is executed, which can process the results and data of the task execution in the Reduce method.</li><li><strong>Sharding</strong>, like Elastic-Job model, configure sharding numbers on the management, which can be scheduled to different client by sharding, and supports multiple languages.</li></ul><h4>Processor</h4><ul><li><strong>Processor</strong>, execute by function or class(support Java/Golang/PH)</li><li><strong>HTTP</strong>, http request, used to periodically request an HTTP</li><li><strong>Kettle</strong>, built-in Kettle command executor</li><li><strong>Shell</strong>, shell script</li></ul><h4>Visual operations</h4><ul><li><strong>Dashboard</strong>, rich task statistics and reports</li><li><strong>Task history</strong>, task execution history records</li><li><strong>Task log</strong>, complete task log, and suppport storage (H2/Mysql/Elasticsearch).</li><li><strong>Task running stack</strong>, detailed recording of task execution stack information</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*0U2cKl6m8PIsVGdq.png" /></figure><h4>Alarms and permissions</h4><ul><li>Provides task event monitoring alarms, detailed alarm histories, and support notifications with Webhook,Wecom, Dingding and Feishu triggers.</li><li>Designed with namespace, support button-level access and easy to manage complex project.</li></ul><h4>Multiple languages</h4><ul><li><strong>Java </strong>java and its frameworks, with native support.</li><li><strong>Go </strong>golang support use go mod install</li><li><strong>PHP</strong> PHP support use Golang agent to execute task by command mode 。Swoole frameworks support composer install.</li><li><strong>Python</strong> python support use Golang agent to execute task by command mode</li></ul><h3>Application Scenario</h3><p>Openjob is well-suited for business scenarios that have task schedule and delay task. such as every day to clean data and report generation. It is also suitable for lightweight computing, and Map/MapReduce can process big data computing. For complex task flows or workflow, it can design workflow with UI</p><h3>Update</h3><p>Openjob v1.0.6 add monitoring and alarm, fixed known issues</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*fFKwOaHwVSpW01H-.png" /></figure><p><strong>Feature</strong></p><p>[<a href="https://github.com/open-job/openjob/pull/141">#141</a>] Add alarm and monitoring(dingding/wecom/feishu/webhook)<br>[<a href="https://github.com/open-job/openjob/pull/141">#141</a>] Add execute timeout for cronjob<br>[<a href="https://github.com/open-job/openjob/pull/141">#141</a>] Add child fail status<br>[<a href="https://github.com/open-job/openjob/pull/144">#144</a>] Add next execute time<br>[<a href="https://github.com/open-job/openjob/pull/144">#144</a>] Add personal page<br>[<a href="https://github.com/open-job/openjob/pull/144">#144</a>] Add running status to dashboard</p><p><strong>Bugfix</strong></p><p>[<a href="https://github.com/open-job/openjob/pull/144">#144</a>] Fixed big task log</p><p><strong>Optimize</strong></p><p>[<a href="https://github.com/open-job/openjob/pull/144">#144</a>] Auto create index for Elasticsearch7<br>[<a href="https://github.com/open-job/openjob/pull/144">#144</a>] User default avatar</p><h3>More</h3><ul><li>Official website: <a href="https://openjob.io/">https://openjob.io</a></li><li>Github: <a href="https://github.com/open-job/openjob">https://github.com/open-job/openjob</a></li><li>Live demo: <a href="https://demo.openjob.io/">https://demo.openjob.io</a></li><li>username: openjob</li><li>password: <a href="http://openjob.io/">openjob.io</a></li></ul><p><a href="https://github.com/open-job/openjob">GitHub - open-job/openjob: Distributed high performance task scheduling framework</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7ef5ac75274f" width="1" height="1" alt=""><hr><p><a href="https://itnext.io/more-powerful-and-intelligent-task-scheduling-framework-openjob-1-0-6-published-7ef5ac75274f">More powerful and intelligent task scheduling framework — Openjob 1.0.6 published</a> was originally published in <a href="https://itnext.io/">ITNEXT</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[gookit/goutil — released v0.6.10, an extension library of common Go features]]></title>
            <link>https://itnext.io/gookit-goutil-released-v0-6-10-an-extension-library-of-common-go-features-14e529540b0?source=rss-9ffcd72d742------2</link>
            <guid isPermaLink="false">https://medium.com/p/14e529540b0</guid>
            <category><![CDATA[utilities]]></category>
            <category><![CDATA[libraries]]></category>
            <category><![CDATA[development]]></category>
            <category><![CDATA[go]]></category>
            <category><![CDATA[tools]]></category>
            <dc:creator><![CDATA[Simon Tony]]></dc:creator>
            <pubDate>Wed, 28 Jun 2023 14:55:28 GMT</pubDate>
            <atom:updated>2023-07-05T06:41:15.160Z</atom:updated>
            <content:encoded><![CDATA[<h3>gookit/goutil — released v0.6.10, an extension library of common Go features</h3><p>gookit/goutil An extended library of Go&#39;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.</p><blockquote><strong><em>Github</em></strong><em>: </em><a href="https://github.com/gookit/goutil"><em>https://github.com/gookit/goutil</em></a></blockquote><h3>v0.6.10 change log</h3><blockquote><em>Full changelog </em><a href="https://github.com/gookit/goutil/compare/v0.6.9...v0.6.10"><em>v0.6.9…v0.6.10</em></a></blockquote><h3>✨ New features</h3><ul><li>✨ feat: testutil — adds a new child package fakeobj to create fake objects for testing</li><li>✨ feat: testutil — adds a new function NewEchoServer() to start the echo server</li><li>✨ feat: byteutil — Buffer adds more useful methods for ease of use</li><li>✨ feat: dump — Special handling of custom int, uint type values, will print instructions in String format</li><li>✨ feat: fsutil — adds a new function to Glob() quickly list matching files</li><li>♻️ feat: httpreq — Refactoring the built-in HTTP client httpreq logic</li></ul><h3>👔 Update Adjustments</h3><ul><li>👔 up: structs — Update and support InitDefaults() for struct slice fields that are not null</li><li>👔 up: maputil — SimpleMerge() Supports deep merging of map[string]any data</li></ul><h3>Other adjustments</h3><ul><li>✅ test: Update some documentation and fix some unit tests</li><li>⬆️ dep: Update golang.org/x depends on the latest version</li></ul><h3>Some feature usage examples</h3><h4>dump — prints custom types</h4><p>dump — special handles custom int, uint type values, and will print instructions in String format</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*67jv52U1tIbesvKs.png" /></figure><h4>Initialize the struct</h4><ul><li>Support for initializing the use of environment variables</li><li>Support initializing slice fields and nesting structs</li></ul><pre>type ExtraDefault struct {<br>    City   string `default:&quot;some where&quot;`<br>    Github string `default:&quot;${ GITHUB_ADDR }&quot;`<br>}<br><br>type User struct {<br>    Name  string        `default:&quot;inhere&quot;`<br>    Age   int           `default:&quot;300&quot;`<br>    Extra *ExtraDefault `default:&quot;&quot;` // mark: need init<br>}<br><br>optFn := func(opt *structs.InitOptions) {<br>    opt.ParseEnv = true<br>}<br>obj := &amp;User{}<br>err := structs.InitDefaults(obj, optFn)<br>goutil.PanicErr(err)<br><br>dump.P(obj)</pre><p><strong>Initialization result:</strong></p><pre>&amp;structs_test.User {<br>  Name: string(&quot;inhere&quot;), #len=6<br>  Age: int(300),<br>  Extra: &amp;structs_test.ExtraDefault {<br>    City: string(&quot;some where&quot;), #len=10<br>    Github: string(&quot;https://some .... url&quot;), #len=21<br>  },<br>},</pre><h4>Test with Echo Server</h4><p>Use testutil.NewEchoServer() to quickly create an HTTP echo server. Convenient for testing HTTP requests, responses, etc.</p><p><strong>Usage examples:</strong></p><pre>var testSrvAddr string<br><br>func TestMain(m *testing.M) {<br>    s := testutil.NewEchoServer()<br>    defer s.Close()<br><br>    testSrvAddr = &quot;http://&quot; + s.Listener.Addr().String()<br>    fmt.Println(&quot;server addr:&quot;, testSrvAddr)<br>    m.Run()<br>}<br><br>func TestNewEchoServer(t *testing.T) {<br>    // can direct request the echo server<br>    r, err := http.Post(testSrvAddr, &quot;text/plain&quot;, strings.NewReader(&quot;hello!&quot;))<br>    assert.NoErr(t, err)<br>    // binding response to the testutil.EchoReply<br>    rr := testutil.ParseRespToReply(r)<br>    <br>    dump.P(rr)<br>    assert.Eq(t, &quot;POST&quot;, rr.Method)<br>    assert.Eq(t, &quot;text/plain&quot;, rr.ContentType())<br>    assert.Eq(t, &quot;hello!&quot;, rr.Body)<br>}</pre><h3>v0.6.9 change log</h3><blockquote><em>Full changelog </em><a href="https://github.com/gookit/goutil/compare/v0.6.8...v0.6.9"><em>v0.6.8…v0.6.9</em></a></blockquote><h3>✨ New features</h3><ul><li>strutil — Added ToByteSize(),SafeByteSize() for converting size string (eg: 5MB) to byte size</li><li>strutil has added ParseSizeRange() to facilitate and quickly parse string size range expressions eg: 200kb~50mb</li><li>strutil — Added new utility functions: DatetimeNo(), RandWithTpl(), SimpleMatch()</li><li>byteutil — Add new utility functions: Random(), AppendAny()</li><li>fsutil — Add new utility functions: ReadOrErr(), ReadStringOrErr()</li><li>cliutil — Add new utility functions: Confirm(), ReadAsBool()</li><li>errorx — Add new utility functions Err(), Errf() for easy creation of error</li><li>structs — InitDefaults() enhanced to support nested structure pointer initialization</li><li>structs — SetValues() enhanced to support setting and processing structure pointer values</li><li>structs — Add ToSMap(), TryToSMap(), TryToSMap() to convert structure to string map</li><li>testutil/assert new assertion methods NotContainsKey(), NotContainsKeys()</li><li>Reflects new utility methods UnexportedValue(), SetUnexportedValue()</li><li>maputil new tool functions HasOneKey(), CombineToMap(), TryAnyMap()</li><li>Added utility functions AnyToSlice(), CombineToMap() in arrutil</li><li>jsonutil has added tool methods IsJSON(), IsJSONFast() to check whether it is JSON</li><li>♻️ fsutil/finder file and directory search reconstruction, enhanced functions, more convenient to use</li><li>timex new utility functions NowAddSec(), IsDuration(), InRange()</li><li>timex ToDuration() enhanced, support unit d, w and long unit hour, min, sec</li><li>mathutil new tool methods InRange(), OutRange(), InUintRange()</li><li>fsutil new tool method RemoveSub() to quickly delete subdirectories and files</li><li>maputil GetByPath() is enhanced to support matching paths similar to top.*.field to get values</li></ul><h3>👔 Update adjustments</h3><ul><li>strutil optimization ToByteSize() and GlobMatch() logic</li><li>struts — InitDefaults() supports initializing non-zero ptr substructure fields</li><li>💥 struts — InitDefaults() To initialize a substructure, the field must be marked with default:&quot;&quot;</li></ul><h3>Other adjustments</h3><ul><li>📝 doc: README documentation updated, some subpackages added README</li><li>✅ test: Added and fixed some unit tests</li><li>⬆️ dep: update golang.org/x dependencies to latest version</li></ul><h3>Some feature usage examples</h3><h4>finder — find files or dir</h4><p>fsutil/finder Provides a simple and fast way to find matching files and directories.</p><ul><li>Built-in support Name, extension, suffix, prefix, glob, path, etc. include or exclude</li><li>Support matching file size, modification time (ModTime) and other extended matching methods</li></ul><pre>ff := finder.NewFinder(&quot;/path/to/dir/&quot;).<br>    // OnlyFindDir(). // default find files<br>    UseAbsPath().<br>    WithoutDotDir().<br>    WithDirName(&quot;testdata&quot;).<br>    Include(finder.HumanModTime(&quot;&lt;10m&quot;)). // latest 10 minutes, to Now<br>    Include(finder.HumanSize(&quot;500kb~10mb&quot;)) // limit file size<br><br>// Find() returns chan, can be for handling<br>for el := range f.Find() {<br>    fmt.Println(el.Path())<br>}</pre><h4>strutil.ParseSizeRange</h4><p>It is easy to parse the string size range to byte size.</p><pre>opt := &amp;strutil.ParseSizeOpt{}<br><br>mix, max, err := strutil.ParseSizeRange(&quot;1kb~1mb&quot;, opt)<br>goutil.PanicErr(err)<br>fmt.Println(min, max) // OUTPUT: 1024, 1048576</pre><p><strong>Examples of supported expression formats:</strong></p><pre>&quot;1KB~2MB&quot;       =&gt; 1KB to 2MB<br>&quot;-1KB&quot;          =&gt; &lt;1KB<br>&quot;~1MB&quot;          =&gt; &lt;1MB<br>&quot;&lt; 1KB&quot;         =&gt; &lt;1KB<br>&quot;1KB&quot;           =&gt; &gt;1KB<br>&quot;1KB~&quot;          =&gt; &gt;1KB<br>&quot;&gt;1KB&quot;          =&gt; &gt;1KB<br>&quot;+1KB&quot;          =&gt; &gt;1KB</pre><h4>timex.ParseRange()</h4><p>timex.ParseRange() It is easy to quickly parse the relative time size range or keyword into time.Time</p><pre>start, end, err := ParseRange(&quot;-1h~1h&quot;, nil)<br>goutil.PanicErr(err)<br><br>fmt.Println(start, end)</pre><p><strong>Examples of supported expression formats:</strong></p><pre>&quot;-5h~-1h&quot;           =&gt; 5 hours ago to 1 hour ago<br>&quot;1h~5h&quot;             =&gt; 1 hour after to 5 hours after<br>&quot;-1h~1h&quot;            =&gt; 1 hour ago to 1 hour after<br>&quot;-1h&quot;               =&gt; 1 hour ago to feature. eq &quot;-1h~&quot;<br>&quot;-1h~0&quot;             =&gt; 1 hour ago to now.<br>&quot;&lt; -1h&quot; OR &quot;~-1h&quot;   =&gt; 1 hour ago.<br>&quot;&gt; 1h&quot; OR &quot;1h&quot;      =&gt; 1 hour after to feature<br><br>// keyword: now, today, yesterday, tomorrow<br>&quot;today&quot;          =&gt; today start to today end<br>&quot;yesterday&quot;      =&gt; yesterday start to yesterday end<br>&quot;tomorrow&quot;       =&gt; tomorrow start to tomorrow end</pre><h3>More usage</h3><p>For more instructions, see Unit tests for <a href="https://github.com/gookit/slog/blob/master/README.zh-CN.md">README</a> and related methods</p><p><a href="https://github.com/gookit/goutil">GitHub - gookit/goutil: 💪 Helper Utils(800+): int, byte, string, array/slice, map, struct, dump, convert/format, error, web/http, cli/flag, OS/ENV, filesystem, system, test/assert, time and more. Go 常用的工具函数：数字，字符串，数组，Map，结构体，反射，文本，文件，错误，时间日期，特殊处理，格式化，常用信息获取等等</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=14e529540b0" width="1" height="1" alt=""><hr><p><a href="https://itnext.io/gookit-goutil-released-v0-6-10-an-extension-library-of-common-go-features-14e529540b0">gookit/goutil — released v0.6.10, an extension library of common Go features</a> was originally published in <a href="https://itnext.io/">ITNEXT</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Openjob: best distributed task scheduling framework]]></title>
            <link>https://itnext.io/openjob-best-distributed-task-scheduling-framework-b6e326c0eaa9?source=rss-9ffcd72d742------2</link>
            <guid isPermaLink="false">https://medium.com/p/b6e326c0eaa9</guid>
            <category><![CDATA[jobs]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[task-management]]></category>
            <category><![CDATA[big-data]]></category>
            <category><![CDATA[scheduling]]></category>
            <dc:creator><![CDATA[Simon Tony]]></dc:creator>
            <pubDate>Mon, 19 Jun 2023 07:05:47 GMT</pubDate>
            <atom:updated>2023-06-19T08:03:42.962Z</atom:updated>
            <content:encoded><![CDATA[<h3>What is Openjob?</h3><p>Openjob is a new distributed task scheduling framework based on Akka. Supports a variety of cronjob, delayed jobs, and workflow. Uses a consistency sharding algorithm and supports unlimited horizontal scaling.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*76UyFCGzyRJ0Ft5H.png" /></figure><h3>Feature</h3><p>Openjob not only supports basic cronjob, but also provides delayed jobs, distributed computing, and workflow</p><h4>Cronjob</h4><ul><li><strong>Cronjob，</strong>support Unix Crontab expression</li><li><strong>Second，</strong>execution cycle less than 60 seconds</li><li><strong>Fixed rate，</strong>execute tasks at a fixed frequency with minute unit</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*GU5_M7DsOsKzgVBd.png" /></figure><p>Delay Task</p><ul><li>Distributed, high-performance delay task based on Redis, and providing rich reports and statistics</li></ul><p>Distributed Computing</p><ul><li><strong>Standalone，</strong> execute on a worker client</li><li><strong>Broadcast，</strong> execute on all worker clients</li><li><strong>Map，</strong>a map function can distribute big data to multiple machines for execution, like Hadoop map</li><li><strong>MapReduce，</strong> MapReduce is an extension of the Map.After all map sub-tasks are completed, the Reduce method is executed, which can process the results and data of the task execution in the Reduce method.</li><li><strong>Sharding，</strong> like Elastic-Job model, configure sharding numbers on the management, which can be scheduled to different client by sharding, and supports multiple languages.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*UOR8ons1M2Ezb1be.png" /></figure><p>Processor</p><ul><li><strong>Processor</strong>, execute by function or class(support Java/Golang/PH)</li><li><strong>HTTP</strong>, http request, used to periodically request an HTTP</li><li><strong>Shell</strong>,shell script</li></ul><p>Visual operations</p><ul><li><strong>Dashboard</strong>，rich task statistics and reports</li><li><strong>Task history</strong>，task execution history records</li><li><strong>Task log</strong>，complete task log, and suppport storage (H2/Mysql/Elasticsearch).</li><li><strong>Task running stack</strong>，detailed recording of task execution stack information</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*3rhHfe1fBVkYEvYl.png" /></figure><p>Alarms and permissions</p><ul><li>Provides task event monitoring alarms, detailed alarm histories, and support notifications with WeChat, Feishu, and webhook triggers.</li><li>Designed with namespace, support button-level access and easy to manage complex project.</li></ul><p>Multiple languages</p><ul><li><strong>Java</strong> java and its frameworks, with native support.</li><li><strong>Go</strong> golang support use go mod install</li><li><strong>PHP</strong> PHP support use Golang agent to execute task by shell mode 。Swoole frameworks support composer install.</li><li><strong>Python</strong> python support use Golang agent to execute task by shell mode</li></ul><h3>Use cases</h3><p>Openjob is well-suited for business scenarios that have task schedule and delay task. such as every day to clean data and report generation. It is also suitable for lightweight computing, and Map/MapReduce can process big data computing. For complex task flows or workflow, it can design workflow with UI</p><h3>Open source</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4DpeuO4EwNAJpm8c8-l2nA.png" /></figure><h3>More</h3><ul><li>Official website: <a href="https://openjob.io/">https://openjob.io</a></li><li>Github: <a href="https://github.com/open-job/openjob">https://github.com/open-job/openjob</a></li><li>Live demo: <a href="https://demo.openjob.io/">https://demo.openjob.io</a></li></ul><p><a href="https://github.com/open-job/openjob">GitHub - open-job/openjob: Distributed high performance task scheduling framework</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b6e326c0eaa9" width="1" height="1" alt=""><hr><p><a href="https://itnext.io/openjob-best-distributed-task-scheduling-framework-b6e326c0eaa9">Openjob: best distributed task scheduling framework</a> was originally published in <a href="https://itnext.io/">ITNEXT</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>