Skip to content
This repository was archived by the owner on Apr 28, 2026. It is now read-only.

Commit af98d1e

Browse files
committed
add -c to clipboard
1 parent b11d7df commit af98d1e

4 files changed

Lines changed: 398 additions & 42 deletions

File tree

README.md

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Download the version for your operating system from the [Releases](https://githu
66

77
> To use the default behavior of `pmc`, your device needs a `git` environment.
88
9-
After installation, you can run `pmc -v` to verify it.
9+
After installation, you can run `pmc -v` to verify it.
1010
If installed correctly, it will output: `pmc -- pack-my-code. version <your version number>`.
1111

1212
## Usage
@@ -22,7 +22,7 @@ pmc .
2222
pmc ./src
2323
```
2424

25-
`pmc .` can be shortened to `pmc`.
25+
`pmc .` can be shortened to `pmc`.
2626
`pmc` also has the following default behaviors:
2727

2828
- Follows `.gitignore`
@@ -59,12 +59,12 @@ pmc . -m "*.lua"
5959
pmc . -m "src/,README.md"
6060
```
6161

62-
> `-m` has lower priority than `-x`.
62+
> `-m` has lower priority than `-x`.
6363
> Even if a file matches `-m`, it will still be excluded if it also matches `-x`.
6464
6565
### `-r`: Ignore `.gitignore`
6666

67-
By default, `pmc` follows `.gitignore` through `git`.
67+
By default, `pmc` follows `.gitignore` through `git`.
6868
With `-r`, it performs a direct filesystem scan and ignores `.gitignore`.
6969

7070
This option is useful when:
@@ -82,13 +82,13 @@ Appends statistical information at the end of the output.
8282

8383
### `-w`: Wrapping Mode
8484

85-
Specifies how each files content is wrapped in output.
85+
Specifies how each file's content is wrapped in output.
8686

8787
`-w` supports the following modes:
8888

8989
- `md` (default): wrapped as Markdown code blocks
9090
- `nil`: no wrapping
91-
- `block`: wrapped in a block format
91+
- `block`: wrapped in a "block" format
9292

9393
### `-p`: Path Mode
9494

@@ -104,7 +104,7 @@ Specifies how paths are displayed before each file block.
104104

105105
Enables YAML-mode output.
106106

107-
In YAML mode, output no longer follows normal plain-text concatenation.
107+
In YAML mode, output no longer follows normal plain-text concatenation.
108108
Instead, it uses its own tree hierarchy structure to organize content.
109109

110110
Therefore, in `-y` mode, the following options cannot be used together:
@@ -116,6 +116,22 @@ Therefore, in `-y` mode, the following options cannot be used together:
116116

117117
That is, YAML mode handles structure, paths, and content organization by itself.
118118

119+
In YAML mode, output must be directed somewhere other than stdout: use `-o` to write to a `.yaml`/`.yml` file, or `-c` to copy to clipboard, or both.
120+
121+
### `-c`: Copy to Clipboard
122+
123+
Copies the output directly to the system clipboard instead of printing to terminal.
124+
125+
Platform support:
126+
127+
- **Windows**: uses the built-in `clip` command (no extra installation needed)
128+
- **Linux**: requires one of the following clipboard tools to be installed:
129+
- `xclip` (X11)
130+
- `xsel` (X11)
131+
- `wl-copy` (Wayland, part of `wl-clipboard`)
132+
133+
`-c` and `-o` can be used together — the output will be written to the file **and** copied to the clipboard simultaneously. When `-c` or `-o` is specified, stdout is silent.
134+
119135
### `-o`: Output Redirection
120136

121137
Write results directly to a specified file instead of printing to terminal.
@@ -161,10 +177,35 @@ Export in YAML mode:
161177
pmc . -y -o context.yaml
162178
```
163179

180+
Copy output to clipboard:
181+
182+
```bash
183+
pmc . -c
184+
```
185+
186+
Copy to clipboard with tree and stats:
187+
188+
```bash
189+
pmc . -t -s -c
190+
```
191+
192+
Write to file and clipboard at the same time:
193+
194+
```bash
195+
pmc . -o context.md -c
196+
```
197+
198+
Export YAML to clipboard:
199+
200+
```bash
201+
pmc . -y -c
202+
```
203+
164204
## Notice
165205

166206
- Files larger than **1 MB** are automatically skipped.
167207
- Known binary formats and files containing null bytes are skipped.
168208
- Files are assumed to be **UTF‑8 compatible text**.
169209
- Very large repositories may produce large outputs; consider using `-x` or `-m` to limit scope.
170210
- Pattern syntax supports `*`, `**`, and `?`, but not advanced glob expressions such as `{}` or `[]`.
211+
- On Linux, clipboard support requires `xclip`, `xsel`, or `wl-copy` to be installed. If none is found, `pmc` will report an error when `-c` is used.

pmc.lua

Lines changed: 99 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
--@author: WaterRun
33
--@file: pmc.lua
44
--@date: 2026-03-08
5-
--@updated: 2026-03-09
5+
--@updated: 2026-03-23
66

77
-- =============================================================================
88
-- Type annotations
@@ -19,6 +19,7 @@
1919
---@field path_mode "relative"|"name"|"absolute"
2020
---@field yaml_mode boolean
2121
---@field output_file string|nil
22+
---@field clipboard boolean
2223
---@field show_version boolean
2324
---@field show_help boolean
2425
---@field user_set_t boolean
@@ -47,7 +48,7 @@
4748
-- Constants
4849
-- =============================================================================
4950

50-
local VERSION = "1"
51+
local VERSION = "2"
5152

5253
local MAX_FILE_SIZE = 1024 * 1024 -- 1 MB
5354

@@ -414,6 +415,47 @@ local function runCommand(cmd)
414415
return true, data, 0
415416
end
416417

418+
-- =============================================================================
419+
-- Clipboard utilities
420+
-- =============================================================================
421+
422+
--@description: detect clipboard command for the current platform
423+
local function getClipboardCommand()
424+
if IS_WINDOWS then
425+
return "clip"
426+
end
427+
-- Linux: try common clipboard tools in order
428+
local tools = {
429+
{ cmd = "xclip -selection clipboard", bin = "xclip" },
430+
{ cmd = "xsel --clipboard --input", bin = "xsel" },
431+
{ cmd = "wl-copy", bin = "wl-copy" },
432+
}
433+
for _, t in ipairs(tools) do
434+
local ok = runCommand("which " .. t.bin .. " >/dev/null 2>&1")
435+
if ok then
436+
return t.cmd
437+
end
438+
end
439+
return nil
440+
end
441+
442+
--@description: write text to system clipboard
443+
local function writeToClipboard(text)
444+
local cmd = getClipboardCommand()
445+
if not cmd then
446+
fail("no clipboard tool found (need clip on Windows, or xclip / xsel / wl-copy on Linux)")
447+
end
448+
local pipe = io.popen(cmd, "w")
449+
if not pipe then
450+
fail("cannot open clipboard command: " .. cmd)
451+
end
452+
pipe:write(text)
453+
local close_ok = pipe:close()
454+
if not close_ok then
455+
fail("clipboard write failed")
456+
end
457+
end
458+
417459
-- =============================================================================
418460
-- Path utilities
419461
-- =============================================================================
@@ -1178,40 +1220,48 @@ local function renderYaml(root, items, target_abs)
11781220
end
11791221

11801222
-- =============================================================================
1181-
-- Output emission
1223+
-- Output assembly and emission
11821224
-- =============================================================================
11831225

1184-
--@description: write sections to output, each section is a list of string entries.
1185-
-- Between sections a blank line is inserted.
1186-
-- Each entry is written via safeWrite followed by a newline.
1187-
-- File content entries may contain embedded newlines — that is fine.
1188-
local function emitOutput(sections, output_file)
1189-
local handle
1190-
if output_file then
1191-
local f, err = io.open(output_file, "wb")
1192-
if not f then
1193-
fail("cannot open output file: " .. output_file .. " (" .. tostring(err) .. ")")
1194-
end
1195-
handle = f
1196-
else
1197-
handle = io.stdout
1198-
end
1199-
1226+
--@description: assemble all sections into a single output string
1227+
local function buildOutputString(sections)
1228+
local parts = {}
12001229
for sect_idx, lines in ipairs(sections) do
12011230
if sect_idx > 1 then
1202-
-- blank line between sections
1203-
handle:write("\n")
1231+
parts[#parts + 1] = "\n"
12041232
end
12051233
for _, entry in ipairs(lines) do
1206-
safeWrite(handle, entry)
1207-
handle:write("\n")
1234+
parts[#parts + 1] = entry
1235+
parts[#parts + 1] = "\n"
12081236
end
12091237
end
1238+
return table.concat(parts)
1239+
end
12101240

1211-
handle:flush()
1241+
--@description: emit output to file, clipboard, or stdout based on options
1242+
-- When -o or -c is specified, stdout is silent.
1243+
-- -o and -c can be used together.
1244+
local function emitOutput(sections, output_file, clipboard)
1245+
local text = buildOutputString(sections)
12121246

12131247
if output_file then
1214-
handle:close()
1248+
local f, err = io.open(output_file, "wb")
1249+
if not f then
1250+
fail("cannot open output file: " .. output_file .. " (" .. tostring(err) .. ")")
1251+
end
1252+
safeWrite(f, text)
1253+
f:flush()
1254+
f:close()
1255+
end
1256+
1257+
if clipboard then
1258+
writeToClipboard(text)
1259+
end
1260+
1261+
-- if neither -o nor -c, write to stdout
1262+
if not output_file and not clipboard then
1263+
safeWrite(io.stdout, text)
1264+
io.stdout:flush()
12151265
end
12161266
end
12171267

@@ -1232,6 +1282,7 @@ local function makeDefaultOptions()
12321282
path_mode = "relative",
12331283
yaml_mode = false,
12341284
output_file = nil,
1285+
clipboard = false,
12351286
show_version = false,
12361287
show_help = false,
12371288
user_set_t = false,
@@ -1314,6 +1365,9 @@ local function parseArgs(argv)
13141365
elseif a == "-y" then
13151366
opt.yaml_mode = true
13161367
i = i + 1
1368+
elseif a == "-c" then
1369+
opt.clipboard = true
1370+
i = i + 1
13171371
elseif a == "-o" then
13181372
local val = argv[i + 1]
13191373
if not val or trim(val) == "" then
@@ -1338,12 +1392,14 @@ local function parseArgs(argv)
13381392
if opt.user_set_t or opt.user_set_s or opt.user_set_w or opt.user_set_p then
13391393
fail("-y cannot be used with -t, -s, -w, or -p")
13401394
end
1341-
if not opt.output_file then
1342-
fail("yaml mode requires -o with .yaml or .yml file")
1395+
if not opt.output_file and not opt.clipboard then
1396+
fail("yaml mode requires -o with .yaml or .yml file, or -c for clipboard")
13431397
end
1344-
local lower = opt.output_file:lower()
1345-
if not (endsWith(lower, ".yaml") or endsWith(lower, ".yml")) then
1346-
fail("yaml output file must end with .yaml or .yml")
1398+
if opt.output_file then
1399+
local lower = opt.output_file:lower()
1400+
if not (endsWith(lower, ".yaml") or endsWith(lower, ".yml")) then
1401+
fail("yaml output file must end with .yaml or .yml")
1402+
end
13471403
end
13481404
end
13491405

@@ -1372,9 +1428,17 @@ Options:
13721428
-w <mode> Wrap mode: md | nil | block
13731429
-p <mode> Path mode: relative | name | absolute
13741430
-y YAML mode (cannot combine with -t -s -w -p)
1431+
-c Copy output to clipboard
13751432
-o <file> Redirect output to file
13761433
-h, --help Show help
13771434
1435+
Clipboard support:
1436+
Windows: uses built-in 'clip' command
1437+
Linux: uses xclip, xsel, or wl-copy (install one)
1438+
1439+
-c and -o can be used together (write to file AND clipboard).
1440+
When -c or -o is used, stdout is silent.
1441+
13781442
Pattern syntax:
13791443
*.lua Match by extension
13801444
.lua Extension shorthand
@@ -1395,6 +1459,9 @@ Examples:
13951459
pmc . -m "*.lua" -x "test/"
13961460
pmc . -r -w block
13971461
pmc . -y -o context.yaml
1462+
pmc . -c
1463+
pmc . -t -s -c
1464+
pmc . -o context.md -c
13981465
]]
13991466
io.write(msg)
14001467
io.stdout:flush()
@@ -1434,7 +1501,7 @@ local function main(argv)
14341501
-- yaml mode: dedicated output path
14351502
if opt.yaml_mode then
14361503
local yaml_lines = renderYaml(root, items, target_abs)
1437-
emitOutput({ yaml_lines }, opt.output_file)
1504+
emitOutput({ yaml_lines }, opt.output_file, opt.clipboard)
14381505
return
14391506
end
14401507

@@ -1451,7 +1518,7 @@ local function main(argv)
14511518
table.insert(sections, renderStats(items))
14521519
end
14531520

1454-
emitOutput(sections, opt.output_file)
1521+
emitOutput(sections, opt.output_file, opt.clipboard)
14551522
end
14561523

14571524
main(arg)

release/pmc.exe

2.38 KB
Binary file not shown.

0 commit comments

Comments
 (0)