Port to hugo.
This commit is contained in:
172
content/posts/React-Native-with-Gitlab-CI.md
Normal file
172
content/posts/React-Native-with-Gitlab-CI.md
Normal file
@@ -0,0 +1,172 @@
|
||||
+++
|
||||
title = "React Native with Gitlab CI/CD"
|
||||
date = "2020-04-06"
|
||||
author = "Ceda EI"
|
||||
authorTwitter = "" #do not include @
|
||||
cover = ""
|
||||
tags = ["react-native", "javascript", "gitlab"]
|
||||
keywords = ["react-native", "javascript", "gitlab", "ci", "cd"]
|
||||
description = "Recently, I decided to set up automated builds for [Sonzai](https://gitlab.com/ceda_ei/sonzai.git)."
|
||||
showFullContent = false
|
||||
+++
|
||||
|
||||
## Introduction
|
||||
|
||||
The aim of setting up CI/CD is to automatically build the app on every commit
|
||||
and send the APK. I looked into Gitlab CI/CD for this purpose since my [project
|
||||
repository](https://gitlab.com/ceda_ei/sonzai/) is already hosted on Gitlab.
|
||||
|
||||
## Build Stage
|
||||
|
||||
To get started, I needed to choose a docker image in which the repo would be
|
||||
built. After looking around a bit, I found that the
|
||||
[react-native-community](https://github.com/react-native-community/) has an
|
||||
official [Docker
|
||||
image](https://hub.docker.com/r/reactnativecommunity/react-native-android), the
|
||||
source of which can be found on
|
||||
[GitHub](https://github.com/react-native-community/docker-android).
|
||||
|
||||
Gitlab CI/CD is controlled by a versioned file in the repo: `.gitlab-ci.yml`.
|
||||
To start off, I added the `build` job in the file and set image to the docker
|
||||
image above.
|
||||
|
||||
```yml
|
||||
stages:
|
||||
- build
|
||||
|
||||
build:
|
||||
image: reactnativecommunity/react-native-android
|
||||
stage: build
|
||||
```
|
||||
|
||||
I needed to add the commands to be executed in order to build the app. Those
|
||||
commands are added under the `script` key as an array. In my case, I need to
|
||||
run `yarn install` to install all dependencies followed by `./gradlew
|
||||
assembleRelease` in the `android` directory. The file now looks like.
|
||||
|
||||
```yml
|
||||
stages:
|
||||
- build
|
||||
|
||||
build:
|
||||
image: reactnativecommunity/react-native-android
|
||||
stage: build
|
||||
script:
|
||||
- yarn install
|
||||
- cd android && chmod +x gradlew
|
||||
- ./gradlew assembleRelease
|
||||
```
|
||||
|
||||
Next, once the build is done, I need to export the `outputs` generated in the
|
||||
build to be consumed by the deploy stage. This is done by adding an `artifacts`
|
||||
object which contains a `path` array for all the paths that need to be included
|
||||
in the artifacts. In this case, the outputs are in
|
||||
`android/app/build/outputs/`. The file now looks like:
|
||||
|
||||
```yml
|
||||
stages:
|
||||
- build
|
||||
- deploy
|
||||
|
||||
build:
|
||||
image: reactnativecommunity/react-native-android
|
||||
stage: build
|
||||
script:
|
||||
- yarn install
|
||||
- cd android && chmod +x gradlew
|
||||
- ./gradlew assembleRelease
|
||||
artifacts:
|
||||
paths:
|
||||
- android/app/build/outputs/
|
||||
```
|
||||
|
||||
The build stage is now done.
|
||||
|
||||
## Deploy Stage
|
||||
|
||||
Although I could use the same image, the image is fairly large and takes time
|
||||
to initialize. So, I used
|
||||
[curlimages/curl](https://hub.docker.com/r/curlimages/curl) which is an alpine
|
||||
image with curl added and thus is really light. I will be using `curl` to
|
||||
upload the file to Telegram. Check out the
|
||||
[documentation](core.telegram.org/bots/api) for bots API. Adding the deploy
|
||||
stage, the file looks as:
|
||||
|
||||
```yml
|
||||
stages:
|
||||
- build
|
||||
- deploy
|
||||
|
||||
build:
|
||||
image: reactnativecommunity/react-native-android
|
||||
stage: build
|
||||
script:
|
||||
- yarn install
|
||||
- cd android && chmod +x gradlew
|
||||
- ./gradlew assembleRelease
|
||||
artifacts:
|
||||
paths:
|
||||
- android/app/build/outputs/
|
||||
|
||||
|
||||
deploy_tg:
|
||||
image: curlimages/curl
|
||||
stage: deploy
|
||||
```
|
||||
|
||||
I created a bot via [@BotFather](https://t.me/BotFather) and added it to a
|
||||
[channel](https://t.me/sonzai_builds). Next, I got the channel's chat ID. I
|
||||
stored the Bot Token and the channel's chat ID as variables in Gitlab's UI
|
||||
under Repository > Settings > CI / CD > Variables as `TG_BOT_TOKEN` and
|
||||
`TG_CHAT_ID` respectively.
|
||||
|
||||

|
||||
|
||||
Next, I added a curl request in the script array to make the actual request to
|
||||
Telegram Bot API which utilizes these variables. It also utilizes some
|
||||
predefined variables in [Gitlab's default
|
||||
environment](https://gitlab.com/help/ci/variables/predefined_variables.md).
|
||||
Here is the final `.gitlab-ci.yml`
|
||||
|
||||
```yml
|
||||
stages:
|
||||
- build
|
||||
- deploy
|
||||
|
||||
build:
|
||||
image: reactnativecommunity/react-native-android
|
||||
stage: build
|
||||
script:
|
||||
- yarn install
|
||||
- cd android && chmod +x gradlew
|
||||
- ./gradlew assembleRelease
|
||||
artifacts:
|
||||
paths:
|
||||
- android/app/build/outputs/
|
||||
|
||||
|
||||
deploy_tg:
|
||||
image: curlimages/curl
|
||||
stage: deploy
|
||||
script:
|
||||
- >-
|
||||
curl
|
||||
-F chat_id=$TG_CHAT_ID
|
||||
-F document=@android/app/build/outputs/apk/release/app-release.apk
|
||||
-F caption=" <b>Branch</b>: <code>$CI_COMMIT_BRANCH</code>
|
||||
|
||||
<b>Commit</b>: <code>$CI_COMMIT_SHORT_SHA</code>
|
||||
|
||||
<b>Tag(if any)</b>: <code>$CI_COMMIT_TAG</code>
|
||||
|
||||
|
||||
<code>$CI_COMMIT_MESSAGE</code>"
|
||||
-F parse_mode=html
|
||||
https://api.telegram.org/bot${TG_BOT_TOKEN}/sendDocument
|
||||
```
|
||||
|
||||
Here is the first build using this.
|
||||
|
||||
{{< rawhtml >}}
|
||||
<script async src="https://telegram.org/js/telegram-widget.js?7" data-telegram-post="sonzai_builds/8" data-width="100%"></script>
|
||||
{{< /rawhtml >}}
|
214
content/posts/vim-folds.md
Normal file
214
content/posts/vim-folds.md
Normal file
@@ -0,0 +1,214 @@
|
||||
+++
|
||||
title = "Sorting VCF files with Vim folds"
|
||||
date = "2019-07-23"
|
||||
author = "Ceda EI"
|
||||
authorTwitter = "" #do not include @
|
||||
cover = ""
|
||||
tags = ["vim", "neovim"]
|
||||
keywords = ["vim", "neovim", "folds"]
|
||||
description = "A while ago I wanted to sort my contacts which had become a mess. I wanted to use this opportunity to do something nice with Vim too. Enter Vim-folds."
|
||||
showFullContent = false
|
||||
+++
|
||||
|
||||
|
||||
|
||||
## Getting Started
|
||||
|
||||
I initiated a repository for the plugin.
|
||||
|
||||
```sh
|
||||
cd ~/repos/
|
||||
git init vcf.Vim
|
||||
```
|
||||
|
||||
I use [Vim-plug](https://github.com/junegunn/Vim-plug) for package management
|
||||
in Vim and added that to `Vim-plug`
|
||||
|
||||
```Vim
|
||||
Plug '~/repos/vcf.Vim/'
|
||||
```
|
||||
|
||||
Next, I exported all my contacts into a Virtual Contact File.
|
||||
|
||||
## Filetype Detection
|
||||
|
||||
To get started I needed Vim to assign a filetype to VCFs. A small `autocmd` is
|
||||
enough to take care of that. By convention, filetype detection files go in
|
||||
`ftdetect` directory.
|
||||
|
||||
`ftdetect/vcf.Vim`
|
||||
```Vim
|
||||
au BufNewFile,BufRead *.vcf set filetype=vcf
|
||||
```
|
||||
|
||||
This sets the filetype of all files with filenames ending with `.vcf` to `vcf`
|
||||
|
||||
## Folding
|
||||
|
||||
Plugins for specific filetypes are stored in the `ftplugin` directory. Vim
|
||||
sources the file if the filetype matches the filename without the `.vim`
|
||||
extension e.g. if the filetype is set to `javascript`, `javascript.vim` in
|
||||
`ftplugin` directory will be sourced.
|
||||
|
||||
Each contact in a VCF looks like this:
|
||||
|
||||
```vcf
|
||||
BEGIN:VCARD
|
||||
VERSION:2.1
|
||||
N:Lname;Fname;;;
|
||||
FN:Fname Lname
|
||||
TEL;VOICE:+911234567890
|
||||
END:VCARD
|
||||
```
|
||||
|
||||
I want my folds to look like
|
||||
|
||||
```vcf
|
||||
Fname Lname····································
|
||||
```
|
||||
|
||||
I am going to use `expr` foldmethod. This can simply be done using `set
|
||||
foldmethod=expr`. What this tells vim is to run a function on every line and
|
||||
set the fold level based on that. To set the expression to run, we use `set
|
||||
foldexpr=OurFunction()`.
|
||||
|
||||
Let's write a function first and set `foldexpr` to it. Define the function by
|
||||
the usual syntax.
|
||||
|
||||
```vim
|
||||
function! VCFFold()
|
||||
endfunction
|
||||
|
||||
set foldmethod=expr
|
||||
set foldexpr=VCFFold()
|
||||
```
|
||||
|
||||
When the function is run, vim sets a special variable `v:num` that tells us
|
||||
which line the function is being run on. To get the current line, we use the
|
||||
`getline` function.
|
||||
|
||||
```vim
|
||||
let thisline = getline(v:lnum)
|
||||
```
|
||||
|
||||
We want to start a new level fold at every line which starts with `BEGIN`. For
|
||||
all the other lines, we want to keep the same fold level as previous line since
|
||||
we don't have nested folds in VCF. Vim folds with expr work by running the
|
||||
function on each line and determining the fold level of that line based on the
|
||||
return value of that function. Some of the return values are:
|
||||
|
||||
+ `>n` - This tells vim to start a new `n`th level fold there
|
||||
+ `=` - This tells vim that the fold level is same as previous line.
|
||||
+ `n` - This tells vim that the fold level is `n`
|
||||
|
||||
There are more return values. Check `:help fold-expr`
|
||||
|
||||
We can simply set the fold level to `>1` at every `BEGIN` and set it to `=` on
|
||||
every other line. This way, every contact will end up in a fold starting at the
|
||||
`BEGIN` of every contact. We can simply use `match` function to check if the
|
||||
line begins with `BEGIN` and return `>1` in that case, else we will return `=`.
|
||||
|
||||
|
||||
```vim
|
||||
if match(thisline, '^BEGIN') >= 0
|
||||
return ">1"
|
||||
endif
|
||||
return "="
|
||||
```
|
||||
|
||||
Putting it all together, we get.
|
||||
|
||||
```vim
|
||||
function! VCFFold()
|
||||
let thisline = getline(v:lnum)
|
||||
if match(thisline, '^BEGIN') >= 0
|
||||
return ">1"
|
||||
endif
|
||||
return "="
|
||||
endfunction
|
||||
set foldmethod=expr
|
||||
set foldexpr=VCFFold()
|
||||
```
|
||||
|
||||
## Fold Text
|
||||
|
||||
To set the fold text, we have to set the `foldtext` to a function. Let's create
|
||||
a funtion named `VCFFoldText` for this purpose and set `foldtext` to it.
|
||||
|
||||
```vim
|
||||
function! VCFFoldText()
|
||||
endfunction
|
||||
|
||||
set foldtext=VCFFoldText()
|
||||
```
|
||||
|
||||
The name is stored as a line `N:Lname;Fname;;;`. Two special variables set for
|
||||
foldtext function are `v:foldstart` and `v:foldend` which are the line numbers
|
||||
where the current fold starts and ends. We can iterate from `v:foldstart` to
|
||||
`v:foldend` using the range function. While iterating, we can simply look for a
|
||||
line that begins with `N:` and return the name from it. If we don't find any
|
||||
such line, we can return `No Name`.
|
||||
|
||||
```vim
|
||||
function! VCFFoldText()
|
||||
for i in range(v:foldstart, v:foldend)
|
||||
let l:thisline = getline(i)
|
||||
if match(l:thisline, '^N:') >= 0
|
||||
" Return the string here
|
||||
endif
|
||||
endfor
|
||||
return "No Name"
|
||||
endfunction
|
||||
```
|
||||
|
||||
All we need to do is split the parts on semi-colons and join that array back
|
||||
depending on our preferences of whether we want first name first or last name
|
||||
first. In my case I want first name first.
|
||||
|
||||
```vim
|
||||
let l:parts = split(l:thisline, ';')
|
||||
return substitute(join(l:parts[1:], " ") . l:parts[0][2:], '\s\+', ' ', 'g')
|
||||
```
|
||||
|
||||
`split` splits the string into an array with the second parameter (`;` in this
|
||||
case) as delimiter. I then join all the elements from first element (skipping
|
||||
the zeroth element which is the last name) and then append the zeroth element
|
||||
without the first two characters (since those are `N:`). Finally, I replace
|
||||
multiple spaces with one space.
|
||||
|
||||
## Complete Program
|
||||
|
||||
```vim
|
||||
function! VCFFold()
|
||||
let thisline = getline(v:lnum)
|
||||
if match(thisline, '^BEGIN') >= 0
|
||||
return ">1"
|
||||
endif
|
||||
return "="
|
||||
endfunction
|
||||
set foldmethod=expr
|
||||
set foldexpr=VCFFold()
|
||||
|
||||
function! VCFFoldText()
|
||||
for i in range(v:foldstart, v:foldend)
|
||||
let l:thisline = getline(i)
|
||||
if match(l:thisline, '^N:') >= 0
|
||||
let l:parts = split(l:thisline, ';')
|
||||
return substitute(join(l:parts[1:], " ") . l:parts[0][2:], '\s\+', ' ', 'g')
|
||||
endif
|
||||
endfor
|
||||
return "No Name"
|
||||
endfunction
|
||||
|
||||
set foldtext=VCFFoldText()
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
If you just want the above program, it is available as
|
||||
[vcf.vim](https://gitlab.com/ceda_ei/vcf.vim). You can install it with
|
||||
[Vim-plug](https://github.com/junegunn/Vim-plug) via:
|
||||
|
||||
```vim
|
||||
Plug 'https://gitlab.com/ceda_ei/vcf.vim.git'
|
||||
```
|
Reference in New Issue
Block a user