楚新元 | All in R

Welcome to R Square

用 R 更优雅地分享图片

楚新元 / 2021-10-17


目前我的个人主页采用 Hugo 框架、hugo-ivy 主题、blogdown 包搭建,GitLab 代码托管,Netlify 网站部署。自从有了这个网站,分享文件变得十分容易,比如,我只需要在 static/img 文件夹下放一张图片 Rlogo.png,那么我一旦推送到 GitLab,GitLab 一有动静就会触发 Netlify 自动部署。部署后这张图片的网址就一定是 https://cxy.rbind.io/img/Rlogo.png,所以只要用 blogdown 搭建一个网站,分享文件就是一件很容易的事情。

其实和本站不相关的文件我是很不情愿放进来的。所以我又搭建了另外一个网站,这个网站因为主要是分享图片为主,所以网站长什么样子就无关紧要了,足够轻量就好,这个网站另外一个用途是测试各种好玩的功能。最后选择了益辉的 hugo-xmin 主题,这个主题很适合 blogdown 初学者学习,删繁就简,极简风格,简约而不简单,后期可以在这个主题的基础上继续美化、增加功能,只有这样你的网站才是有灵魂的,拿来主义就像是看别人放烟花,毫无乐趣可言。

以前,对于分享图片这事,我是把图片放在 static/img 目录下,然后推送到 GitLab 上就可以了。但是最近看到叶寻的博客上有一篇文章《GitHub 图床教程》,他是通过 Python 自动将图片上传到 GitHub 上,这个思路给了我启发,是否可以用 R 实现呢?

搜了下 CRAN,果然没有让我失望,git2r 包可以实现在 R 中操纵 Git 向 GitLab 推送文件的需求。代码如下:

# 加载相关 R 包
library(git2r)  # Linux 下安装 git2r 前务必先安装 libssh2-1-dev

# 初始化本地仓库
local_Rproj = "/path/to/local_Rproj"
repo = init(local_Rproj)

# 定义要上传到 GitLab 上的文件
file = "xxxx.png"
path = "/path/to/"
local_file = paste0(path, file)

# 把要上传的文件复制到本地仓库指定目录下
add_file = paste0(local_Rproj, "/static/img/", file)
file.copy(local_file, add_file)

# 推送文件到 GitLab 指定仓库
add(repo, add_file)
commit(repo, paste("upload", file, sep = " "))
push(
  object = repo,
  name = "origin",
  refspec = "refs/heads/master",
  credentials = cred_ssh_key(),
  set_upstream = TRUE
)

# 生成图片的 url 地址
img_url = paste0("博客地址/img/", file)
print(img_url)

运行上述代码就可以自动把图片上传到 GitLab 上,并输出图片的 URL 地址。但,在运行上述代码前,首先需要通过在 Git 里运行如下代码将密钥转化为 PEM 格式(重新创建 PEM 格式的密钥也是可以的,但是太麻烦)。

# id_rsa文件路径根据实际情况调整
ssh-keygen -p -m PEM -f ~/.ssh/id_rsa

最开始我这边没有把密钥转为 PEM 格式,在 push 那一步报错了,报错日志如下:

Error in push(object = repo, name = "origin", refspec = "refs/heads/master",  : 
  Error in 'git2r_push': Failed to authenticate SSH session: Unable to send userauth-publickey request

这个问题我向叶寻兄弟发邮件寻求帮助,他认认真真重现整个过程,并且通过谷歌搜索到了两个可能的答案,其中一个解决方案是删除私钥底部的空行,这个方案对于我这边无效,但是这个方案指向的一个链接另外一条评论给出的将密钥转化为 PEM 格式却有神奇疗效。在此隆重感谢叶寻兄弟的大力协助。

功能基本实现了,再做点锦上添花的工作,制作一个 Shiny app 让操作更加方便,代码如下:

#---------------------------------------------------------------------------
# 注意事项
#---------------------------------------------------------------------------

# 注意:密钥需要转为PEM格式
# 代码:ssh-keygen -p -m PEM -f ~/.ssh/id_rsa

# Linux 系统需要安装 libgit2:
# Debian: libgit2-dev
# Fedora / CentOS: libgit2-devel
# Arch Linux: libgit2

#---------------------------------------------------------------------------
# 配置环境变量
#---------------------------------------------------------------------------

# Sys.setlocale(category = "LC_ALL", locale = "zh_CN.UTF-8")
local_repo = "../xmin"
blog_url = "https://xmin.netlify.app"

#---------------------------------------------------------------------------
# 定义前端交互界面
#---------------------------------------------------------------------------

library(shiny)
ui = fluidPage(
  fluidRow(
    column(
      width = 6, 
      offset = 3,
      br(),
      br(),
      br(),
      wellPanel(
        fileInput(
          inputId = "choose_file",
          label = "选择上传文件",
          buttonLabel = "浏览...",
          placeholder = "没有选择文件"
        ),
        div(
          style = "display: inline-block;
                   width: 100%;
                   text-align: center;",
          actionButton(
            inputId = "upload_file",
            label = "上传文件",
            class = "btn-success",
          )
        ),
        br(),
        tableOutput("file_url"),
        br()
      )
    )
  )
)

#---------------------------------------------------------------------------
# 定义后台服务逻辑
#---------------------------------------------------------------------------

server = function(input, output, session) {
  
  # 上传文件
  observeEvent(input$upload_file, {
    
    # 定义要上传到 GitLab 上的文件
    file = input$choose_file
    file_name = file$name
    file_path = file$datapath
    
    # 把要上传的文件复制到本地指定目录下
    img_ext = c(
      "png", "PNG", "jpg", "JPG", "jpeg", "JPEG", "bmp",
      "BMP", "svg", "SVG", "ico", "ICO", "tif", "TIF",
      "gif", "GIF", "webp", "WEBP", "tiff", "TIFF"
    )
    ext = tools::file_ext(file_name)
    if (ext %in% img_ext) {
      dest_dir = "img"
    } else {
      dest_dir = "src"
    }
    file_add = file.path(local_repo, "static", dest_dir, file_name)
    file.copy(file_path, file_add)
    Sys.sleep(2)
    
    # 推送文件到 GitLab 指定仓库
    git2r::add(local_repo, file_add)
    git2r::commit(local_repo, paste("upload", file_name, sep = " "))
    git2r::push(
      object = local_repo,
      name = "origin",
      refspec = "refs/heads/master",
      credentials = git2r::cred_ssh_key(),
      set_upstream = TRUE
    )
    
    # 生成图片的 URL 地址
    file_url = file.path(blog_url, dest_dir, file_name)
    
    # 反馈文件的 URL
    output$file_url = renderTable(
      print(file_url),
      colnames = FALSE
    )
    
  })

}

#---------------------------------------------------------------------------
# 运行 Shiny app
#---------------------------------------------------------------------------

shinyApp(ui = ui, server = server)

#---------------------------------------------------------------------------